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.DecimalFormatSymbols;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.CountDownLatch;
import lombok.extern.slf4j.Slf4j;
import monero.common.MoneroRpcConnection;
import monero.daemon.model.MoneroOutput;
import monero.wallet.model.MoneroDestination;
import monero.wallet.model.MoneroTxWallet;
@ -496,4 +499,10 @@ public class HavenoUtils {
}
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.network.p2p.SendDirectMessageListener;
import lombok.extern.slf4j.Slf4j;
import monero.daemon.model.MoneroOutput;
import monero.wallet.model.MoneroTxWallet;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
// 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) {
// re-freeze reserved outputs
if (trade.getSelf().getReserveTxKeyImages() != null) {
trade.getXmrWalletService().freezeOutputs(trade.getSelf().getReserveTxKeyImages());
// thaw deposit inputs
if (depositTx != null) {
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;
@ -129,17 +132,13 @@ public class MaybeSendSignContractRequest extends TradeTask {
// reset protocol timeout
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
BigInteger securityDeposit = trade instanceof BuyerTrade ? trade.getBuyerSecurityDepositBeforeMiningFee() : trade.getSellerSecurityDepositBeforeMiningFee();
trade.getSelf().setSecurityDeposit(securityDeposit.subtract(depositTx.getFee()));
trade.getSelf().setDepositTx(depositTx);
trade.getSelf().setDepositTxHash(depositTx.getHash());
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().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.core.offer.OfferDirection;
import haveno.core.trade.HavenoUtils;
import haveno.core.trade.TakerTrade;
import haveno.core.trade.Trade;
import haveno.core.trade.protocol.TradeProtocol;
import haveno.core.xmr.model.XmrAddressEntry;
import haveno.core.xmr.wallet.XmrWalletService;
import lombok.extern.slf4j.Slf4j;
import monero.daemon.model.MoneroOutput;
import monero.wallet.model.MoneroTxWallet;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
@Slf4j
public class TakerReserveTradeFunds extends TradeTask {
@ -44,6 +42,11 @@ public class TakerReserveTradeFunds extends TradeTask {
try {
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
MoneroTxWallet reserveTx = null;
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();
// attempt creating reserve tx
synchronized (HavenoUtils.getWalletFunctionLock()) {
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
try {
reserveTx = model.getXmrWalletService().createReserveTx(penaltyFee, takerFee, sendAmount, securityDeposit, returnAddress, false, null);
} catch (Exception e) {
log.warn("Error creating reserve tx, attempt={}/{}, tradeId={}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, trade.getShortId(), e.getMessage());
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
try {
synchronized (HavenoUtils.getWalletFunctionLock()) {
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
try {
reserveTx = model.getXmrWalletService().createReserveTx(penaltyFee, takerFee, sendAmount, securityDeposit, returnAddress, false, null);
} catch (Exception e) {
log.warn("Error creating reserve tx, attempt={}/{}, tradeId={}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, trade.getShortId(), e.getMessage());
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
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
trade.getTaker().setReserveTxKeyImages(reservedKeyImages);
trade.getTaker().setReserveTxKeyImages(HavenoUtils.getInputKeyImages(reserveTx));
}
// save process state