stagenet deployment fixes

execute protocol tasks off main thread
fix confirmation progress indicators
fix expected unlock height
process sign contract request after contract signature requested
deposit listener only advances trade state
This commit is contained in:
woodser 2022-07-26 01:03:19 -04:00
parent 3dcaf67edd
commit a3a5b96c06
21 changed files with 259 additions and 223 deletions

View File

@ -154,7 +154,7 @@ public class TradeInfo implements Payload {
.withPhase(trade.getPhase().name())
.withPeriodState(trade.getPeriodState().name())
.withIsDepositPublished(trade.isDepositPublished())
.withIsDepositUnlocked(trade.isDepositConfirmed())
.withIsDepositUnlocked(trade.isDepositUnlocked())
.withIsPaymentSent(trade.isPaymentSent())
.withIsPaymentReceived(trade.isPaymentReceived())
.withIsPayoutPublished(trade.isPayoutPublished())

View File

@ -39,7 +39,7 @@ import bisq.core.util.ParsingUtils;
import bisq.core.util.VolumeUtil;
import bisq.network.p2p.AckMessage;
import bisq.network.p2p.NodeAddress;
import bisq.common.UserThread;
import bisq.common.crypto.PubKeyRing;
import bisq.common.proto.ProtoUtil;
import bisq.common.taskrunner.Model;
@ -876,20 +876,19 @@ public abstract class Trade implements Tradable, Model {
// handle deposit txs seen
if (txs.size() == 2) {
// update state
setState(this instanceof MakerTrade ? Trade.State.MAKER_SAW_DEPOSIT_TX_IN_NETWORK : Trade.State.TAKER_SAW_DEPOSIT_TX_IN_NETWORK);
boolean makerFirst = txs.get(0).getHash().equals(processModel.getMaker().getDepositTxHash());
makerDepositTx = makerFirst ? txs.get(0) : txs.get(1);
takerDepositTx = makerFirst ? txs.get(1) : txs.get(0);
// check if deposit txs unlocked
if (txs.get(0).isConfirmed() && txs.get(1).isConfirmed()) {
long unlockHeight = Math.max(txs.get(0).getHeight(), txs.get(0).getHeight()) + XmrWalletService.NUM_BLOCKS_UNLOCK - 1;
long unlockHeight = Math.max(txs.get(0).getHeight(), txs.get(1).getHeight()) + XmrWalletService.NUM_BLOCKS_UNLOCK;
if (havenoWallet.getHeight() >= unlockHeight) {
setConfirmedState();
setUnlockedState();
return;
}
} else {
setStateIfValidTransitionTo(this instanceof MakerTrade ? Trade.State.MAKER_SAW_DEPOSIT_TX_IN_NETWORK : Trade.State.TAKER_SAW_DEPOSIT_TX_IN_NETWORK);
}
}
@ -900,40 +899,37 @@ public abstract class Trade implements Tradable, Model {
@Override
public void onNewBlock(long height) {
// ignore if no longer listening
if (depositTxListener == null) return;
// ignore if before unlock height
if (unlockHeight != null && height < unlockHeight) return;
// fetch txs from daemon
List<MoneroTx> txs = daemon.getTxs(Arrays.asList(processModel.getMaker().getDepositTxHash(), processModel.getTaker().getDepositTxHash()), true);
// ignore if deposit txs not seen
if (txs.size() != 2) return;
// update deposit txs
boolean makerFirst = txs.get(0).getHash().equals(processModel.getMaker().getDepositTxHash());
makerDepositTx = makerFirst ? txs.get(0) : txs.get(1);
takerDepositTx = makerFirst ? txs.get(1) : txs.get(0);
// update state when deposit txs seen
if (txs.size() == 2) {
setState(this instanceof MakerTrade ? Trade.State.MAKER_SAW_DEPOSIT_TX_IN_NETWORK : Trade.State.TAKER_SAW_DEPOSIT_TX_IN_NETWORK);
}
// compute unlock height
if (unlockHeight == null && txs.size() == 2 && txs.get(0).isConfirmed() && txs.get(1).isConfirmed()) {
unlockHeight = Math.max(txs.get(0).getHeight(), txs.get(0).getHeight()) + XmrWalletService.NUM_BLOCKS_UNLOCK - 1;
unlockHeight = Math.max(txs.get(0).getHeight(), txs.get(1).getHeight()) + XmrWalletService.NUM_BLOCKS_UNLOCK;
}
// check if txs unlocked
// check if deposit txs unlocked
if (unlockHeight != null && height == unlockHeight) {
log.info("Multisig deposits unlocked for trade {}", getId());
setConfirmedState(); // TODO (woodser): bisq "confirmed" = xmr unlocked after 10 confirmations
setUnlockedState();
xmrWalletService.removeWalletListener(depositTxListener); // remove listener when notified
depositTxListener = null; // prevent re-applying trade state in subsequent requests
} else if (txs.size() == 2) {
setStateIfValidTransitionTo(this instanceof MakerTrade ? Trade.State.MAKER_SAW_DEPOSIT_TX_IN_NETWORK : Trade.State.TAKER_SAW_DEPOSIT_TX_IN_NETWORK);
}
}
};
@ -1079,8 +1075,10 @@ public abstract class Trade implements Tradable, Model {
}
this.state = state;
stateProperty.set(state);
statePhaseProperty.set(state.getPhase());
UserThread.execute(() -> {
stateProperty.set(state);
statePhaseProperty.set(state.getPhase());
});
}
public void setDisputeState(DisputeState disputeState) {
@ -1242,7 +1240,7 @@ public abstract class Trade implements Tradable, Model {
final MoneroTx takerDepositTx = getTakerDepositTx();
final MoneroTx makerDepositTx = getMakerDepositTx();
if (makerDepositTx != null && takerDepositTx != null && getTakeOfferDate() != null) {
if (isDepositConfirmed()) {
if (isDepositUnlocked()) {
final long tradeTime = getTakeOfferDate().getTime();
long maxHeight = Math.max(makerDepositTx.getHeight(), takerDepositTx.getHeight());
MoneroDaemon daemonRpc = xmrWalletService.getDaemon();
@ -1318,7 +1316,7 @@ public abstract class Trade implements Tradable, Model {
disputeState != DisputeState.REFUND_REQUEST_CLOSED;
}
public boolean isDepositConfirmed() {
public boolean isDepositUnlocked() {
return getState().getPhase().ordinal() >= Phase.DEPOSIT_UNLOCKED.ordinal();
}
@ -1479,13 +1477,13 @@ public abstract class Trade implements Tradable, Model {
// }
// }
private void setConfirmedState() {
private void setUnlockedState() {
// we only apply the state if we are not already further in the process
if (!isDepositConfirmed()) {
if (!isDepositUnlocked()) {
// As setState is called here from the trade itself we cannot trigger a requestPersistence call.
// But as we get setupConfidenceListener called at startup anyway there is no issue if it would not be
// persisted in case the shutdown routine did not persist the trade.
setState(State.DEPOSIT_UNLOCKED_IN_BLOCK_CHAIN); // TODO (woodser): for xmr this means deposit txs have unlocked after 10 confirmations
setStateIfValidTransitionTo(State.DEPOSIT_UNLOCKED_IN_BLOCK_CHAIN); // TODO (woodser): for xmr this means deposit txs have unlocked after 10 confirmations
}
}

View File

@ -884,8 +884,8 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
}
private void updateTradePeriodState() {
getObservableList().forEach(trade -> {
UserThread.execute(() -> { // prevent concurrent modification error
UserThread.execute(() -> { // prevent concurrent modification error
getObservableList().forEach(trade -> {
if (!trade.isPayoutPublished()) {
Date maxTradePeriodDate = trade.getMaxTradePeriodDate();
Date halfTradePeriodDate = trade.getHalfTradePeriodDate();

View File

@ -53,7 +53,7 @@ public class ArbitratorProtocol extends DisputeProtocol {
handleTaskRunnerFault(peer, message, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks();
.executeTasks(true);
awaitTradeLatch();
}
}
@ -79,7 +79,7 @@ public class ArbitratorProtocol extends DisputeProtocol {
handleTaskRunnerFault(sender, request, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks();
.executeTasks(true);
awaitTradeLatch();
}
}
@ -106,7 +106,7 @@ public class ArbitratorProtocol extends DisputeProtocol {
handleTaskRunnerFault(sender, message, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks();
.executeTasks(true);
awaitTradeLatch();
}
}
@ -134,7 +134,7 @@ public class ArbitratorProtocol extends DisputeProtocol {
handleTaskRunnerFault(sender, request, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks();
.executeTasks(true);
awaitTradeLatch();
}
}

View File

@ -1,5 +1,5 @@
/*
* This file is part of Haveno.
e * 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
@ -95,7 +95,7 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
handleTaskRunnerFault(peer, message, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks();
.executeTasks(true);
awaitTradeLatch();
}
}
@ -122,40 +122,13 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
handleTaskRunnerFault(sender, request, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks();
.executeTasks(true);
awaitTradeLatch();
}
}
@Override
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handleSignContractRequest()");
synchronized (trade) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(anyPhase(Trade.Phase.INIT)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
startTimeout(TRADE_TIMEOUT);
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
handleTaskRunnerFault(sender, message, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks();
awaitTradeLatch();
}
}
@Override
public void handleSignContractResponse(SignContractResponse message, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handleSignContractResponse() " + trade.getId());
synchronized (trade) {
Validator.checkTradeId(processModel.getOfferId(), message);
@ -164,6 +137,41 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(state(Trade.State.CONTRACT_SIGNATURE_REQUESTED)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
startTimeout(TRADE_TIMEOUT);
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
handleTaskRunnerFault(sender, message, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT)) // extend timeout
.executeTasks(true);
awaitTradeLatch();
} else {
// process sign contract request after contract signature requested
EasyBind.subscribe(trade.stateProperty(), state -> {
if (state == Trade.State.CONTRACT_SIGNATURE_REQUESTED) new Thread(() -> handleSignContractRequest(message, sender)).start(); // process notification without trade lock
});
}
}
}
@Override
public void handleSignContractResponse(SignContractResponse message, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handleSignContractResponse() " + trade.getId());
synchronized (trade) {
Validator.checkTradeId(processModel.getOfferId(), message);
if (trade.getState() == Trade.State.CONTRACT_SIGNED) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(state(Trade.State.CONTRACT_SIGNED)
.with(message)
.from(sender))
.setup(tasks(
@ -178,11 +186,12 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
handleTaskRunnerFault(sender, message, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT)) // extend timeout
.executeTasks();
.executeTasks(true);
awaitTradeLatch();
} else {
// process sign contract response after contract signed
EasyBind.subscribe(trade.stateProperty(), state -> {
if (state == Trade.State.CONTRACT_SIGNATURE_REQUESTED) new Thread(() -> handleSignContractResponse(message, sender)).start(); // process notification without trade lock
if (state == Trade.State.CONTRACT_SIGNED) new Thread(() -> handleSignContractResponse(message, sender)).start(); // process notification without trade lock
});
}
}
@ -210,7 +219,7 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
handleTaskRunnerFault(sender, response, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks();
.executeTasks(true);
awaitTradeLatch();
}
}
@ -241,7 +250,7 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
handleTaskRunnerFault(sender, request, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks();
.executeTasks(true);
awaitTradeLatch();
} else {
EasyBind.subscribe(trade.stateProperty(), state -> {
@ -295,7 +304,7 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
BuyerFinalizesDelayedPayoutTx.class,
BuyerSendsDelayedPayoutTxSignatureResponse.class)
.withTimeout(60))
.executeTasks();
.executeTasks(true);
}
// We keep the handler here in as well to make it more transparent which messages we expect

View File

@ -112,7 +112,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
handleError(errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks();
.executeTasks(true);
awaitTradeLatch();
}
}
@ -139,35 +139,43 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
handleTaskRunnerFault(sender, request, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks();
.executeTasks(true);
awaitTradeLatch();
}
}
@Override
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handleSignContractRequest()");
System.out.println(getClass().getCanonicalName() + ".handleSignContractResponse() " + trade.getId());
synchronized (trade) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(anyPhase(Trade.Phase.INIT)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
startTimeout(TRADE_TIMEOUT);
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
handleTaskRunnerFault(sender, message, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks();
awaitTradeLatch();
if (trade.getState() == Trade.State.CONTRACT_SIGNATURE_REQUESTED) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(state(Trade.State.CONTRACT_SIGNATURE_REQUESTED)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
startTimeout(TRADE_TIMEOUT);
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
handleTaskRunnerFault(sender, message, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT)) // extend timeout
.executeTasks(true);
awaitTradeLatch();
} else {
// process sign contract request after contract signature requested
EasyBind.subscribe(trade.stateProperty(), state -> {
if (state == Trade.State.CONTRACT_SIGNATURE_REQUESTED) new Thread(() -> handleSignContractRequest(message, sender)).start(); // process notification without trade lock
});
}
}
}
@ -176,11 +184,11 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
System.out.println(getClass().getCanonicalName() + ".handleSignContractResponse()");
synchronized (trade) {
Validator.checkTradeId(processModel.getOfferId(), message);
if (trade.getState() == Trade.State.CONTRACT_SIGNATURE_REQUESTED) {
if (trade.getState() == Trade.State.CONTRACT_SIGNED) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(state(Trade.State.CONTRACT_SIGNATURE_REQUESTED)
expect(state(Trade.State.CONTRACT_SIGNED)
.with(message)
.from(sender))
.setup(tasks(
@ -195,11 +203,11 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
handleTaskRunnerFault(sender, message, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT)) // extend timeout
.executeTasks();
.executeTasks(true);
awaitTradeLatch();
} else {
EasyBind.subscribe(trade.stateProperty(), state -> {
if (state == Trade.State.CONTRACT_SIGNATURE_REQUESTED) new Thread(() -> handleSignContractResponse(message, sender)).start(); // process notification without trade lock
if (state == Trade.State.CONTRACT_SIGNED) new Thread(() -> handleSignContractResponse(message, sender)).start(); // process notification without trade lock
});
}
}
@ -227,7 +235,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
handleTaskRunnerFault(sender, response, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks();
.executeTasks(true);
awaitTradeLatch();
}
}
@ -258,7 +266,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
handleTaskRunnerFault(sender, request, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks();
.executeTasks(true);
awaitTradeLatch();
} else {
EasyBind.subscribe(trade.stateProperty(), state -> {
@ -325,7 +333,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
BuyerSetupDepositTxListener.class,
BuyerAsTakerSendsDepositTxMessage.class)
.withTimeout(60))
.executeTasks();
.executeTasks(true);
}
@Override
@ -340,7 +348,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
BuyerFinalizesDelayedPayoutTx.class,
BuyerSendsDelayedPayoutTxSignatureResponse.class)
.withTimeout(60))
.executeTasks();
.executeTasks(true);
}
// We keep the handler here in as well to make it more transparent which messages we expect

View File

@ -161,7 +161,7 @@ public abstract class BuyerProtocol extends DisputeProtocol {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(anyPhase(Trade.Phase.PAYMENT_SENT, Trade.Phase.PAYOUT_PUBLISHED)
expect(anyPhase(Trade.Phase.PAYMENT_SENT, Trade.Phase.PAYMENT_RECEIVED)
.with(message)
.from(peer))
.setup(tasks(
@ -177,7 +177,7 @@ public abstract class BuyerProtocol extends DisputeProtocol {
handleTaskRunnerFault(peer, message, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks();
.executeTasks(true);
awaitTradeLatch();
}
}
@ -191,9 +191,6 @@ public abstract class BuyerProtocol extends DisputeProtocol {
protected void onTradeMessage(TradeMessage message, NodeAddress peer) {
super.onTradeMessage(message, peer);
log.info("Received {} from {} with tradeId {} and uid {}",
message.getClass().getSimpleName(), peer, message.getTradeId(), message.getUid());
if (message instanceof DelayedPayoutTxSignatureRequest) {
handle((DelayedPayoutTxSignatureRequest) message, peer);
} else if (message instanceof DepositTxAndDelayedPayoutTxMessage) {

View File

@ -83,6 +83,17 @@ public class FluentProtocol {
return this;
}
public FluentProtocol executeTasks(boolean newThread) {
if (newThread) {
new Thread(() -> {
executeTasks();
}).start();
} else {
executeTasks();
}
return this;
}
public FluentProtocol executeTasks() {
Condition.Result result = condition.getResult();
if (!result.isValid) {
@ -92,30 +103,27 @@ public class FluentProtocol {
return this;
}
synchronized (tradeProtocol.trade) {
if (setup.getTimeoutSec() > 0) {
tradeProtocol.startTimeout(setup.getTimeoutSec());
}
NodeAddress peer = condition.getPeer();
if (peer != null) {
tradeProtocol.processModel.setTempTradingPeerNodeAddress(peer); // TODO (woodser): node has multiple peers (arbitrator and maker or taker), but fluent protocol assumes only one
tradeProtocol.processModel.getTradeManager().requestPersistence();
}
TradeMessage message = condition.getMessage();
if (message != null) {
tradeProtocol.processModel.setTradeMessage(message);
tradeProtocol.processModel.getTradeManager().requestPersistence();
}
TradeTaskRunner taskRunner = setup.getTaskRunner(peer, message, condition.getEvent());
taskRunner.addTasks(setup.getTasks());
taskRunner.run();
return this;
if (setup.getTimeoutSec() > 0) {
tradeProtocol.startTimeout(setup.getTimeoutSec());
}
}
NodeAddress peer = condition.getPeer();
if (peer != null) {
tradeProtocol.processModel.setTempTradingPeerNodeAddress(peer); // TODO (woodser): node has multiple peers (arbitrator and maker or taker), but fluent protocol assumes only one
tradeProtocol.processModel.getTradeManager().requestPersistence();
}
TradeMessage message = condition.getMessage();
if (message != null) {
tradeProtocol.processModel.setTradeMessage(message);
tradeProtocol.processModel.getTradeManager().requestPersistence();
}
TradeTaskRunner taskRunner = setup.getTaskRunner(peer, message, condition.getEvent());
taskRunner.addTasks(setup.getTasks());
taskRunner.run();
return this;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Condition class

View File

@ -95,7 +95,7 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
handleTaskRunnerFault(peer, message, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks();
.executeTasks(true);
awaitTradeLatch();
}
}
@ -122,35 +122,43 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
handleTaskRunnerFault(sender, request, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks();
.executeTasks(true);
awaitTradeLatch();
}
}
@Override
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handleSignContractRequest()");
System.out.println(getClass().getCanonicalName() + ".handleSignContractResponse() " + trade.getId());
synchronized (trade) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(anyPhase(Trade.Phase.INIT)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
startTimeout(TRADE_TIMEOUT);
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
handleTaskRunnerFault(sender, message, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks();
awaitTradeLatch();
if (trade.getState() == Trade.State.CONTRACT_SIGNATURE_REQUESTED) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(state(Trade.State.CONTRACT_SIGNATURE_REQUESTED)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
startTimeout(TRADE_TIMEOUT);
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
handleTaskRunnerFault(sender, message, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT)) // extend timeout
.executeTasks(true);
awaitTradeLatch();
} else {
// process sign contract request after contract signature requested
EasyBind.subscribe(trade.stateProperty(), state -> {
if (state == Trade.State.CONTRACT_SIGNATURE_REQUESTED) new Thread(() -> handleSignContractRequest(message, sender)).start(); // process notification without trade lock
});
}
}
}
@ -159,11 +167,11 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
System.out.println(getClass().getCanonicalName() + ".handleSignContractResponse()");
synchronized (trade) {
Validator.checkTradeId(processModel.getOfferId(), message);
if (trade.getState() == Trade.State.CONTRACT_SIGNATURE_REQUESTED) {
if (trade.getState() == Trade.State.CONTRACT_SIGNED) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(state(Trade.State.CONTRACT_SIGNATURE_REQUESTED)
expect(state(Trade.State.CONTRACT_SIGNED)
.with(message)
.from(sender))
.setup(tasks(
@ -178,11 +186,11 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
handleTaskRunnerFault(sender, message, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT)) // extend timeout
.executeTasks();
.executeTasks(true);
awaitTradeLatch();
} else {
EasyBind.subscribe(trade.stateProperty(), state -> {
if (state == Trade.State.CONTRACT_SIGNATURE_REQUESTED) new Thread(() -> handleSignContractResponse(message, sender)).start(); // process notification without trade lock
if (state == Trade.State.CONTRACT_SIGNED) new Thread(() -> handleSignContractResponse(message, sender)).start(); // process notification without trade lock
});
}
}
@ -210,7 +218,7 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
handleTaskRunnerFault(sender, response, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks();
.executeTasks(true);
awaitTradeLatch();
}
}
@ -241,7 +249,7 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
handleTaskRunnerFault(sender, request, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks();
.executeTasks(true);
awaitTradeLatch();
} else {
EasyBind.subscribe(trade.stateProperty(), state -> {
@ -300,7 +308,7 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
SellerSignsDelayedPayoutTx.class,
SellerSendDelayedPayoutTxSignatureRequest.class)
.withTimeout(60))
.executeTasks();
.executeTasks(true);
}

View File

@ -105,7 +105,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
handleError(errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks();
.executeTasks(true);
awaitTradeLatch();
}
}
@ -132,40 +132,13 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
handleTaskRunnerFault(sender, request, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks();
.executeTasks(true);
awaitTradeLatch();
}
}
@Override
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handleSignContractRequest()");
synchronized (trade) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(anyPhase(Trade.Phase.INIT)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
startTimeout(TRADE_TIMEOUT);
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
handleTaskRunnerFault(sender, message, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks();
awaitTradeLatch();
}
}
@Override
public void handleSignContractResponse(SignContractResponse message, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handleSignContractResponse() " + trade.getId());
synchronized (trade) {
Validator.checkTradeId(processModel.getOfferId(), message);
@ -174,6 +147,41 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(state(Trade.State.CONTRACT_SIGNATURE_REQUESTED)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
startTimeout(TRADE_TIMEOUT);
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
handleTaskRunnerFault(sender, message, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT)) // extend timeout
.executeTasks(true);
awaitTradeLatch();
} else {
// process sign contract request after contract signature requested
EasyBind.subscribe(trade.stateProperty(), state -> {
if (state == Trade.State.CONTRACT_SIGNATURE_REQUESTED) new Thread(() -> handleSignContractRequest(message, sender)).start(); // process notification without trade lock
});
}
}
}
@Override
public void handleSignContractResponse(SignContractResponse message, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handleSignContractResponse() " + trade.getId());
synchronized (trade) {
Validator.checkTradeId(processModel.getOfferId(), message);
if (trade.getState() == Trade.State.CONTRACT_SIGNED) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(state(Trade.State.CONTRACT_SIGNED)
.with(message)
.from(sender))
.setup(tasks(
@ -188,11 +196,11 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
handleTaskRunnerFault(sender, message, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT)) // extend timeout
.executeTasks();
.executeTasks(true);
awaitTradeLatch();
} else {
EasyBind.subscribe(trade.stateProperty(), state -> {
if (state == Trade.State.CONTRACT_SIGNATURE_REQUESTED) new Thread(() -> handleSignContractResponse(message, sender)).start(); // process notification without trade lock
if (state == Trade.State.CONTRACT_SIGNED) new Thread(() -> handleSignContractResponse(message, sender)).start(); // process notification without trade lock
});
}
}
@ -220,7 +228,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
handleTaskRunnerFault(sender, response, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks();
.executeTasks(true);
awaitTradeLatch();
}
}
@ -251,7 +259,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
handleTaskRunnerFault(sender, request, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks();
.executeTasks(true);
awaitTradeLatch();
} else {
EasyBind.subscribe(trade.stateProperty(), state -> {
@ -324,6 +332,6 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
SellerSignsDelayedPayoutTx.class,
SellerSendDelayedPayoutTxSignatureRequest.class)
.withTimeout(60))
.executeTasks();
.executeTasks(true);
}
}

View File

@ -113,7 +113,7 @@ public abstract class SellerProtocol extends DisputeProtocol {
handleTaskRunnerFault(peer, message, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks();
.executeTasks(true);
awaitTradeLatch();
}
}
@ -126,7 +126,7 @@ public abstract class SellerProtocol extends DisputeProtocol {
log.info("SellerProtocol.onPaymentReceived()");
synchronized (trade) {
SellerEvent event = SellerEvent.PAYMENT_RECEIVED;
expect(anyPhase(Trade.Phase.PAYMENT_SENT)
expect(anyPhase(Trade.Phase.PAYMENT_SENT, Trade.Phase.PAYMENT_RECEIVED)
.with(event)
.preCondition(trade.confirmPermitted()))
.setup(tasks(

View File

@ -461,6 +461,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
handleError(errorMessage);
}
// these are not thread safe, so they must be used within a lock on the trade
protected void handleError(String errorMessage) {
stopTimeout();

View File

@ -134,7 +134,7 @@ public class ProcessSignContractRequest extends TradeTask {
}
private void completeAux() {
trade.setState(State.CONTRACT_SIGNATURE_REQUESTED); // TODO: rename to contract_signature_request_received
trade.setState(State.CONTRACT_SIGNED);
processModel.getTradeManager().requestPersistence();
complete();
}

View File

@ -21,6 +21,7 @@ import bisq.common.app.Version;
import bisq.common.taskrunner.TaskRunner;
import bisq.core.btc.model.XmrAddressEntry;
import bisq.core.trade.Trade;
import bisq.core.trade.Trade.State;
import bisq.core.trade.messages.SignContractRequest;
import bisq.network.p2p.SendDirectMessageListener;
import java.util.Date;
@ -124,6 +125,7 @@ public class SendSignContractRequestAfterMultisig extends TradeTask {
}
private void completeAux() {
trade.setState(State.CONTRACT_SIGNATURE_REQUESTED);
processModel.getTradeManager().requestPersistence();
processModel.getXmrWalletService().saveWallet(processModel.getXmrWalletService().getWallet());
complete();

View File

@ -97,6 +97,7 @@ public class BuyerSendsPaymentSentMessage extends SendMailboxMessageTask {
@Override
protected void setStateArrived() {
trade.setStateIfValidTransitionTo(Trade.State.BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG);
// the message has arrived but we're ultimately waiting for an AckMessage response
if (!trade.isPayoutPublished()) {
tryToSendAgainLater();

View File

@ -40,7 +40,7 @@ import javafx.scene.layout.AnchorPane;
import lombok.Getter;
import lombok.Setter;
import monero.wallet.model.MoneroTxWallet;
import monero.daemon.model.MoneroTx;
import monero.wallet.model.MoneroWalletListener;
import javax.annotation.Nullable;
@ -176,9 +176,10 @@ public class TxIdTextField extends AnchorPane {
}
private void updateConfidence(String txId) {
MoneroTxWallet tx = null;
MoneroTx tx = null;
try {
tx = xmrWalletService.getWallet().getTx(txId);
tx = xmrWalletService.getDaemon().getTx(txId); // TODO: cache results and don't re-fetch
tx.setNumConfirmations(tx.isConfirmed() ? xmrWalletService.getConnectionsService().getLastInfo().getHeight() - tx.getHeight() : 0l); // TODO: use tx.getNumConfirmations() when MoneroDaemonRpc supports it
} catch (Exception e) {
// do nothing
}
@ -188,6 +189,10 @@ public class TxIdTextField extends AnchorPane {
txConfidenceIndicator.setVisible(true);
AnchorPane.setRightAnchor(txConfidenceIndicator, 0.0);
}
if (txConfidenceIndicator.getProgress() >= 1.0 && txUpdater != null) {
xmrWalletService.removeWalletListener(txUpdater); // unregister listener
txUpdater = null;
}
} else {
//TODO we should show some placeholder in case of a tx which we are not aware of but which can be
// confirmed already. This is for instance the case of the other peers trade fee tx, as it is not related

View File

@ -640,11 +640,7 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
if (item != null && !empty) {
trade = item.getTrade();
listener = (observable, oldValue, newValue) -> Platform.runLater(new Runnable() {
@Override public void run() {
update();
}
});
listener = (observable, oldValue, newValue) -> UserThread.execute(() -> update());
trade.stateProperty().addListener(listener);
update();
} else {
@ -805,7 +801,6 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
@Override
public void updateItem(final PendingTradesListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setGraphic(new AutoTooltipLabel(item.getMarketDescription()));
} else {

View File

@ -98,8 +98,8 @@ public abstract class TradeStepView extends AnchorPane {
private TxIdTextField selfTxIdTextField;
private TxIdTextField peerTxIdTextField;
private TradeStepInfo tradeStepInfo;
private Subscription makerTxIdSubscription;
private Subscription takerTxIdSubscription;
private Subscription selfTxIdSubscription;
private Subscription peerTxIdSubscription;
private ClockWatcher.Listener clockListener;
private final ChangeListener<String> errorMessageListener;
protected Label infoLabel;
@ -175,15 +175,11 @@ public abstract class TradeStepView extends AnchorPane {
}
public void activate() {
UserThread.execute(() -> { activateAux(); });
}
private void activateAux() {
if (selfTxIdTextField != null) {
if (makerTxIdSubscription != null)
makerTxIdSubscription.unsubscribe();
if (selfTxIdSubscription != null)
selfTxIdSubscription.unsubscribe();
makerTxIdSubscription = EasyBind.subscribe(model.dataModel.makerTxId, id -> {
selfTxIdSubscription = EasyBind.subscribe(model.dataModel.isMaker() ? model.dataModel.makerTxId : model.dataModel.takerTxId, id -> {
if (!id.isEmpty())
selfTxIdTextField.setup(id);
else
@ -191,10 +187,10 @@ public abstract class TradeStepView extends AnchorPane {
});
}
if (peerTxIdTextField != null) {
if (takerTxIdSubscription != null)
takerTxIdSubscription.unsubscribe();
if (peerTxIdSubscription != null)
peerTxIdSubscription.unsubscribe();
takerTxIdSubscription = EasyBind.subscribe(model.dataModel.takerTxId, id -> {
selfTxIdSubscription = EasyBind.subscribe(model.dataModel.isMaker() ? model.dataModel.takerTxId : model.dataModel.makerTxId, id -> {
if (!id.isEmpty())
peerTxIdTextField.setup(id);
else
@ -288,10 +284,10 @@ public abstract class TradeStepView extends AnchorPane {
}
public void deactivate() {
if (makerTxIdSubscription != null)
makerTxIdSubscription.unsubscribe();
if (takerTxIdSubscription != null)
takerTxIdSubscription.unsubscribe();
if (selfTxIdSubscription != null)
selfTxIdSubscription.unsubscribe();
if (peerTxIdSubscription != null)
peerTxIdSubscription.unsubscribe();
if (selfTxIdTextField != null)
selfTxIdTextField.cleanup();

View File

@ -155,7 +155,7 @@ public class BuyerStep2View extends TradeStepView {
if (timeoutTimer != null)
timeoutTimer.stop();
if (trade.isDepositConfirmed() && !trade.isPaymentSent()) {
if (trade.isDepositUnlocked() && !trade.isPaymentSent()) {
showPopup();
} else if (state.ordinal() <= Trade.State.BUYER_SEND_FAILED_PAYMENT_SENT_MSG.ordinal()) {
if (!trade.hasFailed()) {

View File

@ -136,6 +136,7 @@ public class SellerStep3View extends TradeStepView {
busyAnimation.stop();
statusLabel.setText(Res.get("shared.messageArrived"));
break;
case SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG:
case SELLER_STORED_IN_MAILBOX_PAYOUT_TX_PUBLISHED_MSG:
busyAnimation.stop();
statusLabel.setText(Res.get("shared.messageStoredInMailbox"));

View File

@ -65,7 +65,6 @@ import bisq.common.util.Utilities;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.TransactionConfidence;
import org.bitcoinj.uri.BitcoinURI;
import com.googlecode.jcsv.CSVStrategy;
@ -135,7 +134,7 @@ import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import lombok.extern.slf4j.Slf4j;
import monero.wallet.model.MoneroTxWallet;
import monero.daemon.model.MoneroTx;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
@ -567,7 +566,7 @@ public class GUIUtil {
};
}
public static void updateConfidence(MoneroTxWallet tx,
public static void updateConfidence(MoneroTx tx,
Tooltip tooltip,
TxConfidenceIndicator txConfidenceIndicator) {
if (tx != null) {