Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Only move to failed trades if the reject msg is critical #3830

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 69 additions & 39 deletions core/src/main/java/bisq/core/app/BisqSetup.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
import bisq.common.util.Utilities;

import org.bitcoinj.core.Coin;
import org.bitcoinj.core.RejectMessage;

import javax.inject.Inject;
import javax.inject.Named;
Expand Down Expand Up @@ -694,50 +695,79 @@ private void initDomainServices() {
balances.onAllServicesInitialized();

walletAppSetup.getRejectedTxException().addListener((observable, oldValue, newValue) -> {
// We delay as we might get the rejected tx error before we have completed the create offer protocol
UserThread.runAfter(() -> {
if (rejectedTxErrorMessageHandler != null && newValue != null && newValue.getTxId() != null) {
String txId = newValue.getTxId();
openOfferManager.getObservableList().stream()
.filter(openOffer -> txId.equals(openOffer.getOffer().getOfferFeePaymentTxId()))
.forEach(openOffer -> {
// We delay to avoid concurrent modification exceptions
UserThread.runAfter(() -> {
openOffer.getOffer().setErrorMessage(newValue.getMessage());
rejectedTxErrorMessageHandler.accept(Res.get("popup.warning.openOffer.makerFeeTxRejected", openOffer.getId(), txId));
openOfferManager.removeOpenOffer(openOffer, () -> {
log.warn("We removed an open offer because the maker fee was rejected by the Bitcoin " +
"network. OfferId={}, txId={}", openOffer.getShortId(), txId);
}, log::warn);
}, 1);
});

tradeManager.getTradableList().stream()
.filter(trade -> trade.getOffer() != null)
.forEach(trade -> {
String details = null;
if (txId.equals(trade.getDepositTxId())) {
details = Res.get("popup.warning.trade.txRejected.deposit");
}
if (txId.equals(trade.getOffer().getOfferFeePaymentTxId()) || txId.equals(trade.getTakerFeeTxId())) {
details = Res.get("popup.warning.trade.txRejected.tradeFee");
}

if (details != null) {
if (newValue == null || newValue.getTxId() == null) {
return;
}

RejectMessage rejectMessage = newValue.getRejectMessage();
log.warn("We received reject message: {}", rejectMessage);

// TODO: Find out which reject messages are critical and which not.
// We got a report where a "tx already known" message caused a failed trade but the deposit tx was valid.
// To avoid such false positives we only handle reject messages which we consider clearly critical.

switch (rejectMessage.getReasonCode()) {
case OBSOLETE:
case DUPLICATE:
case NONSTANDARD:
case CHECKPOINT:
case OTHER:
// We ignore those cases to avoid that not critical reject messages trigger a failed trade.
log.warn("We ignore that reject message as it is likely not critical.");
break;
case MALFORMED:
case INVALID:
case DUST:
case INSUFFICIENTFEE:
// We delay as we might get the rejected tx error before we have completed the create offer protocol
log.warn("We handle that reject message as it is likely critical.");
UserThread.runAfter(() -> {
String txId = newValue.getTxId();
openOfferManager.getObservableList().stream()
.filter(openOffer -> txId.equals(openOffer.getOffer().getOfferFeePaymentTxId()))
.forEach(openOffer -> {
// We delay to avoid concurrent modification exceptions
String finalDetails = details;
UserThread.runAfter(() -> {
trade.setErrorMessage(newValue.getMessage());
rejectedTxErrorMessageHandler.accept(Res.get("popup.warning.trade.txRejected",
finalDetails, trade.getShortId(), txId));
tradeManager.addTradeToFailedTrades(trade);
openOffer.getOffer().setErrorMessage(newValue.getMessage());
if (rejectedTxErrorMessageHandler != null) {
rejectedTxErrorMessageHandler.accept(Res.get("popup.warning.openOffer.makerFeeTxRejected", openOffer.getId(), txId));
}
openOfferManager.removeOpenOffer(openOffer, () -> {
log.warn("We removed an open offer because the maker fee was rejected by the Bitcoin " +
"network. OfferId={}, txId={}", openOffer.getShortId(), txId);
}, log::warn);
}, 1);
}
});
}
}, 3);
});

tradeManager.getTradableList().stream()
.filter(trade -> trade.getOffer() != null)
.forEach(trade -> {
String details = null;
if (txId.equals(trade.getDepositTxId())) {
details = Res.get("popup.warning.trade.txRejected.deposit");
}
if (txId.equals(trade.getOffer().getOfferFeePaymentTxId()) || txId.equals(trade.getTakerFeeTxId())) {
details = Res.get("popup.warning.trade.txRejected.tradeFee");
}

if (details != null) {
// We delay to avoid concurrent modification exceptions
String finalDetails = details;
UserThread.runAfter(() -> {
trade.setErrorMessage(newValue.getMessage());
if (rejectedTxErrorMessageHandler != null) {
rejectedTxErrorMessageHandler.accept(Res.get("popup.warning.trade.txRejected",
finalDetails, trade.getShortId(), txId));
}
tradeManager.addTradeToFailedTrades(trade);
}, 1);
}
});
}, 3);
}
});


arbitratorManager.onAllServicesInitialized();
mediatorManager.onAllServicesInitialized();
refundAgentManager.onAllServicesInitialized();
Expand Down