From 7eabde63f3c623a6fff7c650e93f15c6c0feda0e Mon Sep 17 00:00:00 2001 From: woodser Date: Mon, 25 Mar 2024 10:31:36 -0400 Subject: [PATCH] stop trade protocol if timeout while creating reserve or deposit tx --- .../tasks/MakerReserveOfferFunds.java | 3 +- .../tasks/MaybeSendSignContractRequest.java | 179 +++++++++--------- .../tasks/TakerReserveTradeFunds.java | 10 +- 3 files changed, 100 insertions(+), 92 deletions(-) diff --git a/core/src/main/java/haveno/core/offer/placeoffer/tasks/MakerReserveOfferFunds.java b/core/src/main/java/haveno/core/offer/placeoffer/tasks/MakerReserveOfferFunds.java index 8f9a786d0e..2b1ddfabe8 100644 --- a/core/src/main/java/haveno/core/offer/placeoffer/tasks/MakerReserveOfferFunds.java +++ b/core/src/main/java/haveno/core/offer/placeoffer/tasks/MakerReserveOfferFunds.java @@ -58,8 +58,7 @@ public class MakerReserveOfferFunds extends Task { Integer preferredSubaddressIndex = fundingEntry == null ? null : fundingEntry.getSubaddressIndex(); MoneroTxWallet reserveTx = model.getXmrWalletService().createReserveTx(makerFee, sendAmount, securityDeposit, returnAddress, model.getOpenOffer().isReserveExactAmount(), preferredSubaddressIndex); - // check for error in case creating reserve tx exceeded timeout - // TODO: better way? + // check for error in case creating reserve tx exceeded timeout // TODO: better way? if (!model.getXmrWalletService().getAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).isPresent()) { throw new RuntimeException("An error has occurred posting offer " + offer.getId() + " causing its subaddress entry to be deleted"); } diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/MaybeSendSignContractRequest.java b/core/src/main/java/haveno/core/trade/protocol/tasks/MaybeSendSignContractRequest.java index 7e4eb8ae41..e8096841c4 100644 --- a/core/src/main/java/haveno/core/trade/protocol/tasks/MaybeSendSignContractRequest.java +++ b/core/src/main/java/haveno/core/trade/protocol/tasks/MaybeSendSignContractRequest.java @@ -26,6 +26,7 @@ import haveno.core.trade.MakerTrade; import haveno.core.trade.Trade; import haveno.core.trade.Trade.State; import haveno.core.trade.messages.SignContractRequest; +import haveno.core.trade.protocol.TradeProtocol; import haveno.core.xmr.model.XmrAddressEntry; import haveno.network.p2p.SendDirectMessageListener; import lombok.extern.slf4j.Slf4j; @@ -53,103 +54,111 @@ public class MaybeSendSignContractRequest extends TradeTask { @Override protected void run() { try { - runInterceptHook(); + runInterceptHook(); - // skip if arbitrator - if (trade instanceof ArbitratorTrade) { - complete(); - return; - } + // skip if arbitrator + if (trade instanceof ArbitratorTrade) { + complete(); + return; + } - // skip if multisig wallet not complete - if (processModel.getMultisigAddress() == null) { - complete(); - return; - } + // skip if multisig wallet not complete + if (processModel.getMultisigAddress() == null) { + complete(); + return; + } - // skip if deposit tx already created - if (trade.getSelf().getDepositTx() != null) { - complete(); - return; - } + // skip if deposit tx already created + if (trade.getSelf().getDepositTx() != null) { + complete(); + return; + } - // initialize progress steps - trade.addInitProgressStep(); + // initialize progress steps + trade.addInitProgressStep(); - // create deposit tx and freeze inputs - Integer subaddressIndex = null; - boolean reserveExactAmount = false; - if (trade instanceof MakerTrade) { - reserveExactAmount = processModel.getOpenOfferManager().getOpenOfferById(trade.getId()).get().isReserveExactAmount(); - if (reserveExactAmount) subaddressIndex = model.getXmrWalletService().getAddressEntry(trade.getId(), XmrAddressEntry.Context.OFFER_FUNDING).get().getSubaddressIndex(); - } - MoneroTxWallet depositTx = trade.getXmrWalletService().createDepositTx(trade, reserveExactAmount, subaddressIndex); + // create deposit tx and freeze inputs + Integer subaddressIndex = null; + boolean reserveExactAmount = false; + if (trade instanceof MakerTrade) { + reserveExactAmount = processModel.getOpenOfferManager().getOpenOfferById(trade.getId()).get().isReserveExactAmount(); + if (reserveExactAmount) subaddressIndex = model.getXmrWalletService().getAddressEntry(trade.getId(), XmrAddressEntry.Context.OFFER_FUNDING).get().getSubaddressIndex(); + } + MoneroTxWallet depositTx = trade.getXmrWalletService().createDepositTx(trade, reserveExactAmount, subaddressIndex); - // collect reserved key images - List reservedKeyImages = new ArrayList(); - for (MoneroOutput input : depositTx.getInputs()) reservedKeyImages.add(input.getKeyImage().getHex()); + // check if trade still exists + if (!processModel.getTradeManager().hasOpenTrade(trade)) { + throw new RuntimeException("Trade protocol has timed out while creating reserve tx, tradeId=" + trade.getId()); + } - // save process state - trade.getSelf().setDepositTx(depositTx); - trade.getSelf().setDepositTxHash(depositTx.getHash()); - trade.getSelf().setDepositTxFee(depositTx.getFee()); - trade.getSelf().setReserveTxKeyImages(reservedKeyImages); - trade.getSelf().setPayoutAddressString(trade.getXmrWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString()); // TODO (woodser): allow custom payout address? - trade.getSelf().setPaymentAccountPayload(trade.getProcessModel().getPaymentAccountPayload(trade.getSelf().getPaymentAccountId())); + // reset protocol timeout + trade.getProtocol().startTimeout(TradeProtocol.TRADE_TIMEOUT); - // TODO: security deposit should be based on trade amount, not max offer amount - BigInteger securityDeposit = trade instanceof BuyerTrade ? trade.getBuyerSecurityDepositBeforeMiningFee() : trade.getSellerSecurityDepositBeforeMiningFee(); - trade.getSelf().setSecurityDeposit(securityDeposit.subtract(depositTx.getFee())); + // collect reserved key images + List reservedKeyImages = new ArrayList(); + for (MoneroOutput input : depositTx.getInputs()) reservedKeyImages.add(input.getKeyImage().getHex()); - // maker signs deposit hash nonce to avoid challenge protocol - byte[] sig = null; - if (trade instanceof MakerTrade) { - sig = HavenoUtils.sign(processModel.getP2PService().getKeyRing(), depositTx.getHash()); - } + // save process state + trade.getSelf().setDepositTx(depositTx); + trade.getSelf().setDepositTxHash(depositTx.getHash()); + trade.getSelf().setDepositTxFee(depositTx.getFee()); + trade.getSelf().setReserveTxKeyImages(reservedKeyImages); + trade.getSelf().setPayoutAddressString(trade.getXmrWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString()); // TODO (woodser): allow custom payout address? + trade.getSelf().setPaymentAccountPayload(trade.getProcessModel().getPaymentAccountPayload(trade.getSelf().getPaymentAccountId())); - // create request for peer and arbitrator to sign contract - SignContractRequest request = new SignContractRequest( - trade.getOffer().getId(), - UUID.randomUUID().toString(), - Version.getP2PMessageVersion(), - new Date().getTime(), - trade.getProcessModel().getAccountId(), - trade.getSelf().getPaymentAccountPayload().getHash(), - trade.getSelf().getPayoutAddressString(), - depositTx.getHash(), - sig); + // TODO: security deposit should be based on trade amount, not max offer amount + BigInteger securityDeposit = trade instanceof BuyerTrade ? trade.getBuyerSecurityDepositBeforeMiningFee() : trade.getSellerSecurityDepositBeforeMiningFee(); + trade.getSelf().setSecurityDeposit(securityDeposit.subtract(depositTx.getFee())); - // send request to trading peer - processModel.getP2PService().sendEncryptedDirectMessage(trade.getTradePeer().getNodeAddress(), trade.getTradePeer().getPubKeyRing(), request, new SendDirectMessageListener() { - @Override - public void onArrived() { - log.info("{} arrived: trading peer={}; offerId={}; uid={}", request.getClass().getSimpleName(), trade.getTradePeer().getNodeAddress(), trade.getId()); - ack1 = true; - if (ack1 && ack2) completeAux(); - } - @Override - public void onFault(String errorMessage) { - log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), trade.getTradePeer().getNodeAddress(), trade.getId(), errorMessage); - appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage); - failed(); - } - }); + // maker signs deposit hash nonce to avoid challenge protocol + byte[] sig = null; + if (trade instanceof MakerTrade) { + sig = HavenoUtils.sign(processModel.getP2PService().getKeyRing(), depositTx.getHash()); + } - // send request to arbitrator - processModel.getP2PService().sendEncryptedDirectMessage(trade.getArbitrator().getNodeAddress(), trade.getArbitrator().getPubKeyRing(), request, new SendDirectMessageListener() { - @Override - public void onArrived() { - log.info("{} arrived: trading peer={}; offerId={}; uid={}", request.getClass().getSimpleName(), trade.getArbitrator().getNodeAddress(), trade.getId()); - ack2 = true; - if (ack1 && ack2) completeAux(); - } - @Override - public void onFault(String errorMessage) { - log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), trade.getArbitrator().getNodeAddress(), trade.getId(), errorMessage); - appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage); - failed(); - } - }); + // create request for peer and arbitrator to sign contract + SignContractRequest request = new SignContractRequest( + trade.getOffer().getId(), + UUID.randomUUID().toString(), + Version.getP2PMessageVersion(), + new Date().getTime(), + trade.getProcessModel().getAccountId(), + trade.getSelf().getPaymentAccountPayload().getHash(), + trade.getSelf().getPayoutAddressString(), + depositTx.getHash(), + sig); + + // send request to trading peer + processModel.getP2PService().sendEncryptedDirectMessage(trade.getTradePeer().getNodeAddress(), trade.getTradePeer().getPubKeyRing(), request, new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived: trading peer={}; offerId={}; uid={}", request.getClass().getSimpleName(), trade.getTradePeer().getNodeAddress(), trade.getId()); + ack1 = true; + if (ack1 && ack2) completeAux(); + } + @Override + public void onFault(String errorMessage) { + log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), trade.getTradePeer().getNodeAddress(), trade.getId(), errorMessage); + appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage); + failed(); + } + }); + + // send request to arbitrator + processModel.getP2PService().sendEncryptedDirectMessage(trade.getArbitrator().getNodeAddress(), trade.getArbitrator().getPubKeyRing(), request, new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived: trading peer={}; offerId={}; uid={}", request.getClass().getSimpleName(), trade.getArbitrator().getNodeAddress(), trade.getId()); + ack2 = true; + if (ack1 && ack2) completeAux(); + } + @Override + public void onFault(String errorMessage) { + log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), trade.getArbitrator().getNodeAddress(), trade.getId(), errorMessage); + appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage); + failed(); + } + }); } catch (Throwable t) { failed(t); } diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/TakerReserveTradeFunds.java b/core/src/main/java/haveno/core/trade/protocol/tasks/TakerReserveTradeFunds.java index b31b30efee..e43e790fdc 100644 --- a/core/src/main/java/haveno/core/trade/protocol/tasks/TakerReserveTradeFunds.java +++ b/core/src/main/java/haveno/core/trade/protocol/tasks/TakerReserveTradeFunds.java @@ -47,15 +47,15 @@ public class TakerReserveTradeFunds extends TradeTask { String returnAddress = model.getXmrWalletService().getOrCreateAddressEntry(trade.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString(); MoneroTxWallet reserveTx = model.getXmrWalletService().createReserveTx(takerFee, sendAmount, securityDeposit, returnAddress, false, null); + // check if trade still exists + if (!processModel.getTradeManager().hasOpenTrade(trade)) { + throw new RuntimeException("Trade protocol has timed out while creating reserve tx, tradeId=" + trade.getId()); + } + // collect reserved key images List reservedKeyImages = new ArrayList(); for (MoneroOutput input : reserveTx.getInputs()) reservedKeyImages.add(input.getKeyImage().getHex()); - // check if trade still exists - if (!processModel.getTradeManager().hasOpenTrade(trade)) { - throw new RuntimeException("Trade protocol no longer exists after creating reserve tx, tradeId=" + trade.getId()); - } - // reset protocol timeout trade.getProtocol().startTimeout(TradeProtocol.TRADE_TIMEOUT);