synchronize reserving funds for open offer to fix race condition

This commit is contained in:
woodser 2024-04-21 07:39:40 -04:00
parent 9d9635ff50
commit f0862b7aeb
2 changed files with 38 additions and 26 deletions

View File

@ -20,18 +20,11 @@ package haveno.core.offer.placeoffer.tasks;
import haveno.common.taskrunner.Task; import haveno.common.taskrunner.Task;
import haveno.common.taskrunner.TaskRunner; import haveno.common.taskrunner.TaskRunner;
import haveno.core.offer.Offer; import haveno.core.offer.Offer;
import haveno.core.offer.OfferDirection;
import haveno.core.offer.placeoffer.PlaceOfferModel; import haveno.core.offer.placeoffer.PlaceOfferModel;
import haveno.core.trade.HavenoUtils;
import haveno.core.xmr.model.XmrAddressEntry; import haveno.core.xmr.model.XmrAddressEntry;
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.util.ArrayList;
import java.util.List;
@Slf4j @Slf4j
public class MakerReserveOfferFunds extends Task<PlaceOfferModel> { public class MakerReserveOfferFunds extends Task<PlaceOfferModel> {
@ -51,14 +44,8 @@ public class MakerReserveOfferFunds extends Task<PlaceOfferModel> {
model.getXmrWalletService().getConnectionService().verifyConnection(); model.getXmrWalletService().getConnectionService().verifyConnection();
// create reserve tx // create reserve tx
BigInteger penaltyFee = HavenoUtils.multiply(offer.getAmount(), offer.getPenaltyFeePct()); MoneroTxWallet reserveTx = model.getXmrWalletService().createReserveTx(model.getOpenOffer());
BigInteger makerFee = offer.getMaxMakerFee(); model.setReserveTx(reserveTx);
BigInteger sendAmount = offer.getDirection() == OfferDirection.BUY ? BigInteger.ZERO : offer.getAmount();
BigInteger securityDeposit = offer.getDirection() == OfferDirection.BUY ? offer.getMaxBuyerSecurityDeposit() : offer.getMaxSellerSecurityDeposit();
String returnAddress = model.getXmrWalletService().getOrCreateAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString();
XmrAddressEntry fundingEntry = model.getXmrWalletService().getAddressEntry(offer.getId(), XmrAddressEntry.Context.OFFER_FUNDING).orElse(null);
Integer preferredSubaddressIndex = fundingEntry == null ? null : fundingEntry.getSubaddressIndex();
MoneroTxWallet reserveTx = model.getXmrWalletService().createReserveTx(penaltyFee, 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()) { if (!model.getXmrWalletService().getAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).isPresent()) {
@ -67,17 +54,6 @@ public class MakerReserveOfferFunds extends Task<PlaceOfferModel> {
// reset protocol timeout // reset protocol timeout
model.getProtocol().startTimeoutTimer(); model.getProtocol().startTimeoutTimer();
// collect reserved key images
List<String> reservedKeyImages = new ArrayList<String>();
for (MoneroOutput input : reserveTx.getInputs()) reservedKeyImages.add(input.getKeyImage().getHex());
// save offer state
model.setReserveTx(reserveTx);
model.getOpenOffer().setReserveTxHash(reserveTx.getHash());
model.getOpenOffer().setReserveTxHex(reserveTx.getFullHex());
model.getOpenOffer().setReserveTxKey(reserveTx.getKey());
offer.getOfferPayload().setReserveTxKeyImages(reservedKeyImages);
complete(); complete();
} catch (Throwable t) { } catch (Throwable t) {
offer.setErrorMessage("An error occurred.\n" + offer.setErrorMessage("An error occurred.\n" +

View File

@ -33,6 +33,8 @@ import haveno.common.util.Utilities;
import haveno.core.api.AccountServiceListener; import haveno.core.api.AccountServiceListener;
import haveno.core.api.CoreAccountService; import haveno.core.api.CoreAccountService;
import haveno.core.api.XmrConnectionService; import haveno.core.api.XmrConnectionService;
import haveno.core.offer.Offer;
import haveno.core.offer.OfferDirection;
import haveno.core.offer.OpenOffer; import haveno.core.offer.OpenOffer;
import haveno.core.trade.BuyerTrade; import haveno.core.trade.BuyerTrade;
import haveno.core.trade.HavenoUtils; import haveno.core.trade.HavenoUtils;
@ -546,6 +548,40 @@ public class XmrWalletService {
return new ArrayList<Integer>(subaddressIndices); return new ArrayList<Integer>(subaddressIndices);
} }
/**
* Create a reserve tx for an open offer and freeze its inputs.
*
* @param openOffer is the open offer to create a reserve tx for
*/
public MoneroTxWallet createReserveTx(OpenOffer openOffer) {
synchronized (walletLock) {
// collect offer data
Offer offer = openOffer.getOffer();
BigInteger penaltyFee = HavenoUtils.multiply(offer.getAmount(), offer.getPenaltyFeePct());
BigInteger makerFee = offer.getMaxMakerFee();
BigInteger sendAmount = offer.getDirection() == OfferDirection.BUY ? BigInteger.ZERO : offer.getAmount();
BigInteger securityDeposit = offer.getDirection() == OfferDirection.BUY ? offer.getMaxBuyerSecurityDeposit() : offer.getMaxSellerSecurityDeposit();
String returnAddress = getOrCreateAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString();
XmrAddressEntry fundingEntry = getAddressEntry(offer.getId(), XmrAddressEntry.Context.OFFER_FUNDING).orElse(null);
Integer preferredSubaddressIndex = fundingEntry == null ? null : fundingEntry.getSubaddressIndex();
// create reserve tx
MoneroTxWallet reserveTx = createReserveTx(penaltyFee, makerFee, sendAmount, securityDeposit, returnAddress, openOffer.isReserveExactAmount(), preferredSubaddressIndex);
// collect reserved key images
List<String> reservedKeyImages = new ArrayList<String>();
for (MoneroOutput input : reserveTx.getInputs()) reservedKeyImages.add(input.getKeyImage().getHex());
// save offer state
openOffer.setReserveTxHash(reserveTx.getHash());
openOffer.setReserveTxHex(reserveTx.getFullHex());
openOffer.setReserveTxKey(reserveTx.getKey());
offer.getOfferPayload().setReserveTxKeyImages(reservedKeyImages);
return reserveTx;
}
}
/** /**
* Create the reserve tx and freeze its inputs. The full amount is returned * Create the reserve tx and freeze its inputs. The full amount is returned
* to the sender's payout address less the penalty and mining fees. * to the sender's payout address less the penalty and mining fees.