fixes when cloned offers are taken at the same time

This commit is contained in:
woodser 2025-04-19 16:54:01 -04:00 committed by GitHub
parent 13e13d945d
commit c7a3a9740f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 47 additions and 24 deletions

View File

@ -2648,7 +2648,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
}
}
setDepositTxs(txs);
if (getMaker().getDepositTx() == null || (getTaker().getDepositTx() == null && !hasBuyerAsTakerWithoutDeposit())) return; // skip if either deposit tx not seen
if (!isPublished(getMaker().getDepositTx()) || (!hasBuyerAsTakerWithoutDeposit() && !isPublished(getTaker().getDepositTx()))) return; // skip if deposit txs not published successfully
setStateDepositsSeen();
// set actual security deposits
@ -2750,6 +2750,13 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
}
}
private static boolean isPublished(MoneroTx tx) {
if (tx == null) return false;
if (Boolean.TRUE.equals(tx.isFailed())) return false;
if (!Boolean.TRUE.equals(tx.inTxPool()) && !Boolean.TRUE.equals(tx.isConfirmed())) return false;
return true;
}
private void syncWalletIfBehind() {
synchronized (walletLock) {
if (isWalletBehind()) {

View File

@ -460,9 +460,19 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
.using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
this.errorMessageHandler = null; // TODO: set this when trade state is >= DEPOSIT_PUBLISHED
handleTaskRunnerSuccess(sender, response);
if (tradeResultHandler != null) tradeResultHandler.handleResult(trade); // trade is initialized
// tasks may complete successfully but process an error
if (trade.getInitError() == null) {
this.errorMessageHandler = null; // TODO: set this when trade state is >= DEPOSIT_PUBLISHED
handleTaskRunnerSuccess(sender, response);
if (tradeResultHandler != null) tradeResultHandler.handleResult(trade); // trade is initialized
} else {
handleTaskRunnerSuccess(sender, response);
if (errorMessageHandler != null) errorMessageHandler.handleErrorMessage(trade.getInitError().getMessage());
}
this.tradeResultHandler = null;
this.errorMessageHandler = null;
},
errorMessage -> {
handleTaskRunnerFault(sender, response, errorMessage);

View File

@ -95,6 +95,18 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
// set peer's signature
sender.setContractSignature(signature);
// subscribe to trade state once to send responses with ack or nack
if (!hasBothContractSignatures()) {
trade.stateProperty().addListener((obs, oldState, newState) -> {
if (oldState == newState) return;
if (newState == Trade.State.PUBLISH_DEPOSIT_TX_REQUEST_FAILED) {
sendDepositResponsesOnce(trade.getProcessModel().error == null ? "Arbitrator failed to publish deposit txs within timeout for trade " + trade.getId() : trade.getProcessModel().error.getMessage());
} else if (newState.ordinal() >= Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS.ordinal()) {
sendDepositResponsesOnce(null);
}
});
}
// collect expected values
Offer offer = trade.getOffer();
boolean isFromTaker = sender == trade.getTaker();
@ -138,7 +150,7 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
// relay deposit txs when both requests received
MoneroDaemon daemon = trade.getXmrWalletService().getDaemon();
if (processModel.getMaker().getContractSignature() != null && processModel.getTaker().getContractSignature() != null) {
if (hasBothContractSignatures()) {
// check timeout and extend just before relaying
if (isTimedOut()) throw new RuntimeException("Trade protocol has timed out before relaying deposit txs for {} {}" + trade.getClass().getSimpleName() + " " + trade.getShortId());
@ -182,22 +194,15 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
throw e;
}
} else {
// subscribe to trade state once to send responses with ack or nack
trade.stateProperty().addListener((obs, oldState, newState) -> {
if (oldState == newState) return;
if (newState == Trade.State.PUBLISH_DEPOSIT_TX_REQUEST_FAILED) {
sendDepositResponsesOnce(trade.getProcessModel().error == null ? "Arbitrator failed to publish deposit txs within timeout for trade " + trade.getId() : trade.getProcessModel().error.getMessage());
} else if (newState.ordinal() >= Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS.ordinal()) {
sendDepositResponsesOnce(null);
}
});
if (processModel.getMaker().getDepositTxHex() == null) log.info("Arbitrator waiting for deposit request from maker for trade " + trade.getId());
if (processModel.getTaker().getDepositTxHex() == null && !trade.hasBuyerAsTakerWithoutDeposit()) log.info("Arbitrator waiting for deposit request from taker for trade " + trade.getId());
}
}
private boolean hasBothContractSignatures() {
return processModel.getMaker().getContractSignature() != null && processModel.getTaker().getContractSignature() != null;
}
private boolean isTimedOut() {
return !processModel.getTradeManager().hasOpenTrade(trade);
}
@ -210,7 +215,7 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
// log error
if (errorMessage != null) {
log.warn("Sending deposit responses with error={}", errorMessage, new Throwable("Stack trace"));
log.warn("Sending deposit responses for tradeId={}, error={}", trade.getId(), errorMessage);
}
// create deposit response
@ -229,7 +234,7 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
}
private void sendDepositResponse(NodeAddress nodeAddress, PubKeyRing pubKeyRing, DepositResponse response) {
log.info("Sending deposit response to trader={}; offerId={}, error={}", nodeAddress, trade.getId(), trade.getProcessModel().error);
log.info("Sending deposit response to trader={}; offerId={}, error={}", nodeAddress, trade.getId(), response.getErrorMessage());
processModel.getP2PService().sendEncryptedDirectMessage(nodeAddress, pubKeyRing, response, new SendDirectMessageListener() {
@Override
public void onArrived() {

View File

@ -38,13 +38,14 @@ public class ProcessDepositResponse extends TradeTask {
try {
runInterceptHook();
// throw if error
// handle error
DepositResponse message = (DepositResponse) processModel.getTradeMessage();
if (message.getErrorMessage() != null) {
log.warn("Unregistering trade {} {} because deposit response has error message={}", trade.getClass().getSimpleName(), trade.getShortId(), message.getErrorMessage());
log.warn("Deposit response for {} {} has error message={}", trade.getClass().getSimpleName(), trade.getShortId(), message.getErrorMessage());
trade.setStateIfValidTransitionTo(Trade.State.PUBLISH_DEPOSIT_TX_REQUEST_FAILED);
processModel.getTradeManager().unregisterTrade(trade);
throw new RuntimeException(message.getErrorMessage());
trade.setInitError(new RuntimeException(message.getErrorMessage()));
complete();
return;
}
// record security deposits

View File

@ -105,7 +105,6 @@ public abstract class XmrWalletBase {
// start polling wallet for progress
syncProgressLatch = new CountDownLatch(1);
syncProgressLooper = new TaskLooper(() -> {
if (wallet == null) return;
long height;
try {
height = wallet.getHeight(); // can get read timeout while syncing

View File

@ -346,7 +346,8 @@ public class DepositView extends ActivatableView<VBox, Void> {
List<XmrAddressEntry> addressEntries = xmrWalletService.getAddressEntries();
List<DepositListItem> items = new ArrayList<>();
for (XmrAddressEntry addressEntry : addressEntries) {
if (addressEntry.isTradePayout()) continue; // do not show trade payout addresses
DepositListItem item = new DepositListItem(addressEntry, xmrWalletService, formatter);
if (addressEntry.isTradePayout() && BigInteger.ZERO.equals(item.getBalanceAsBI())) continue; // do not show empty trade payout addresses
items.add(new DepositListItem(addressEntry, xmrWalletService, formatter));
}