mirror of
https://github.com/boldsuck/haveno.git
synced 2024-12-23 04:29:22 +00:00
release wallet lock processing payout tx
This commit is contained in:
parent
b179203dd2
commit
4ec5339e5d
1 changed files with 83 additions and 85 deletions
|
@ -143,9 +143,9 @@ public abstract class Trade implements Tradable, Model {
|
||||||
private final Object pollLock = new Object();
|
private final Object pollLock = new Object();
|
||||||
private final LongProperty walletHeight = new SimpleLongProperty(0);
|
private final LongProperty walletHeight = new SimpleLongProperty(0);
|
||||||
private MoneroWallet wallet;
|
private MoneroWallet wallet;
|
||||||
boolean wasWalletSynced;
|
private boolean wasWalletSynced;
|
||||||
boolean pollInProgress;
|
private boolean pollInProgress;
|
||||||
boolean restartInProgress;
|
private boolean restartInProgress;
|
||||||
private Subscription protocolErrorStateSubscription;
|
private Subscription protocolErrorStateSubscription;
|
||||||
private Subscription protocolErrorHeightSubscription;
|
private Subscription protocolErrorHeightSubscription;
|
||||||
|
|
||||||
|
@ -1127,89 +1127,87 @@ public abstract class Trade implements Tradable, Model {
|
||||||
* @param publish publishes the signed payout tx if true
|
* @param publish publishes the signed payout tx if true
|
||||||
*/
|
*/
|
||||||
public void processPayoutTx(String payoutTxHex, boolean sign, boolean publish) {
|
public void processPayoutTx(String payoutTxHex, boolean sign, boolean publish) {
|
||||||
synchronized (walletLock) {
|
log.info("Processing payout tx for {} {}", getClass().getSimpleName(), getId());
|
||||||
log.info("Processing payout tx for {} {}", getClass().getSimpleName(), getId());
|
|
||||||
|
|
||||||
// gather relevant info
|
// gather relevant info
|
||||||
MoneroWallet wallet = getWallet();
|
MoneroWallet wallet = getWallet();
|
||||||
Contract contract = getContract();
|
Contract contract = getContract();
|
||||||
BigInteger sellerDepositAmount = wallet.getTx(getSeller().getDepositTxHash()).getIncomingAmount(); // TODO (woodser): redundancy of processModel.getPreparedDepositTxId() vs this.getDepositTxId() necessary or avoidable?
|
BigInteger sellerDepositAmount = wallet.getTx(getSeller().getDepositTxHash()).getIncomingAmount(); // TODO (woodser): redundancy of processModel.getPreparedDepositTxId() vs this.getDepositTxId() necessary or avoidable?
|
||||||
BigInteger buyerDepositAmount = wallet.getTx(getBuyer().getDepositTxHash()).getIncomingAmount();
|
BigInteger buyerDepositAmount = wallet.getTx(getBuyer().getDepositTxHash()).getIncomingAmount();
|
||||||
BigInteger tradeAmount = getAmount();
|
BigInteger tradeAmount = getAmount();
|
||||||
|
|
||||||
// describe payout tx
|
// describe payout tx
|
||||||
MoneroTxSet describedTxSet = wallet.describeTxSet(new MoneroTxSet().setMultisigTxHex(payoutTxHex));
|
MoneroTxSet describedTxSet = wallet.describeTxSet(new MoneroTxSet().setMultisigTxHex(payoutTxHex));
|
||||||
if (describedTxSet.getTxs() == null || describedTxSet.getTxs().size() != 1) throw new IllegalArgumentException("Bad payout tx"); // TODO (woodser): test nack
|
if (describedTxSet.getTxs() == null || describedTxSet.getTxs().size() != 1) throw new IllegalArgumentException("Bad payout tx"); // TODO (woodser): test nack
|
||||||
MoneroTxWallet payoutTx = describedTxSet.getTxs().get(0);
|
MoneroTxWallet payoutTx = describedTxSet.getTxs().get(0);
|
||||||
|
|
||||||
// verify payout tx has exactly 2 destinations
|
// verify payout tx has exactly 2 destinations
|
||||||
if (payoutTx.getOutgoingTransfer() == null || payoutTx.getOutgoingTransfer().getDestinations() == null || payoutTx.getOutgoingTransfer().getDestinations().size() != 2) throw new IllegalArgumentException("Payout tx does not have exactly two destinations");
|
if (payoutTx.getOutgoingTransfer() == null || payoutTx.getOutgoingTransfer().getDestinations() == null || payoutTx.getOutgoingTransfer().getDestinations().size() != 2) throw new IllegalArgumentException("Payout tx does not have exactly two destinations");
|
||||||
|
|
||||||
// get buyer and seller destinations (order not preserved)
|
// get buyer and seller destinations (order not preserved)
|
||||||
boolean buyerFirst = payoutTx.getOutgoingTransfer().getDestinations().get(0).getAddress().equals(contract.getBuyerPayoutAddressString());
|
boolean buyerFirst = payoutTx.getOutgoingTransfer().getDestinations().get(0).getAddress().equals(contract.getBuyerPayoutAddressString());
|
||||||
MoneroDestination buyerPayoutDestination = payoutTx.getOutgoingTransfer().getDestinations().get(buyerFirst ? 0 : 1);
|
MoneroDestination buyerPayoutDestination = payoutTx.getOutgoingTransfer().getDestinations().get(buyerFirst ? 0 : 1);
|
||||||
MoneroDestination sellerPayoutDestination = payoutTx.getOutgoingTransfer().getDestinations().get(buyerFirst ? 1 : 0);
|
MoneroDestination sellerPayoutDestination = payoutTx.getOutgoingTransfer().getDestinations().get(buyerFirst ? 1 : 0);
|
||||||
|
|
||||||
// verify payout addresses
|
// verify payout addresses
|
||||||
if (!buyerPayoutDestination.getAddress().equals(contract.getBuyerPayoutAddressString())) throw new IllegalArgumentException("Buyer payout address does not match contract");
|
if (!buyerPayoutDestination.getAddress().equals(contract.getBuyerPayoutAddressString())) throw new IllegalArgumentException("Buyer payout address does not match contract");
|
||||||
if (!sellerPayoutDestination.getAddress().equals(contract.getSellerPayoutAddressString())) throw new IllegalArgumentException("Seller payout address does not match contract");
|
if (!sellerPayoutDestination.getAddress().equals(contract.getSellerPayoutAddressString())) throw new IllegalArgumentException("Seller payout address does not match contract");
|
||||||
|
|
||||||
// verify change address is multisig's primary address
|
// verify change address is multisig's primary address
|
||||||
if (!payoutTx.getChangeAmount().equals(BigInteger.ZERO)) log.warn("Dust left in multisig wallet for {} {}: {}", getClass().getSimpleName(), getId(), payoutTx.getChangeAmount());
|
if (!payoutTx.getChangeAmount().equals(BigInteger.ZERO)) log.warn("Dust left in multisig wallet for {} {}: {}", getClass().getSimpleName(), getId(), payoutTx.getChangeAmount());
|
||||||
if (!payoutTx.getChangeAmount().equals(BigInteger.ZERO) && !payoutTx.getChangeAddress().equals(wallet.getPrimaryAddress())) throw new IllegalArgumentException("Change address is not multisig wallet's primary address");
|
if (!payoutTx.getChangeAmount().equals(BigInteger.ZERO) && !payoutTx.getChangeAddress().equals(wallet.getPrimaryAddress())) throw new IllegalArgumentException("Change address is not multisig wallet's primary address");
|
||||||
|
|
||||||
// verify sum of outputs = destination amounts + change amount
|
// verify sum of outputs = destination amounts + change amount
|
||||||
if (!payoutTx.getOutputSum().equals(buyerPayoutDestination.getAmount().add(sellerPayoutDestination.getAmount()).add(payoutTx.getChangeAmount()))) throw new IllegalArgumentException("Sum of outputs != destination amounts + change amount");
|
if (!payoutTx.getOutputSum().equals(buyerPayoutDestination.getAmount().add(sellerPayoutDestination.getAmount()).add(payoutTx.getChangeAmount()))) throw new IllegalArgumentException("Sum of outputs != destination amounts + change amount");
|
||||||
|
|
||||||
// verify buyer destination amount is deposit amount + this amount - 1/2 tx costs
|
// verify buyer destination amount is deposit amount + this amount - 1/2 tx costs
|
||||||
BigInteger txCost = payoutTx.getFee().add(payoutTx.getChangeAmount());
|
BigInteger txCost = payoutTx.getFee().add(payoutTx.getChangeAmount());
|
||||||
BigInteger txCostSplit = txCost.divide(BigInteger.valueOf(2));
|
BigInteger txCostSplit = txCost.divide(BigInteger.valueOf(2));
|
||||||
BigInteger expectedBuyerPayout = buyerDepositAmount.add(tradeAmount).subtract(txCostSplit);
|
BigInteger expectedBuyerPayout = buyerDepositAmount.add(tradeAmount).subtract(txCostSplit);
|
||||||
if (!buyerPayoutDestination.getAmount().equals(expectedBuyerPayout)) throw new IllegalArgumentException("Buyer destination amount is not deposit amount + trade amount - 1/2 tx costs, " + buyerPayoutDestination.getAmount() + " vs " + expectedBuyerPayout);
|
if (!buyerPayoutDestination.getAmount().equals(expectedBuyerPayout)) throw new IllegalArgumentException("Buyer destination amount is not deposit amount + trade amount - 1/2 tx costs, " + buyerPayoutDestination.getAmount() + " vs " + expectedBuyerPayout);
|
||||||
|
|
||||||
// verify seller destination amount is deposit amount - this amount - 1/2 tx costs
|
// verify seller destination amount is deposit amount - this amount - 1/2 tx costs
|
||||||
BigInteger expectedSellerPayout = sellerDepositAmount.subtract(tradeAmount).subtract(txCostSplit);
|
BigInteger expectedSellerPayout = sellerDepositAmount.subtract(tradeAmount).subtract(txCostSplit);
|
||||||
if (!sellerPayoutDestination.getAmount().equals(expectedSellerPayout)) throw new IllegalArgumentException("Seller destination amount is not deposit amount - trade amount - 1/2 tx costs, " + sellerPayoutDestination.getAmount() + " vs " + expectedSellerPayout);
|
if (!sellerPayoutDestination.getAmount().equals(expectedSellerPayout)) throw new IllegalArgumentException("Seller destination amount is not deposit amount - trade amount - 1/2 tx costs, " + sellerPayoutDestination.getAmount() + " vs " + expectedSellerPayout);
|
||||||
|
|
||||||
// check connection
|
// check connection
|
||||||
if (sign || publish) verifyDaemonConnection();
|
if (sign || publish) verifyDaemonConnection();
|
||||||
|
|
||||||
// handle tx signing
|
// handle tx signing
|
||||||
if (sign) {
|
if (sign) {
|
||||||
|
|
||||||
// sign tx
|
// sign tx
|
||||||
MoneroMultisigSignResult result = wallet.signMultisigTxHex(payoutTxHex);
|
MoneroMultisigSignResult result = wallet.signMultisigTxHex(payoutTxHex);
|
||||||
if (result.getSignedMultisigTxHex() == null) throw new RuntimeException("Error signing payout tx");
|
if (result.getSignedMultisigTxHex() == null) throw new RuntimeException("Error signing payout tx");
|
||||||
payoutTxHex = result.getSignedMultisigTxHex();
|
payoutTxHex = result.getSignedMultisigTxHex();
|
||||||
|
|
||||||
// describe result
|
// describe result
|
||||||
describedTxSet = wallet.describeMultisigTxSet(payoutTxHex);
|
describedTxSet = wallet.describeMultisigTxSet(payoutTxHex);
|
||||||
payoutTx = describedTxSet.getTxs().get(0);
|
payoutTx = describedTxSet.getTxs().get(0);
|
||||||
|
|
||||||
// verify fee is within tolerance by recreating payout tx
|
// verify fee is within tolerance by recreating payout tx
|
||||||
// TODO (monero-project): creating tx will require exchanging updated multisig hex if message needs reprocessed. provide weight with describe_transfer so fee can be estimated?
|
// TODO (monero-project): creating tx will require exchanging updated multisig hex if message needs reprocessed. provide weight with describe_transfer so fee can be estimated?
|
||||||
MoneroTxWallet feeEstimateTx = createPayoutTx();
|
MoneroTxWallet feeEstimateTx = createPayoutTx();
|
||||||
BigInteger feeEstimate = feeEstimateTx.getFee();
|
BigInteger feeEstimate = feeEstimateTx.getFee();
|
||||||
double feeDiff = payoutTx.getFee().subtract(feeEstimate).abs().doubleValue() / feeEstimate.doubleValue(); // TODO: use BigDecimal?
|
double feeDiff = payoutTx.getFee().subtract(feeEstimate).abs().doubleValue() / feeEstimate.doubleValue(); // TODO: use BigDecimal?
|
||||||
if (feeDiff > XmrWalletService.MINER_FEE_TOLERANCE) throw new IllegalArgumentException("Miner fee is not within " + (XmrWalletService.MINER_FEE_TOLERANCE * 100) + "% of estimated fee, expected " + feeEstimate + " but was " + payoutTx.getFee());
|
if (feeDiff > XmrWalletService.MINER_FEE_TOLERANCE) throw new IllegalArgumentException("Miner fee is not within " + (XmrWalletService.MINER_FEE_TOLERANCE * 100) + "% of estimated fee, expected " + feeEstimate + " but was " + payoutTx.getFee());
|
||||||
log.info("Payout tx fee {} is within tolerance, diff %={}", payoutTx.getFee(), feeDiff);
|
log.info("Payout tx fee {} is within tolerance, diff %={}", payoutTx.getFee(), feeDiff);
|
||||||
}
|
}
|
||||||
|
|
||||||
// update trade state
|
// update trade state
|
||||||
setPayoutTx(payoutTx);
|
setPayoutTx(payoutTx);
|
||||||
setPayoutTxHex(payoutTxHex);
|
setPayoutTxHex(payoutTxHex);
|
||||||
|
|
||||||
// submit payout tx
|
// submit payout tx
|
||||||
if (publish) {
|
if (publish) {
|
||||||
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
||||||
try {
|
try {
|
||||||
wallet.submitMultisigTxHex(payoutTxHex);
|
wallet.submitMultisigTxHex(payoutTxHex);
|
||||||
break;
|
break;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("Failed to submit payout tx, attempt={}/{}, tradeId={}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, getShortId(), e.getMessage());
|
log.warn("Failed to submit payout tx, attempt={}/{}, tradeId={}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, getShortId(), e.getMessage());
|
||||||
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||||
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue