stop trade protocol if timeout while creating reserve or deposit tx

This commit is contained in:
woodser 2024-03-25 10:31:36 -04:00 committed by GitHub
parent db12f1c2cb
commit 7eabde63f3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 100 additions and 92 deletions

View file

@ -58,8 +58,7 @@ public class MakerReserveOfferFunds extends Task<PlaceOfferModel> {
Integer preferredSubaddressIndex = fundingEntry == null ? null : fundingEntry.getSubaddressIndex(); Integer preferredSubaddressIndex = fundingEntry == null ? null : fundingEntry.getSubaddressIndex();
MoneroTxWallet reserveTx = model.getXmrWalletService().createReserveTx(makerFee, sendAmount, securityDeposit, returnAddress, model.getOpenOffer().isReserveExactAmount(), preferredSubaddressIndex); MoneroTxWallet reserveTx = model.getXmrWalletService().createReserveTx(makerFee, sendAmount, securityDeposit, returnAddress, model.getOpenOffer().isReserveExactAmount(), preferredSubaddressIndex);
// check for error in case creating reserve tx exceeded timeout // check for error in case creating reserve tx exceeded timeout // TODO: better way?
// TODO: better way?
if (!model.getXmrWalletService().getAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).isPresent()) { 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"); throw new RuntimeException("An error has occurred posting offer " + offer.getId() + " causing its subaddress entry to be deleted");
} }

View file

@ -26,6 +26,7 @@ import haveno.core.trade.MakerTrade;
import haveno.core.trade.Trade; import haveno.core.trade.Trade;
import haveno.core.trade.Trade.State; import haveno.core.trade.Trade.State;
import haveno.core.trade.messages.SignContractRequest; import haveno.core.trade.messages.SignContractRequest;
import haveno.core.trade.protocol.TradeProtocol;
import haveno.core.xmr.model.XmrAddressEntry; import haveno.core.xmr.model.XmrAddressEntry;
import haveno.network.p2p.SendDirectMessageListener; import haveno.network.p2p.SendDirectMessageListener;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -53,103 +54,111 @@ public class MaybeSendSignContractRequest extends TradeTask {
@Override @Override
protected void run() { protected void run() {
try { try {
runInterceptHook(); runInterceptHook();
// skip if arbitrator // skip if arbitrator
if (trade instanceof ArbitratorTrade) { if (trade instanceof ArbitratorTrade) {
complete(); complete();
return; return;
} }
// skip if multisig wallet not complete // skip if multisig wallet not complete
if (processModel.getMultisigAddress() == null) { if (processModel.getMultisigAddress() == null) {
complete(); complete();
return; return;
} }
// skip if deposit tx already created // skip if deposit tx already created
if (trade.getSelf().getDepositTx() != null) { if (trade.getSelf().getDepositTx() != null) {
complete(); complete();
return; return;
} }
// initialize progress steps // initialize progress steps
trade.addInitProgressStep(); trade.addInitProgressStep();
// create deposit tx and freeze inputs // create deposit tx and freeze inputs
Integer subaddressIndex = null; Integer subaddressIndex = null;
boolean reserveExactAmount = false; boolean reserveExactAmount = false;
if (trade instanceof MakerTrade) { if (trade instanceof MakerTrade) {
reserveExactAmount = processModel.getOpenOfferManager().getOpenOfferById(trade.getId()).get().isReserveExactAmount(); reserveExactAmount = processModel.getOpenOfferManager().getOpenOfferById(trade.getId()).get().isReserveExactAmount();
if (reserveExactAmount) subaddressIndex = model.getXmrWalletService().getAddressEntry(trade.getId(), XmrAddressEntry.Context.OFFER_FUNDING).get().getSubaddressIndex(); if (reserveExactAmount) subaddressIndex = model.getXmrWalletService().getAddressEntry(trade.getId(), XmrAddressEntry.Context.OFFER_FUNDING).get().getSubaddressIndex();
} }
MoneroTxWallet depositTx = trade.getXmrWalletService().createDepositTx(trade, reserveExactAmount, subaddressIndex); MoneroTxWallet depositTx = trade.getXmrWalletService().createDepositTx(trade, reserveExactAmount, subaddressIndex);
// collect reserved key images // check if trade still exists
List<String> reservedKeyImages = new ArrayList<String>(); if (!processModel.getTradeManager().hasOpenTrade(trade)) {
for (MoneroOutput input : depositTx.getInputs()) reservedKeyImages.add(input.getKeyImage().getHex()); throw new RuntimeException("Trade protocol has timed out while creating reserve tx, tradeId=" + trade.getId());
}
// save process state // reset protocol timeout
trade.getSelf().setDepositTx(depositTx); trade.getProtocol().startTimeout(TradeProtocol.TRADE_TIMEOUT);
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()));
// TODO: security deposit should be based on trade amount, not max offer amount // collect reserved key images
BigInteger securityDeposit = trade instanceof BuyerTrade ? trade.getBuyerSecurityDepositBeforeMiningFee() : trade.getSellerSecurityDepositBeforeMiningFee(); List<String> reservedKeyImages = new ArrayList<String>();
trade.getSelf().setSecurityDeposit(securityDeposit.subtract(depositTx.getFee())); for (MoneroOutput input : depositTx.getInputs()) reservedKeyImages.add(input.getKeyImage().getHex());
// maker signs deposit hash nonce to avoid challenge protocol // save process state
byte[] sig = null; trade.getSelf().setDepositTx(depositTx);
if (trade instanceof MakerTrade) { trade.getSelf().setDepositTxHash(depositTx.getHash());
sig = HavenoUtils.sign(processModel.getP2PService().getKeyRing(), 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 // TODO: security deposit should be based on trade amount, not max offer amount
SignContractRequest request = new SignContractRequest( BigInteger securityDeposit = trade instanceof BuyerTrade ? trade.getBuyerSecurityDepositBeforeMiningFee() : trade.getSellerSecurityDepositBeforeMiningFee();
trade.getOffer().getId(), trade.getSelf().setSecurityDeposit(securityDeposit.subtract(depositTx.getFee()));
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 // maker signs deposit hash nonce to avoid challenge protocol
processModel.getP2PService().sendEncryptedDirectMessage(trade.getTradePeer().getNodeAddress(), trade.getTradePeer().getPubKeyRing(), request, new SendDirectMessageListener() { byte[] sig = null;
@Override if (trade instanceof MakerTrade) {
public void onArrived() { sig = HavenoUtils.sign(processModel.getP2PService().getKeyRing(), depositTx.getHash());
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 // create request for peer and arbitrator to sign contract
processModel.getP2PService().sendEncryptedDirectMessage(trade.getArbitrator().getNodeAddress(), trade.getArbitrator().getPubKeyRing(), request, new SendDirectMessageListener() { SignContractRequest request = new SignContractRequest(
@Override trade.getOffer().getId(),
public void onArrived() { UUID.randomUUID().toString(),
log.info("{} arrived: trading peer={}; offerId={}; uid={}", request.getClass().getSimpleName(), trade.getArbitrator().getNodeAddress(), trade.getId()); Version.getP2PMessageVersion(),
ack2 = true; new Date().getTime(),
if (ack1 && ack2) completeAux(); trade.getProcessModel().getAccountId(),
} trade.getSelf().getPaymentAccountPayload().getHash(),
@Override trade.getSelf().getPayoutAddressString(),
public void onFault(String errorMessage) { depositTx.getHash(),
log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), trade.getArbitrator().getNodeAddress(), trade.getId(), errorMessage); sig);
appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage);
failed(); // 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) { } catch (Throwable t) {
failed(t); failed(t);
} }

View file

@ -47,15 +47,15 @@ public class TakerReserveTradeFunds extends TradeTask {
String returnAddress = model.getXmrWalletService().getOrCreateAddressEntry(trade.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString(); String returnAddress = model.getXmrWalletService().getOrCreateAddressEntry(trade.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString();
MoneroTxWallet reserveTx = model.getXmrWalletService().createReserveTx(takerFee, sendAmount, securityDeposit, returnAddress, false, null); 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 // collect reserved key images
List<String> reservedKeyImages = new ArrayList<String>(); List<String> reservedKeyImages = new ArrayList<String>();
for (MoneroOutput input : reserveTx.getInputs()) reservedKeyImages.add(input.getKeyImage().getHex()); 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 // reset protocol timeout
trade.getProtocol().startTimeout(TradeProtocol.TRADE_TIMEOUT); trade.getProtocol().startTimeout(TradeProtocol.TRADE_TIMEOUT);