mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-10-12 10:30:46 -04:00
skip reverting deposit or payout tx state until next confirmation
This commit is contained in:
parent
457518e3a9
commit
f8af703eac
1 changed files with 80 additions and 40 deletions
|
@ -170,6 +170,8 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||||
public BooleanProperty wasWalletPolledProperty = new SimpleBooleanProperty(false);
|
public BooleanProperty wasWalletPolledProperty = new SimpleBooleanProperty(false);
|
||||||
public BooleanProperty wasWalletSyncedAndPolledProperty = new SimpleBooleanProperty(false);
|
public BooleanProperty wasWalletSyncedAndPolledProperty = new SimpleBooleanProperty(false);
|
||||||
private static final long MISSING_TXS_DELAY_MS = Config.baseCurrencyNetwork().isTestnet() ? 5000 : 30000;
|
private static final long MISSING_TXS_DELAY_MS = Config.baseCurrencyNetwork().isTestnet() ? 5000 : 30000;
|
||||||
|
private Long lastDepositTxMissingHeight; // height when we last saw missing deposit txs (to wait for a confirmation before reverting state)
|
||||||
|
private Long lastPayoutTxMissingHeight; // height when we last saw missing payout tx (to wait for a confirmation before reverting state)
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Enums
|
// Enums
|
||||||
|
@ -3041,13 +3043,19 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||||
|
|
||||||
private void setDepositTxs(List<MoneroTxWallet> txs, boolean poolChecked) {
|
private void setDepositTxs(List<MoneroTxWallet> txs, boolean poolChecked) {
|
||||||
|
|
||||||
// set deposit txs
|
// get deposit txs
|
||||||
getMaker().setDepositTx(getMakerDepositTx(txs));
|
MoneroTxWallet makerDepositTx = getMakerDepositTx(txs);
|
||||||
getTaker().setDepositTx(getTakerDepositTx(txs));
|
MoneroTxWallet takerDepositTx = getTakerDepositTx(txs);
|
||||||
|
MoneroTxWallet buyerDepositTx = getBuyerDepositTx(txs);
|
||||||
|
MoneroTxWallet sellerDepositTx = getSellerDepositTx(txs);
|
||||||
|
|
||||||
|
// set txs if known
|
||||||
|
if (makerDepositTx != null) getMaker().setDepositTx(makerDepositTx);
|
||||||
|
if (takerDepositTx != null) getTaker().setDepositTx(takerDepositTx);
|
||||||
|
|
||||||
// set actual buyer security deposit
|
// set actual buyer security deposit
|
||||||
if (isSeen(getBuyer().getDepositTx())) {
|
if (isSeen(buyerDepositTx)) {
|
||||||
BigInteger buyerSecurityDeposit = ((MoneroTxWallet) getBuyer().getDepositTx()).getIncomingAmount();
|
BigInteger buyerSecurityDeposit = buyerDepositTx.getIncomingAmount();
|
||||||
if (!getBuyer().getSecurityDeposit().equals(BigInteger.ZERO) && !buyerSecurityDeposit.equals(getBuyer().getSecurityDeposit())) {
|
if (!getBuyer().getSecurityDeposit().equals(BigInteger.ZERO) && !buyerSecurityDeposit.equals(getBuyer().getSecurityDeposit())) {
|
||||||
log.warn("Overwriting buyer security deposit for {} {}, old={}, new={}", getClass().getSimpleName(), getShortId(), getBuyer().getSecurityDeposit(), buyerSecurityDeposit);
|
log.warn("Overwriting buyer security deposit for {} {}, old={}, new={}", getClass().getSimpleName(), getShortId(), getBuyer().getSecurityDeposit(), buyerSecurityDeposit);
|
||||||
}
|
}
|
||||||
|
@ -3055,8 +3063,8 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||||
}
|
}
|
||||||
|
|
||||||
// set actual seller security deposit
|
// set actual seller security deposit
|
||||||
if (isSeen(getSeller().getDepositTx())) {
|
if (isSeen(sellerDepositTx)) {
|
||||||
BigInteger sellerSecurityDeposit = ((MoneroTxWallet) getSeller().getDepositTx()).getIncomingAmount().subtract(getAmount());
|
BigInteger sellerSecurityDeposit = sellerDepositTx.getIncomingAmount().subtract(getAmount());
|
||||||
if (!getSeller().getSecurityDeposit().equals(BigInteger.ZERO) && !sellerSecurityDeposit.equals(getSeller().getSecurityDeposit())) {
|
if (!getSeller().getSecurityDeposit().equals(BigInteger.ZERO) && !sellerSecurityDeposit.equals(getSeller().getSecurityDeposit())) {
|
||||||
log.warn("Overwriting seller security deposit for {} {}, old={}, new={}", getClass().getSimpleName(), getShortId(), getSeller().getSecurityDeposit(), sellerSecurityDeposit);
|
log.warn("Overwriting seller security deposit for {} {}, old={}, new={}", getClass().getSimpleName(), getShortId(), getSeller().getSecurityDeposit(), sellerSecurityDeposit);
|
||||||
}
|
}
|
||||||
|
@ -3064,33 +3072,44 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||||
}
|
}
|
||||||
|
|
||||||
// advance deposit state
|
// advance deposit state
|
||||||
if (isSeen(getMaker().getDepositTx()) && (hasBuyerAsTakerWithoutDeposit() || isSeen(getTaker().getDepositTx()))) {
|
if (isSeen(makerDepositTx) && (hasBuyerAsTakerWithoutDeposit() || isSeen(takerDepositTx))) {
|
||||||
setStateDepositsSeen();
|
setStateDepositsSeen();
|
||||||
|
|
||||||
// check for deposit txs confirmed
|
// check for deposit txs confirmed
|
||||||
if (getMaker().getDepositTx().isConfirmed() && (hasBuyerAsTakerWithoutDeposit() || getTaker().getDepositTx().isConfirmed())) {
|
if (makerDepositTx.isConfirmed() && (hasBuyerAsTakerWithoutDeposit() || takerDepositTx.isConfirmed())) {
|
||||||
setStateDepositsConfirmed();
|
setStateDepositsConfirmed();
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for deposit txs unlocked
|
// check for deposit txs unlocked
|
||||||
if (getMaker().getDepositTx().getNumConfirmations() >= XmrWalletService.NUM_BLOCKS_UNLOCK && (hasBuyerAsTakerWithoutDeposit() || getTaker().getDepositTx().getNumConfirmations() >= XmrWalletService.NUM_BLOCKS_UNLOCK)) {
|
if (makerDepositTx.getNumConfirmations() >= XmrWalletService.NUM_BLOCKS_UNLOCK && (hasBuyerAsTakerWithoutDeposit() || takerDepositTx.getNumConfirmations() >= XmrWalletService.NUM_BLOCKS_UNLOCK)) {
|
||||||
setStateDepositsUnlocked();
|
setStateDepositsUnlocked();
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for deposit txs finalized
|
// check for deposit txs finalized
|
||||||
if (getMaker().getDepositTx().getNumConfirmations() >= NUM_BLOCKS_DEPOSITS_FINALIZED && (hasBuyerAsTakerWithoutDeposit() || getTaker().getDepositTx().getNumConfirmations() >= NUM_BLOCKS_DEPOSITS_FINALIZED)) {
|
if (makerDepositTx.getNumConfirmations() >= NUM_BLOCKS_DEPOSITS_FINALIZED && (hasBuyerAsTakerWithoutDeposit() || takerDepositTx.getNumConfirmations() >= NUM_BLOCKS_DEPOSITS_FINALIZED)) {
|
||||||
setStateDepositsFinalized();
|
setStateDepositsFinalized();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// revert deposit state if necessary
|
// revert deposit state if necessary
|
||||||
State depositsState = getDepositsState();
|
State depositsState = getDepositsState(makerDepositTx, takerDepositTx);
|
||||||
State minDepositsState = isPaymentSent() ? State.DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN : getState();
|
State minDepositsState = isPaymentSent() ? State.DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN : getState();
|
||||||
if (poolChecked && depositsState.ordinal() < minDepositsState.ordinal()) {
|
if (poolChecked && depositsState.ordinal() < minDepositsState.ordinal()) {
|
||||||
log.warn("Deposits state has reverted from {} to {} for {} {}. Possible reorg?", minDepositsState, depositsState, getClass().getSimpleName(), getShortId());
|
|
||||||
|
// skip reverting state until next confirmation // TODO: sometimes txs are missing from the wallet and reappear without reorg
|
||||||
|
if (lastDepositTxMissingHeight == null || lastDepositTxMissingHeight <= walletHeight.get()) {
|
||||||
|
if (lastDepositTxMissingHeight == null) log.warn("Missing deposit txs for {} {} at height {}, waiting for a block before reverting state", getClass().getSimpleName(), getShortId(), lastDepositTxMissingHeight);
|
||||||
|
lastDepositTxMissingHeight = wallet.getHeight();
|
||||||
|
} else {
|
||||||
|
log.warn("Reverting deposits state from {} to {} for {} {}. Possible reorg?", minDepositsState, depositsState, getClass().getSimpleName(), getShortId());
|
||||||
|
getMaker().setDepositTx(makerDepositTx);
|
||||||
|
getTaker().setDepositTx(takerDepositTx);
|
||||||
if (depositsState == State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS) setErrorMessage("Deposit transactions are missing for trade " + getShortId() + ". This can happen after a blockchain reorganization.\n\nIf the issue continues, you can contact support or mark the trade as failed.");
|
if (depositsState == State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS) setErrorMessage("Deposit transactions are missing for trade " + getShortId() + ". This can happen after a blockchain reorganization.\n\nIf the issue continues, you can contact support or mark the trade as failed.");
|
||||||
if (!isPaymentSent()) setState(depositsState); // only revert state if payment not sent
|
if (!isPaymentSent()) setState(depositsState); // only revert state if payment not sent
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
lastDepositTxMissingHeight = null;
|
||||||
|
}
|
||||||
|
|
||||||
// announce deposits update
|
// announce deposits update
|
||||||
depositTxsUpdateCounter.set(depositTxsUpdateCounter.get() + 1);
|
depositTxsUpdateCounter.set(depositTxsUpdateCounter.get() + 1);
|
||||||
|
@ -3117,13 +3136,22 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||||
if (payoutTx != null) setPayoutTx(payoutTx);
|
if (payoutTx != null) setPayoutTx(payoutTx);
|
||||||
else if (hasPayoutTx) setPayoutState(PayoutState.PAYOUT_PUBLISHED);
|
else if (hasPayoutTx) setPayoutState(PayoutState.PAYOUT_PUBLISHED);
|
||||||
else if (poolChecked && isPayoutPublished()) { // payout tx seen then lost (e.g. reorg)
|
else if (poolChecked && isPayoutPublished()) { // payout tx seen then lost (e.g. reorg)
|
||||||
|
|
||||||
|
// skip reverting state until next confirmation
|
||||||
|
if (lastPayoutTxMissingHeight == null || lastPayoutTxMissingHeight <= walletHeight.get()) {
|
||||||
|
if (lastPayoutTxMissingHeight == null) log.warn("Missing payout tx for {} {} at height {}, waiting for a block before reverting state", getClass().getSimpleName(), getShortId(), lastPayoutTxMissingHeight);
|
||||||
|
lastPayoutTxMissingHeight = wallet.getHeight();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// reset payment received and dispute closed messages
|
||||||
for (TradePeer peer : getAllPeers()) {
|
for (TradePeer peer : getAllPeers()) {
|
||||||
peer.setPaymentReceivedMessage(null);
|
peer.setPaymentReceivedMessage(null);
|
||||||
peer.setPaymentReceivedMessageState(MessageState.UNDEFINED);
|
peer.setPaymentReceivedMessageState(MessageState.UNDEFINED);
|
||||||
peer.setDisputeClosedMessage(null);
|
peer.setDisputeClosedMessage(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// revert state
|
||||||
setPayoutState(PayoutState.PAYOUT_UNPUBLISHED);
|
setPayoutState(PayoutState.PAYOUT_UNPUBLISHED);
|
||||||
if (isCompleted()) processModel.getTradeManager().onMoveClosedTradeToPendingTrades(this);
|
|
||||||
String errorMsg = "The payout transaction is not seen for trade " + getShortId() + ". This can happen after a blockchain reorganization..\n\nIf the payout does not confirm automatically, you can contact support or mark the trade as failed.";
|
String errorMsg = "The payout transaction is not seen for trade " + getShortId() + ". This can happen after a blockchain reorganization..\n\nIf the payout does not confirm automatically, you can contact support or mark the trade as failed.";
|
||||||
if (isSeller() && getState().ordinal() >= State.BUYER_RECEIVED_PAYMENT_RECEIVED_MSG.ordinal()) {
|
if (isSeller() && getState().ordinal() >= State.BUYER_RECEIVED_PAYMENT_RECEIVED_MSG.ordinal()) {
|
||||||
log.warn("Reverting state of {} {} from {} to {} because payout is unseen. Possible reorg?", getClass().getSimpleName(), getId(), getState(), Trade.State.BUYER_SENT_PAYMENT_SENT_MSG);
|
log.warn("Reverting state of {} {} from {} to {} because payout is unseen. Possible reorg?", getClass().getSimpleName(), getId(), getState(), Trade.State.BUYER_SENT_PAYMENT_SENT_MSG);
|
||||||
|
@ -3135,6 +3163,10 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||||
setState(State.SELLER_CONFIRMED_PAYMENT_RECEIPT);
|
setState(State.SELLER_CONFIRMED_PAYMENT_RECEIPT);
|
||||||
setErrorMessage(errorMsg);
|
setErrorMessage(errorMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// move trade back to pending if marked completed
|
||||||
|
if (isCompleted()) processModel.getTradeManager().onMoveClosedTradeToPendingTrades(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3257,6 +3289,14 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||||
return getValidDepositTx(txs, getTaker());
|
return getValidDepositTx(txs, getTaker());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MoneroTxWallet getBuyerDepositTx(List<MoneroTxWallet> txs) {
|
||||||
|
return getValidDepositTx(txs, getBuyer());
|
||||||
|
}
|
||||||
|
|
||||||
|
private MoneroTxWallet getSellerDepositTx(List<MoneroTxWallet> txs) {
|
||||||
|
return getValidDepositTx(txs, getSeller());
|
||||||
|
}
|
||||||
|
|
||||||
private MoneroTxWallet getValidDepositTx(List<MoneroTxWallet> txs, TradePeer peer) {
|
private MoneroTxWallet getValidDepositTx(List<MoneroTxWallet> txs, TradePeer peer) {
|
||||||
for (MoneroTxWallet tx : txs) {
|
for (MoneroTxWallet tx : txs) {
|
||||||
if (tx.getHash().equals(peer.getDepositTxHash()) && !Boolean.TRUE.equals(tx.isFailed())) {
|
if (tx.getHash().equals(peer.getDepositTxHash()) && !Boolean.TRUE.equals(tx.isFailed())) {
|
||||||
|
@ -3273,14 +3313,14 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private State getDepositsState() {
|
private State getDepositsState(MoneroTxWallet makerDepositTx, MoneroTxWallet takerDepositTx) {
|
||||||
if (getMaker().getDepositTx() == null || (!hasBuyerAsTakerWithoutDeposit() && getTaker().getDepositTx() == null)) return State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS;
|
if (makerDepositTx == null || (!hasBuyerAsTakerWithoutDeposit() && takerDepositTx == null)) return State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS;
|
||||||
if (getMaker().getDepositTx().isFailed() || (!hasBuyerAsTakerWithoutDeposit() && getTaker().getDepositTx().isFailed())) return State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS;
|
if (makerDepositTx.isFailed() || (!hasBuyerAsTakerWithoutDeposit() && takerDepositTx.isFailed())) return State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS;
|
||||||
if (getMaker().getDepositTx().getNumConfirmations() == null || (!hasBuyerAsTakerWithoutDeposit() && getTaker().getDepositTx().getNumConfirmations() == null)) return State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS;
|
if (makerDepositTx.getNumConfirmations() == null || (!hasBuyerAsTakerWithoutDeposit() && takerDepositTx.getNumConfirmations() == null)) return State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS;
|
||||||
if (getMaker().getDepositTx().getNumConfirmations() >= NUM_BLOCKS_DEPOSITS_FINALIZED && (hasBuyerAsTakerWithoutDeposit() || getTaker().getDepositTx().getNumConfirmations() >= NUM_BLOCKS_DEPOSITS_FINALIZED)) return State.DEPOSIT_TXS_FINALIZED_IN_BLOCKCHAIN;
|
if (makerDepositTx.getNumConfirmations() >= NUM_BLOCKS_DEPOSITS_FINALIZED && (hasBuyerAsTakerWithoutDeposit() || takerDepositTx.getNumConfirmations() >= NUM_BLOCKS_DEPOSITS_FINALIZED)) return State.DEPOSIT_TXS_FINALIZED_IN_BLOCKCHAIN;
|
||||||
if (getMaker().getDepositTx().getNumConfirmations() >= XmrWalletService.NUM_BLOCKS_UNLOCK && (hasBuyerAsTakerWithoutDeposit() || getTaker().getDepositTx().getNumConfirmations() >= XmrWalletService.NUM_BLOCKS_UNLOCK)) return State.DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN;
|
if (makerDepositTx.getNumConfirmations() >= XmrWalletService.NUM_BLOCKS_UNLOCK && (hasBuyerAsTakerWithoutDeposit() || takerDepositTx.getNumConfirmations() >= XmrWalletService.NUM_BLOCKS_UNLOCK)) return State.DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN;
|
||||||
if (getMaker().getDepositTx().isConfirmed() && (hasBuyerAsTakerWithoutDeposit() || getTaker().getDepositTx().isConfirmed())) return State.DEPOSIT_TXS_CONFIRMED_IN_BLOCKCHAIN;
|
if (makerDepositTx.isConfirmed() && (hasBuyerAsTakerWithoutDeposit() || takerDepositTx.isConfirmed())) return State.DEPOSIT_TXS_CONFIRMED_IN_BLOCKCHAIN;
|
||||||
if (isSeen(getMaker().getDepositTx()) && (hasBuyerAsTakerWithoutDeposit() || isSeen(getTaker().getDepositTx()))) return State.DEPOSIT_TXS_SEEN_IN_NETWORK;
|
if (isSeen(makerDepositTx) && (hasBuyerAsTakerWithoutDeposit() || isSeen(takerDepositTx))) return State.DEPOSIT_TXS_SEEN_IN_NETWORK;
|
||||||
return State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS;
|
return State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue