mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-10-16 04:20:42 -04:00
use nack flow if cannot create payout tx and stop repeat sending
This commit is contained in:
parent
0bf6052b7c
commit
4989eab498
7 changed files with 44 additions and 21 deletions
|
@ -542,7 +542,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||||
break;
|
break;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (trade.isPayoutPublished()) return null;
|
if (trade.isPayoutPublished()) return null;
|
||||||
if (HavenoUtils.isTransactionRejected(e) || HavenoUtils.isNotEnoughSigners(e) || HavenoUtils.isFailedToParse(e)) throw new IllegalArgumentException(e);
|
if (HavenoUtils.isMultisigError(e)) throw new IllegalArgumentException(e);
|
||||||
log.warn("Failed to submit dispute payout tx, tradeId={}, attempt={}/{}, error={}", trade.getShortId(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage());
|
log.warn("Failed to submit dispute payout tx, tradeId={}, attempt={}/{}, error={}", trade.getShortId(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage());
|
||||||
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||||
if (trade.getXmrConnectionService().isConnected()) trade.requestSwitchToNextBestConnection(sourceConnection);
|
if (trade.getXmrConnectionService().isConnected()) trade.requestSwitchToNextBestConnection(sourceConnection);
|
||||||
|
|
|
@ -630,22 +630,35 @@ public class HavenoUtils {
|
||||||
return isConnectionRefused(e) || isReadTimeout(e) || XmrWalletBase.isSyncWithProgressTimeout(e);
|
return isConnectionRefused(e) || isReadTimeout(e) || XmrWalletBase.isSyncWithProgressTimeout(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isNotEnoughSigners(Throwable e) {
|
private static boolean isNotEnoughSigners(Throwable e) {
|
||||||
return e != null && e.getMessage().contains("Not enough signers");
|
return e != null && e.getMessage().contains("Not enough signers");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isFailedToParse(Throwable e) {
|
private static boolean isFailedToParse(Throwable e) {
|
||||||
return e != null && e.getMessage().contains("Failed to parse");
|
return e != null && e.getMessage().contains("Failed to parse");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isStaleData(Throwable e) {
|
||||||
|
return e != null && e.getMessage().contains("stale data");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isNoTransactionCreated(Throwable e) {
|
||||||
|
return e != null && e.getMessage().contains("No transaction created");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isLRNotFound(Throwable e) {
|
||||||
|
return e != null && e.getMessage().contains("LR not found for enough participants");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: handling specific error messages is brittle, inverse so all errors are illegal except known local issues?
|
||||||
|
public static boolean isMultisigError(Throwable e) {
|
||||||
|
return isLRNotFound(e) || isNotEnoughSigners(e) || isNoTransactionCreated(e) || isFailedToParse(e) || isStaleData(e);
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isTransactionRejected(Throwable e) {
|
public static boolean isTransactionRejected(Throwable e) {
|
||||||
return e != null && e.getMessage().contains("was rejected");
|
return e != null && e.getMessage().contains("was rejected");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isLRNotFound(Throwable e) {
|
|
||||||
return e != null && e.getMessage().contains("LR not found for enough participants");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isIllegal(Throwable e) {
|
public static boolean isIllegal(Throwable e) {
|
||||||
return e instanceof IllegalArgumentException || e instanceof IllegalStateException;
|
return e instanceof IllegalArgumentException || e instanceof IllegalStateException;
|
||||||
}
|
}
|
||||||
|
|
|
@ -828,7 +828,6 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||||
setState(Trade.State.BUYER_SENT_PAYMENT_SENT_MSG);
|
setState(Trade.State.BUYER_SENT_PAYMENT_SENT_MSG);
|
||||||
for (TradePeer peer : getAllPeers()) {
|
for (TradePeer peer : getAllPeers()) {
|
||||||
peer.setPaymentReceivedMessage(null);
|
peer.setPaymentReceivedMessage(null);
|
||||||
peer.setPaymentReceivedMessageState(MessageState.UNDEFINED);
|
|
||||||
}
|
}
|
||||||
setPayoutTxHex(null);
|
setPayoutTxHex(null);
|
||||||
}
|
}
|
||||||
|
@ -1385,7 +1384,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||||
.setRelay(false)
|
.setRelay(false)
|
||||||
.setPriority(XmrWalletService.PROTOCOL_FEE_PRIORITY));
|
.setPriority(XmrWalletService.PROTOCOL_FEE_PRIORITY));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (HavenoUtils.isLRNotFound(e)) throw new IllegalStateException(e);
|
if (HavenoUtils.isMultisigError(e)) throw new IllegalStateException(e);
|
||||||
else throw e;
|
else throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1409,6 +1408,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||||
} catch (IllegalArgumentException | IllegalStateException e) {
|
} catch (IllegalArgumentException | IllegalStateException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
if (HavenoUtils.isMultisigError(e)) throw new IllegalStateException(e);
|
||||||
if (e.getMessage().contains("not possible")) throw new IllegalArgumentException("Loser payout is too small to cover the mining fee");
|
if (e.getMessage().contains("not possible")) throw new IllegalArgumentException("Loser payout is too small to cover the mining fee");
|
||||||
handleWalletError(e, sourceConnection, i + 1);
|
handleWalletError(e, sourceConnection, i + 1);
|
||||||
doPollWallet();
|
doPollWallet();
|
||||||
|
@ -1557,7 +1557,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||||
setPayoutStatePublished();
|
setPayoutStatePublished();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (!isPayoutPublished()) {
|
if (!isPayoutPublished()) {
|
||||||
if (HavenoUtils.isTransactionRejected(e) || HavenoUtils.isNotEnoughSigners(e) || HavenoUtils.isFailedToParse(e)) throw new IllegalArgumentException(e);
|
if (HavenoUtils.isTransactionRejected(e) || HavenoUtils.isMultisigError(e)) throw new IllegalArgumentException(e);
|
||||||
throw new RuntimeException("Failed to submit payout tx for " + getClass().getSimpleName() + " " + getId() + ", error=" + e.getMessage(), e);
|
throw new RuntimeException("Failed to submit payout tx for " + getClass().getSimpleName() + " " + getId() + ", error=" + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2191,13 +2191,13 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||||
throw new RuntimeException("Trade is not maker, taker, or arbitrator");
|
throw new RuntimeException("Trade is not maker, taker, or arbitrator");
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<TradePeer> getOtherPeers() {
|
public List<TradePeer> getOtherPeers() {
|
||||||
List<TradePeer> peers = getAllPeers();
|
List<TradePeer> peers = getAllPeers();
|
||||||
if (!peers.remove(getSelf())) throw new IllegalStateException("Failed to remove self from list of peers");
|
if (!peers.remove(getSelf())) throw new IllegalStateException("Failed to remove self from list of peers");
|
||||||
return peers;
|
return peers;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<TradePeer> getAllPeers() {
|
public List<TradePeer> getAllPeers() {
|
||||||
List<TradePeer> peers = new ArrayList<TradePeer>();
|
List<TradePeer> peers = new ArrayList<TradePeer>();
|
||||||
peers.add(getMaker());
|
peers.add(getMaker());
|
||||||
peers.add(getTaker());
|
peers.add(getTaker());
|
||||||
|
@ -3194,7 +3194,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||||
processModel.setPaymentSentPayoutTxStale(true);
|
processModel.setPaymentSentPayoutTxStale(true);
|
||||||
if (paymentReceivedNackSender != null) {
|
if (paymentReceivedNackSender != null) {
|
||||||
paymentReceivedNackSender.setPaymentReceivedMessage(null);
|
paymentReceivedNackSender.setPaymentReceivedMessage(null);
|
||||||
paymentReceivedNackSender.setPaymentReceivedMessageState(MessageState.UNDEFINED);
|
paymentReceivedNackSender.setPaymentReceivedMessageState(MessageState.NACKED);
|
||||||
}
|
}
|
||||||
if (!isPayoutPublished()) {
|
if (!isPayoutPublished()) {
|
||||||
getSelf().setUnsignedPayoutTxHex(null);
|
getSelf().setUnsignedPayoutTxHex(null);
|
||||||
|
@ -3302,8 +3302,8 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||||
// rescan blockchain
|
// rescan blockchain
|
||||||
rescanBlockchain();
|
rescanBlockchain();
|
||||||
|
|
||||||
// import multisig hex
|
// must import multisig hex after rescan
|
||||||
log.warn("Importing multisig hex to recover wallet data for {} {}", getClass().getSimpleName(), getShortId());
|
log.warn("Importing multisig hex after rescanning blockchain for {} {}", getClass().getSimpleName(), getShortId());
|
||||||
importMultisigHex();
|
importMultisigHex();
|
||||||
|
|
||||||
// poll wallet
|
// poll wallet
|
||||||
|
|
|
@ -120,7 +120,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||||
private boolean makerInitTradeRequestHasBeenNacked = false;
|
private boolean makerInitTradeRequestHasBeenNacked = false;
|
||||||
private PaymentReceivedMessage lastAckedPaymentReceivedMessage = null;
|
private PaymentReceivedMessage lastAckedPaymentReceivedMessage = null;
|
||||||
|
|
||||||
private static int MAX_PAYMENT_RECEIVED_NACKS = 5;
|
private static int MAX_PAYMENT_RECEIVED_NACKS = 6;
|
||||||
private int numPaymentReceivedNacks = 0;
|
private int numPaymentReceivedNacks = 0;
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -47,7 +47,6 @@ import haveno.core.trade.messages.PaymentSentMessage;
|
||||||
import haveno.core.util.Validator;
|
import haveno.core.util.Validator;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@ -66,7 +65,6 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
|
||||||
PaymentReceivedMessage message = (PaymentReceivedMessage) processModel.getTradeMessage();
|
PaymentReceivedMessage message = (PaymentReceivedMessage) processModel.getTradeMessage();
|
||||||
checkNotNull(message);
|
checkNotNull(message);
|
||||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||||
checkArgument(message.getUnsignedPayoutTxHex() != null || message.getSignedPayoutTxHex() != null, "No payout tx hex provided");
|
|
||||||
|
|
||||||
// verify signature of payment received message
|
// verify signature of payment received message
|
||||||
HavenoUtils.verifyPaymentReceivedMessage(trade, message);
|
HavenoUtils.verifyPaymentReceivedMessage(trade, message);
|
||||||
|
@ -146,6 +144,11 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
|
||||||
// handle if payout tx not published
|
// handle if payout tx not published
|
||||||
if (!trade.isPayoutPublished()) {
|
if (!trade.isPayoutPublished()) {
|
||||||
|
|
||||||
|
// nack with updated multisig info if no payout tx provided
|
||||||
|
if (message.getUnsignedPayoutTxHex() == null && message.getSignedPayoutTxHex() == null && message.getPayoutTxId() == null) {
|
||||||
|
throw new IllegalStateException("No payout tx provided in PaymentReceivedMessage for " + trade.getClass().getSimpleName() + " " + trade.getId());
|
||||||
|
}
|
||||||
|
|
||||||
// wait to publish payout tx if defer flag set from seller (payout is expected)
|
// wait to publish payout tx if defer flag set from seller (payout is expected)
|
||||||
if (message.isDeferPublishPayout()) {
|
if (message.isDeferPublishPayout()) {
|
||||||
log.info("Deferring publishing payout tx for {} {}", trade.getClass().getSimpleName(), trade.getId());
|
log.info("Deferring publishing payout tx for {} {}", trade.getClass().getSimpleName(), trade.getId());
|
||||||
|
|
|
@ -107,7 +107,13 @@ public class SellerPreparePaymentReceivedMessage extends TradeTask {
|
||||||
trade.requestPersistence();
|
trade.requestPersistence();
|
||||||
complete();
|
complete();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
failed(t);
|
if (HavenoUtils.isIllegal(t)) {
|
||||||
|
log.error("Illegal exception preparing payment received message in {} {}: {}", trade.getClass().getSimpleName(), trade.getId(), t.getMessage(), t);
|
||||||
|
trade.exportMultisigHex();
|
||||||
|
complete(); // proceed to send the message to perform nack flow with updated multsig state
|
||||||
|
} else {
|
||||||
|
failed(t);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -96,6 +96,9 @@ public abstract class SellerSendPaymentReceivedMessage extends SendMailboxMessag
|
||||||
try {
|
try {
|
||||||
runInterceptHook();
|
runInterceptHook();
|
||||||
|
|
||||||
|
// reset ack state
|
||||||
|
getReceiver().setPaymentReceivedMessageState(MessageState.UNDEFINED);
|
||||||
|
|
||||||
// skip if stopped
|
// skip if stopped
|
||||||
if (stopSending()) {
|
if (stopSending()) {
|
||||||
if (!isCompleted()) complete();
|
if (!isCompleted()) complete();
|
||||||
|
@ -149,8 +152,6 @@ public abstract class SellerSendPaymentReceivedMessage extends SendMailboxMessag
|
||||||
// verify message
|
// verify message
|
||||||
if (trade.isPayoutPublished()) {
|
if (trade.isPayoutPublished()) {
|
||||||
checkArgument(message.getUpdatedMultisigHex() != null || message.getPayoutTxId() != null, "PaymentReceivedMessage does not include updated multisig hex or payout tx id after payout published");
|
checkArgument(message.getUpdatedMultisigHex() != null || message.getPayoutTxId() != null, "PaymentReceivedMessage does not include updated multisig hex or payout tx id after payout published");
|
||||||
} else {
|
|
||||||
checkArgument(message.getUnsignedPayoutTxHex() != null || message.getSignedPayoutTxHex() != null, "PaymentReceivedMessage does not include payout tx hex");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// sign message
|
// sign message
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue