thaw reserved inputs and re-freeze offer inputs on create tx errors

This commit is contained in:
woodser 2024-05-08 07:42:51 -04:00
parent 5d7991e4f7
commit f1b8cd1e2e
3 changed files with 50 additions and 31 deletions

View file

@ -45,12 +45,15 @@ import java.security.PrivateKey;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols; import java.text.DecimalFormatSymbols;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import monero.common.MoneroRpcConnection; import monero.common.MoneroRpcConnection;
import monero.daemon.model.MoneroOutput;
import monero.wallet.model.MoneroDestination; import monero.wallet.model.MoneroDestination;
import monero.wallet.model.MoneroTxWallet; import monero.wallet.model.MoneroTxWallet;
@ -496,4 +499,10 @@ public class HavenoUtils {
} }
return null; return null;
} }
public static List<String> getInputKeyImages(MoneroTxWallet tx) {
List<String> inputKeyImages = new ArrayList<String>();
for (MoneroOutput input : tx.getInputs()) inputKeyImages.add(input.getKeyImage().getHex());
return inputKeyImages;
}
} }

View file

@ -31,13 +31,10 @@ import haveno.core.xmr.model.XmrAddressEntry;
import haveno.core.xmr.wallet.XmrWalletService; import haveno.core.xmr.wallet.XmrWalletService;
import haveno.network.p2p.SendDirectMessageListener; import haveno.network.p2p.SendDirectMessageListener;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import monero.daemon.model.MoneroOutput;
import monero.wallet.model.MoneroTxWallet; import monero.wallet.model.MoneroTxWallet;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List;
import java.util.UUID; import java.util.UUID;
// TODO (woodser): separate classes for deposit tx creation and contract request, or combine into ProcessInitMultisigRequest // TODO (woodser): separate classes for deposit tx creation and contract request, or combine into ProcessInitMultisigRequest
@ -118,9 +115,15 @@ public class MaybeSendSignContractRequest extends TradeTask {
} }
} catch (Exception e) { } catch (Exception e) {
// re-freeze reserved outputs // thaw deposit inputs
if (trade.getSelf().getReserveTxKeyImages() != null) { if (depositTx != null) {
trade.getXmrWalletService().freezeOutputs(trade.getSelf().getReserveTxKeyImages()); trade.getXmrWalletService().thawOutputs(HavenoUtils.getInputKeyImages(depositTx));
trade.getSelf().setReserveTxKeyImages(null);
}
// re-freeze maker offer inputs
if (trade instanceof MakerTrade) {
trade.getXmrWalletService().freezeOutputs(trade.getOffer().getOfferPayload().getReserveTxKeyImages());
} }
throw e; throw e;
@ -129,17 +132,13 @@ public class MaybeSendSignContractRequest extends TradeTask {
// reset protocol timeout // reset protocol timeout
trade.addInitProgressStep(); trade.addInitProgressStep();
// collect reserved key images
List<String> reservedKeyImages = new ArrayList<String>();
for (MoneroOutput input : depositTx.getInputs()) reservedKeyImages.add(input.getKeyImage().getHex());
// update trade state // update trade state
BigInteger securityDeposit = trade instanceof BuyerTrade ? trade.getBuyerSecurityDepositBeforeMiningFee() : trade.getSellerSecurityDepositBeforeMiningFee(); BigInteger securityDeposit = trade instanceof BuyerTrade ? trade.getBuyerSecurityDepositBeforeMiningFee() : trade.getSellerSecurityDepositBeforeMiningFee();
trade.getSelf().setSecurityDeposit(securityDeposit.subtract(depositTx.getFee())); trade.getSelf().setSecurityDeposit(securityDeposit.subtract(depositTx.getFee()));
trade.getSelf().setDepositTx(depositTx); trade.getSelf().setDepositTx(depositTx);
trade.getSelf().setDepositTxHash(depositTx.getHash()); trade.getSelf().setDepositTxHash(depositTx.getHash());
trade.getSelf().setDepositTxFee(depositTx.getFee()); trade.getSelf().setDepositTxFee(depositTx.getFee());
trade.getSelf().setReserveTxKeyImages(reservedKeyImages); trade.getSelf().setReserveTxKeyImages(HavenoUtils.getInputKeyImages(depositTx));
trade.getSelf().setPayoutAddressString(trade.getXmrWalletService().getOrCreateAddressEntry(trade.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString()); // TODO (woodser): allow custom payout address? trade.getSelf().setPayoutAddressString(trade.getXmrWalletService().getOrCreateAddressEntry(trade.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString()); // TODO (woodser): allow custom payout address?
trade.getSelf().setPaymentAccountPayload(trade.getProcessModel().getPaymentAccountPayload(trade.getSelf().getPaymentAccountId())); trade.getSelf().setPaymentAccountPayload(trade.getProcessModel().getPaymentAccountPayload(trade.getSelf().getPaymentAccountId()));
} }

View file

@ -20,17 +20,15 @@ package haveno.core.trade.protocol.tasks;
import haveno.common.taskrunner.TaskRunner; import haveno.common.taskrunner.TaskRunner;
import haveno.core.offer.OfferDirection; import haveno.core.offer.OfferDirection;
import haveno.core.trade.HavenoUtils; import haveno.core.trade.HavenoUtils;
import haveno.core.trade.TakerTrade;
import haveno.core.trade.Trade; import haveno.core.trade.Trade;
import haveno.core.trade.protocol.TradeProtocol; import haveno.core.trade.protocol.TradeProtocol;
import haveno.core.xmr.model.XmrAddressEntry; import haveno.core.xmr.model.XmrAddressEntry;
import haveno.core.xmr.wallet.XmrWalletService; import haveno.core.xmr.wallet.XmrWalletService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import monero.daemon.model.MoneroOutput;
import monero.wallet.model.MoneroTxWallet; import monero.wallet.model.MoneroTxWallet;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
@Slf4j @Slf4j
public class TakerReserveTradeFunds extends TradeTask { public class TakerReserveTradeFunds extends TradeTask {
@ -44,6 +42,11 @@ public class TakerReserveTradeFunds extends TradeTask {
try { try {
runInterceptHook(); runInterceptHook();
// taker trade expected
if (!(trade instanceof TakerTrade)) {
throw new RuntimeException("Expected taker trade but was " + trade.getClass().getSimpleName() + " " + trade.getShortId() + ". That should never happen.");
}
// create reserve tx // create reserve tx
MoneroTxWallet reserveTx = null; MoneroTxWallet reserveTx = null;
synchronized (XmrWalletService.WALLET_LOCK) { synchronized (XmrWalletService.WALLET_LOCK) {
@ -60,31 +63,39 @@ public class TakerReserveTradeFunds extends TradeTask {
String returnAddress = trade.getXmrWalletService().getOrCreateAddressEntry(trade.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString(); String returnAddress = trade.getXmrWalletService().getOrCreateAddressEntry(trade.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString();
// attempt creating reserve tx // attempt creating reserve tx
synchronized (HavenoUtils.getWalletFunctionLock()) { try {
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) { synchronized (HavenoUtils.getWalletFunctionLock()) {
try { for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
reserveTx = model.getXmrWalletService().createReserveTx(penaltyFee, takerFee, sendAmount, securityDeposit, returnAddress, false, null); try {
} catch (Exception e) { reserveTx = model.getXmrWalletService().createReserveTx(penaltyFee, takerFee, sendAmount, securityDeposit, returnAddress, false, null);
log.warn("Error creating reserve tx, attempt={}/{}, tradeId={}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, trade.getShortId(), e.getMessage()); } catch (Exception e) {
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e; log.warn("Error creating reserve tx, attempt={}/{}, tradeId={}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, trade.getShortId(), e.getMessage());
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
}
// check for timeout
if (isTimedOut()) throw new RuntimeException("Trade protocol has timed out while creating reserve tx, tradeId=" + trade.getShortId());
if (reserveTx != null) break;
} }
// check for timeout
if (isTimedOut()) throw new RuntimeException("Trade protocol has timed out while creating reserve tx, tradeId=" + trade.getShortId());
if (reserveTx != null) break;
} }
} catch (Exception e) {
// thaw reserved inputs
if (reserveTx != null) {
model.getXmrWalletService().thawOutputs(HavenoUtils.getInputKeyImages(reserveTx));
trade.getSelf().setReserveTxKeyImages(null);
}
throw e;
} }
// reset protocol timeout // reset protocol timeout
trade.startProtocolTimeout(); trade.startProtocolTimeout();
// collect reserved key images
List<String> reservedKeyImages = new ArrayList<String>();
for (MoneroOutput input : reserveTx.getInputs()) reservedKeyImages.add(input.getKeyImage().getHex());
// update trade state // update trade state
trade.getTaker().setReserveTxKeyImages(reservedKeyImages); trade.getTaker().setReserveTxKeyImages(HavenoUtils.getInputKeyImages(reserveTx));
} }
// save process state // save process state