mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-06-24 23:00:36 -04:00
maker recreates reserve tx then cancels offer on trade nacks
This commit is contained in:
parent
264e5f436e
commit
33a91cf980
6 changed files with 231 additions and 15 deletions
|
@ -276,6 +276,10 @@ public final class OpenOffer implements Tradable {
|
||||||
return state == State.AVAILABLE;
|
return state == State.AVAILABLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isReserved() {
|
||||||
|
return state == State.RESERVED;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isDeactivated() {
|
public boolean isDeactivated() {
|
||||||
return state == State.DEACTIVATED;
|
return state == State.DEACTIVATED;
|
||||||
}
|
}
|
||||||
|
|
|
@ -661,7 +661,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
ErrorMessageHandler errorMessageHandler) {
|
ErrorMessageHandler errorMessageHandler) {
|
||||||
log.info("Canceling open offer: {}", openOffer.getId());
|
log.info("Canceling open offer: {}", openOffer.getId());
|
||||||
if (!offersToBeEdited.containsKey(openOffer.getId())) {
|
if (!offersToBeEdited.containsKey(openOffer.getId())) {
|
||||||
if (openOffer.isAvailable()) {
|
if (isOnOfferBook(openOffer)) {
|
||||||
openOffer.setState(OpenOffer.State.CANCELED);
|
openOffer.setState(OpenOffer.State.CANCELED);
|
||||||
offerBookService.removeOffer(openOffer.getOffer().getOfferPayload(),
|
offerBookService.removeOffer(openOffer.getOffer().getOfferPayload(),
|
||||||
() -> {
|
() -> {
|
||||||
|
@ -683,6 +683,10 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isOnOfferBook(OpenOffer openOffer) {
|
||||||
|
return openOffer.isAvailable() || openOffer.isReserved();
|
||||||
|
}
|
||||||
|
|
||||||
public void editOpenOfferStart(OpenOffer openOffer,
|
public void editOpenOfferStart(OpenOffer openOffer,
|
||||||
ResultHandler resultHandler,
|
ResultHandler resultHandler,
|
||||||
ErrorMessageHandler errorMessageHandler) {
|
ErrorMessageHandler errorMessageHandler) {
|
||||||
|
|
|
@ -997,7 +997,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||||
log.warn("Unregistering {} {}", trade.getClass().getSimpleName(), trade.getId());
|
log.warn("Unregistering {} {}", trade.getClass().getSimpleName(), trade.getId());
|
||||||
removeTrade(trade, true);
|
removeTrade(trade, true);
|
||||||
removeFailedTrade(trade);
|
removeFailedTrade(trade);
|
||||||
xmrWalletService.swapPayoutAddressEntryToAvailable(trade.getId()); // TODO The address entry should have been removed already. Check and if its the case remove that.
|
if (!trade.isMaker()) xmrWalletService.swapPayoutAddressEntryToAvailable(trade.getId()); // TODO The address entry should have been removed already. Check and if its the case remove that.
|
||||||
requestPersistence();
|
requestPersistence();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,7 @@ import haveno.common.handlers.ErrorMessageHandler;
|
||||||
import haveno.common.proto.network.NetworkEnvelope;
|
import haveno.common.proto.network.NetworkEnvelope;
|
||||||
import haveno.common.taskrunner.Task;
|
import haveno.common.taskrunner.Task;
|
||||||
import haveno.core.network.MessageState;
|
import haveno.core.network.MessageState;
|
||||||
|
import haveno.core.offer.OpenOffer;
|
||||||
import haveno.core.trade.ArbitratorTrade;
|
import haveno.core.trade.ArbitratorTrade;
|
||||||
import haveno.core.trade.BuyerTrade;
|
import haveno.core.trade.BuyerTrade;
|
||||||
import haveno.core.trade.HavenoUtils;
|
import haveno.core.trade.HavenoUtils;
|
||||||
|
@ -55,13 +56,17 @@ import haveno.core.trade.messages.DepositRequest;
|
||||||
import haveno.core.trade.messages.DepositResponse;
|
import haveno.core.trade.messages.DepositResponse;
|
||||||
import haveno.core.trade.messages.DepositsConfirmedMessage;
|
import haveno.core.trade.messages.DepositsConfirmedMessage;
|
||||||
import haveno.core.trade.messages.InitMultisigRequest;
|
import haveno.core.trade.messages.InitMultisigRequest;
|
||||||
|
import haveno.core.trade.messages.InitTradeRequest;
|
||||||
import haveno.core.trade.messages.PaymentReceivedMessage;
|
import haveno.core.trade.messages.PaymentReceivedMessage;
|
||||||
import haveno.core.trade.messages.PaymentSentMessage;
|
import haveno.core.trade.messages.PaymentSentMessage;
|
||||||
import haveno.core.trade.messages.SignContractRequest;
|
import haveno.core.trade.messages.SignContractRequest;
|
||||||
import haveno.core.trade.messages.SignContractResponse;
|
import haveno.core.trade.messages.SignContractResponse;
|
||||||
import haveno.core.trade.messages.TradeMessage;
|
import haveno.core.trade.messages.TradeMessage;
|
||||||
import haveno.core.trade.protocol.FluentProtocol.Condition;
|
import haveno.core.trade.protocol.FluentProtocol.Condition;
|
||||||
|
import haveno.core.trade.protocol.FluentProtocol.Event;
|
||||||
import haveno.core.trade.protocol.tasks.ApplyFilter;
|
import haveno.core.trade.protocol.tasks.ApplyFilter;
|
||||||
|
import haveno.core.trade.protocol.tasks.MakerRecreateReserveTx;
|
||||||
|
import haveno.core.trade.protocol.tasks.MakerSendInitTradeRequestToArbitrator;
|
||||||
import haveno.core.trade.protocol.tasks.MaybeSendSignContractRequest;
|
import haveno.core.trade.protocol.tasks.MaybeSendSignContractRequest;
|
||||||
import haveno.core.trade.protocol.tasks.ProcessDepositResponse;
|
import haveno.core.trade.protocol.tasks.ProcessDepositResponse;
|
||||||
import haveno.core.trade.protocol.tasks.ProcessDepositsConfirmedMessage;
|
import haveno.core.trade.protocol.tasks.ProcessDepositsConfirmedMessage;
|
||||||
|
@ -110,6 +115,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||||
private boolean depositsConfirmedTasksCalled;
|
private boolean depositsConfirmedTasksCalled;
|
||||||
private int reprocessPaymentSentMessageCount;
|
private int reprocessPaymentSentMessageCount;
|
||||||
private int reprocessPaymentReceivedMessageCount;
|
private int reprocessPaymentReceivedMessageCount;
|
||||||
|
private boolean makerInitTradeRequestNacked = false;
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Constructor
|
// Constructor
|
||||||
|
@ -758,6 +764,18 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||||
peer.setNodeAddress(sender);
|
peer.setNodeAddress(sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: arbitrator may nack maker's InitTradeRequest if reserve tx has become invalid (e.g. check_tx_key shows 0 funds received). recreate reserve tx in this case
|
||||||
|
if (!ackMessage.isSuccess() && trade.isMaker() && peer == trade.getArbitrator() && ackMessage.getSourceMsgClassName().equals(InitTradeRequest.class.getSimpleName())) {
|
||||||
|
if (makerInitTradeRequestNacked) {
|
||||||
|
handleSecondMakerInitTradeRequestNack(ackMessage);
|
||||||
|
// use default postprocessing
|
||||||
|
} else {
|
||||||
|
makerInitTradeRequestNacked = true;
|
||||||
|
handleFirstMakerInitTradeRequestNack(ackMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// handle nack of deposit request
|
// handle nack of deposit request
|
||||||
if (ackMessage.getSourceMsgClassName().equals(DepositRequest.class.getSimpleName())) {
|
if (ackMessage.getSourceMsgClassName().equals(DepositRequest.class.getSimpleName())) {
|
||||||
if (!ackMessage.isSuccess()) {
|
if (!ackMessage.isSuccess()) {
|
||||||
|
@ -774,12 +792,12 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||||
|
|
||||||
// handle ack message for PaymentSentMessage, which automatically re-sends if not ACKed in a certain time
|
// handle ack message for PaymentSentMessage, which automatically re-sends if not ACKed in a certain time
|
||||||
if (ackMessage.getSourceMsgClassName().equals(PaymentSentMessage.class.getSimpleName())) {
|
if (ackMessage.getSourceMsgClassName().equals(PaymentSentMessage.class.getSimpleName())) {
|
||||||
if (trade.getTradePeer(sender) == trade.getSeller()) {
|
if (peer == trade.getSeller()) {
|
||||||
trade.getSeller().setPaymentSentAckMessage(ackMessage);
|
trade.getSeller().setPaymentSentAckMessage(ackMessage);
|
||||||
if (ackMessage.isSuccess()) trade.setStateIfValidTransitionTo(Trade.State.SELLER_RECEIVED_PAYMENT_SENT_MSG);
|
if (ackMessage.isSuccess()) trade.setStateIfValidTransitionTo(Trade.State.SELLER_RECEIVED_PAYMENT_SENT_MSG);
|
||||||
else trade.setState(Trade.State.BUYER_SEND_FAILED_PAYMENT_SENT_MSG);
|
else trade.setState(Trade.State.BUYER_SEND_FAILED_PAYMENT_SENT_MSG);
|
||||||
processModel.getTradeManager().requestPersistence();
|
processModel.getTradeManager().requestPersistence();
|
||||||
} else if (trade.getTradePeer(sender) == trade.getArbitrator()) {
|
} else if (peer == trade.getArbitrator()) {
|
||||||
trade.getArbitrator().setPaymentSentAckMessage(ackMessage);
|
trade.getArbitrator().setPaymentSentAckMessage(ackMessage);
|
||||||
processModel.getTradeManager().requestPersistence();
|
processModel.getTradeManager().requestPersistence();
|
||||||
} else {
|
} else {
|
||||||
|
@ -792,7 +810,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||||
if (ackMessage.getSourceMsgClassName().equals(PaymentReceivedMessage.class.getSimpleName())) {
|
if (ackMessage.getSourceMsgClassName().equals(PaymentReceivedMessage.class.getSimpleName())) {
|
||||||
|
|
||||||
// ack message from buyer
|
// ack message from buyer
|
||||||
if (trade.getTradePeer(sender) == trade.getBuyer()) {
|
if (peer == trade.getBuyer()) {
|
||||||
trade.getBuyer().setPaymentReceivedAckMessage(ackMessage);
|
trade.getBuyer().setPaymentReceivedAckMessage(ackMessage);
|
||||||
|
|
||||||
// handle successful ack
|
// handle successful ack
|
||||||
|
@ -819,7 +837,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||||
}
|
}
|
||||||
|
|
||||||
// ack message from arbitrator
|
// ack message from arbitrator
|
||||||
else if (trade.getTradePeer(sender) == trade.getArbitrator()) {
|
else if (peer == trade.getArbitrator()) {
|
||||||
trade.getArbitrator().setPaymentReceivedAckMessage(ackMessage);
|
trade.getArbitrator().setPaymentReceivedAckMessage(ackMessage);
|
||||||
|
|
||||||
// handle nack
|
// handle nack
|
||||||
|
@ -856,6 +874,48 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||||
trade.onAckMessage(ackMessage, sender);
|
trade.onAckMessage(ackMessage, sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleFirstMakerInitTradeRequestNack(AckMessage ackMessage) {
|
||||||
|
log.warn("Maker received NACK to InitTradeRequest from arbitrator for {} {}, messageUid={}, errorMessage={}", trade.getClass().getSimpleName(), trade.getId(), ackMessage.getSourceUid(), ackMessage.getErrorMessage());
|
||||||
|
ThreadUtils.execute(() -> {
|
||||||
|
Event event = new Event() {
|
||||||
|
@Override
|
||||||
|
public String name() {
|
||||||
|
return "MakerRecreateReserveTx";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
synchronized (trade.getLock()) {
|
||||||
|
latchTrade();
|
||||||
|
expect(phase(Trade.Phase.INIT)
|
||||||
|
.with(event))
|
||||||
|
.setup(tasks(
|
||||||
|
MakerRecreateReserveTx.class,
|
||||||
|
MakerSendInitTradeRequestToArbitrator.class)
|
||||||
|
.using(new TradeTaskRunner(trade,
|
||||||
|
() -> {
|
||||||
|
startTimeout();
|
||||||
|
unlatchTrade();
|
||||||
|
},
|
||||||
|
errorMessage -> {
|
||||||
|
handleError("Failed to re-send InitTradeRequest to arbitrator for " + trade.getClass().getSimpleName() + " " + trade.getId() + ": " + errorMessage);
|
||||||
|
}))
|
||||||
|
.withTimeout(TRADE_STEP_TIMEOUT_SECONDS))
|
||||||
|
.executeTasks(true);
|
||||||
|
awaitTradeLatch();
|
||||||
|
}
|
||||||
|
}, trade.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSecondMakerInitTradeRequestNack(AckMessage ackMessage) {
|
||||||
|
log.warn("Maker received 2nd NACK to InitTradeRequest from arbitrator for {} {}, messageUid={}, errorMessage={}", trade.getClass().getSimpleName(), trade.getId(), ackMessage.getSourceUid(), ackMessage.getErrorMessage());
|
||||||
|
String warningMessage = "Your offer (" + trade.getOffer().getShortId() + ") has been removed because there was a problem taking the trade.\n\nError message: " + ackMessage.getErrorMessage();
|
||||||
|
OpenOffer openOffer = HavenoUtils.openOfferManager.getOpenOffer(trade.getId()).orElse(null);
|
||||||
|
if (openOffer != null) {
|
||||||
|
HavenoUtils.openOfferManager.cancelOpenOffer(openOffer, null, null);
|
||||||
|
HavenoUtils.setTopError(warningMessage);
|
||||||
|
}
|
||||||
|
log.warn(warningMessage);
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isPaymentReceivedMessageAckedByEither() {
|
private boolean isPaymentReceivedMessageAckedByEither() {
|
||||||
if (trade.getBuyer().getPaymentReceivedMessageStateProperty().get() == MessageState.ACKNOWLEDGED) return true;
|
if (trade.getBuyer().getPaymentReceivedMessageStateProperty().get() == MessageState.ACKNOWLEDGED) return true;
|
||||||
if (trade.getArbitrator().getPaymentReceivedMessageStateProperty().get() == MessageState.ACKNOWLEDGED) return true;
|
if (trade.getArbitrator().getPaymentReceivedMessageStateProperty().get() == MessageState.ACKNOWLEDGED) return true;
|
||||||
|
@ -992,11 +1052,11 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||||
void handleTaskRunnerFault(NodeAddress ackReceiver, @Nullable TradeMessage message, String source, String errorMessage, String updatedMultisigHex) {
|
void handleTaskRunnerFault(NodeAddress ackReceiver, @Nullable TradeMessage message, String source, String errorMessage, String updatedMultisigHex) {
|
||||||
log.error("Task runner failed with error {}. Triggered from {}. Monerod={}" , errorMessage, source, trade.getXmrWalletService().getXmrConnectionService().getConnection());
|
log.error("Task runner failed with error {}. Triggered from {}. Monerod={}" , errorMessage, source, trade.getXmrWalletService().getXmrConnectionService().getConnection());
|
||||||
|
|
||||||
|
handleError(errorMessage);
|
||||||
|
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
sendAckMessage(ackReceiver, message, false, errorMessage, updatedMultisigHex);
|
sendAckMessage(ackReceiver, message, false, errorMessage, updatedMultisigHex);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleError(errorMessage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// these are not thread safe, so they must be used within a lock on the trade
|
// these are not thread safe, so they must be used within a lock on the trade
|
||||||
|
@ -1006,9 +1066,9 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||||
log.error(errorMessage);
|
log.error(errorMessage);
|
||||||
trade.setErrorMessage(errorMessage);
|
trade.setErrorMessage(errorMessage);
|
||||||
processModel.getTradeManager().requestPersistence();
|
processModel.getTradeManager().requestPersistence();
|
||||||
|
unlatchTrade();
|
||||||
if (errorMessageHandler != null) errorMessageHandler.handleErrorMessage(errorMessage);
|
if (errorMessageHandler != null) errorMessageHandler.handleErrorMessage(errorMessage);
|
||||||
errorMessageHandler = null;
|
errorMessageHandler = null;
|
||||||
unlatchTrade();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void latchTrade() {
|
protected void latchTrade() {
|
||||||
|
|
|
@ -0,0 +1,147 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Haveno.
|
||||||
|
*
|
||||||
|
* Haveno is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package haveno.core.trade.protocol.tasks;
|
||||||
|
|
||||||
|
import haveno.common.taskrunner.TaskRunner;
|
||||||
|
import haveno.core.offer.Offer;
|
||||||
|
import haveno.core.offer.OfferDirection;
|
||||||
|
import haveno.core.offer.OpenOffer;
|
||||||
|
import haveno.core.trade.HavenoUtils;
|
||||||
|
import haveno.core.trade.MakerTrade;
|
||||||
|
import haveno.core.trade.Trade;
|
||||||
|
import haveno.core.trade.protocol.TradeProtocol;
|
||||||
|
import haveno.core.xmr.model.XmrAddressEntry;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import monero.common.MoneroRpcConnection;
|
||||||
|
import monero.wallet.model.MoneroTxWallet;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class MakerRecreateReserveTx extends TradeTask {
|
||||||
|
|
||||||
|
public MakerRecreateReserveTx(TaskRunner taskHandler, Trade trade) {
|
||||||
|
super(taskHandler, trade);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void run() {
|
||||||
|
try {
|
||||||
|
runInterceptHook();
|
||||||
|
|
||||||
|
// maker trade expected
|
||||||
|
if (!(trade instanceof MakerTrade)) {
|
||||||
|
throw new RuntimeException("Expected maker trade but was " + trade.getClass().getSimpleName() + " " + trade.getShortId() + ". That should never happen.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// get open offer
|
||||||
|
OpenOffer openOffer = HavenoUtils.openOfferManager.getOpenOffer(trade.getOffer().getId()).orElse(null);
|
||||||
|
if (openOffer == null) throw new RuntimeException("Open offer not found for " + trade.getClass().getSimpleName() + " " + trade.getId());
|
||||||
|
Offer offer = openOffer.getOffer();
|
||||||
|
|
||||||
|
// reset reserve tx state
|
||||||
|
trade.getSelf().setReserveTxHex(null);
|
||||||
|
trade.getSelf().setReserveTxHash(null);
|
||||||
|
trade.getSelf().setReserveTxKey(null);
|
||||||
|
trade.getSelf().setReserveTxKeyImages(null);
|
||||||
|
|
||||||
|
// recreate reserve tx
|
||||||
|
log.warn("Maker is recreating reserve tx for tradeId={}", trade.getShortId());
|
||||||
|
MoneroTxWallet reserveTx = null;
|
||||||
|
synchronized (HavenoUtils.xmrWalletService.getWalletLock()) {
|
||||||
|
|
||||||
|
// check for timeout
|
||||||
|
if (isTimedOut()) throw new RuntimeException("Trade protocol has timed out while getting lock to create reserve tx, tradeId=" + trade.getShortId());
|
||||||
|
trade.startProtocolTimeout();
|
||||||
|
|
||||||
|
// thaw reserved key images
|
||||||
|
log.info("Thawing reserve tx key images for tradeId={}", trade.getShortId());
|
||||||
|
HavenoUtils.xmrWalletService.thawOutputs(openOffer.getOffer().getOfferPayload().getReserveTxKeyImages());
|
||||||
|
|
||||||
|
// check for timeout
|
||||||
|
if (isTimedOut()) throw new RuntimeException("Trade protocol has timed out while thawing key images, tradeId=" + trade.getShortId());
|
||||||
|
trade.startProtocolTimeout();
|
||||||
|
|
||||||
|
// collect relevant info
|
||||||
|
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 = model.getXmrWalletService().getAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).get().getAddressString();
|
||||||
|
XmrAddressEntry fundingEntry = model.getXmrWalletService().getAddressEntry(offer.getId(), XmrAddressEntry.Context.OFFER_FUNDING).orElse(null);
|
||||||
|
Integer preferredSubaddressIndex = fundingEntry == null ? null : fundingEntry.getSubaddressIndex();
|
||||||
|
|
||||||
|
// attempt re-creating reserve tx
|
||||||
|
try {
|
||||||
|
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
||||||
|
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
||||||
|
MoneroRpcConnection sourceConnection = trade.getXmrConnectionService().getConnection();
|
||||||
|
try {
|
||||||
|
reserveTx = model.getXmrWalletService().createReserveTx(penaltyFee, makerFee, sendAmount, securityDeposit, returnAddress, openOffer.isReserveExactAmount(), preferredSubaddressIndex);
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
log.warn("Illegal state creating reserve tx, tradeId={}, error={}", trade.getShortId(), i + 1, e.getMessage());
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Error creating reserve tx, tradeId={}, attempt={}/{}, error={}", trade.getShortId(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage());
|
||||||
|
trade.getXmrWalletService().handleWalletError(e, sourceConnection);
|
||||||
|
if (isTimedOut()) throw new RuntimeException("Trade protocol has timed out while creating reserve tx, tradeId=" + trade.getShortId());
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
|
||||||
|
// reset state
|
||||||
|
if (reserveTx != null) model.getXmrWalletService().thawOutputs(HavenoUtils.getInputKeyImages(reserveTx));
|
||||||
|
model.getXmrWalletService().freezeOutputs(offer.getOfferPayload().getReserveTxKeyImages());
|
||||||
|
trade.getSelf().setReserveTxKeyImages(null);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset protocol timeout
|
||||||
|
trade.startProtocolTimeout();
|
||||||
|
|
||||||
|
// update state
|
||||||
|
trade.getSelf().setReserveTxHash(reserveTx.getHash());
|
||||||
|
trade.getSelf().setReserveTxHex(reserveTx.getFullHex());
|
||||||
|
trade.getSelf().setReserveTxKey(reserveTx.getKey());
|
||||||
|
trade.getSelf().setReserveTxKeyImages(HavenoUtils.getInputKeyImages(reserveTx));
|
||||||
|
trade.getXmrWalletService().freezeOutputs(HavenoUtils.getInputKeyImages(reserveTx));
|
||||||
|
}
|
||||||
|
|
||||||
|
// save process state
|
||||||
|
processModel.setReserveTx(reserveTx); // TODO: remove this? how is it used?
|
||||||
|
processModel.getTradeManager().requestPersistence();
|
||||||
|
complete();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
trade.setErrorMessage("An error occurred.\n" +
|
||||||
|
"Error message:\n"
|
||||||
|
+ t.getMessage());
|
||||||
|
failed(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isTimedOut() {
|
||||||
|
return !processModel.getTradeManager().hasOpenTrade(trade);
|
||||||
|
}
|
||||||
|
}
|
|
@ -88,7 +88,7 @@ public class TakerReserveTradeFunds extends TradeTask {
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
||||||
// reset state with wallet lock
|
// reset state
|
||||||
model.getXmrWalletService().swapPayoutAddressEntryToAvailable(trade.getId());
|
model.getXmrWalletService().swapPayoutAddressEntryToAvailable(trade.getId());
|
||||||
if (reserveTx != null) {
|
if (reserveTx != null) {
|
||||||
model.getXmrWalletService().thawOutputs(HavenoUtils.getInputKeyImages(reserveTx));
|
model.getXmrWalletService().thawOutputs(HavenoUtils.getInputKeyImages(reserveTx));
|
||||||
|
@ -101,11 +101,12 @@ public class TakerReserveTradeFunds extends TradeTask {
|
||||||
// reset protocol timeout
|
// reset protocol timeout
|
||||||
trade.startProtocolTimeout();
|
trade.startProtocolTimeout();
|
||||||
|
|
||||||
// update trade state
|
// update state
|
||||||
trade.getTaker().setReserveTxHash(reserveTx.getHash());
|
trade.getSelf().setReserveTxHash(reserveTx.getHash());
|
||||||
trade.getTaker().setReserveTxHex(reserveTx.getFullHex());
|
trade.getSelf().setReserveTxHex(reserveTx.getFullHex());
|
||||||
trade.getTaker().setReserveTxKey(reserveTx.getKey());
|
trade.getSelf().setReserveTxKey(reserveTx.getKey());
|
||||||
trade.getTaker().setReserveTxKeyImages(HavenoUtils.getInputKeyImages(reserveTx));
|
trade.getSelf().setReserveTxKeyImages(HavenoUtils.getInputKeyImages(reserveTx));
|
||||||
|
trade.getXmrWalletService().freezeOutputs(HavenoUtils.getInputKeyImages(reserveTx));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue