From 50126874a04f4df8d1c2d05e25213f06a8c5793e Mon Sep 17 00:00:00 2001 From: woodser Date: Sat, 30 Jul 2022 12:36:52 -0400 Subject: [PATCH] add multisig wallet state and wait for multisig to complete refactor trade protocol --- core/src/main/java/bisq/core/trade/Trade.java | 11 +- .../trade/protocol/ArbitratorProtocol.java | 71 ++------ .../trade/protocol/BuyerAsMakerProtocol.java | 167 ++--------------- .../trade/protocol/BuyerAsTakerProtocol.java | 154 +--------------- .../core/trade/protocol/BuyerProtocol.java | 2 +- .../core/trade/protocol/ProcessModel.java | 6 - .../trade/protocol/SellerAsMakerProtocol.java | 170 ++--------------- .../trade/protocol/SellerAsTakerProtocol.java | 159 +--------------- .../core/trade/protocol/SellerProtocol.java | 14 +- .../core/trade/protocol/TradeProtocol.java | 171 +++++++++++++++++- ...java => MaybeSendSignContractRequest.java} | 17 +- .../tasks/ProcessInitMultisigRequest.java | 10 +- ...enOffer.java => MaybeRemoveOpenOffer.java} | 11 +- .../bisq/daemon/grpc/GrpcTradesService.java | 5 +- .../bisq/desktop/main/debug/DebugView.java | 6 +- .../pendingtrades/PendingTradesViewModel.java | 1 + proto/src/main/proto/pb.proto | 78 ++++---- 17 files changed, 305 insertions(+), 748 deletions(-) rename core/src/main/java/bisq/core/trade/protocol/tasks/{SendSignContractRequestAfterMultisig.java => MaybeSendSignContractRequest.java} (91%) rename core/src/main/java/bisq/core/trade/protocol/tasks/maker/{MakerRemovesOpenOffer.java => MaybeRemoveOpenOffer.java} (77%) diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/Trade.java index 84dfef0a45..9d2fbd63a7 100644 --- a/core/src/main/java/bisq/core/trade/Trade.java +++ b/core/src/main/java/bisq/core/trade/Trade.java @@ -107,7 +107,10 @@ public abstract class Trade implements Tradable, Model { // #################### Phase INIT // When trade protocol starts no funds are on stake PREPARATION(Phase.INIT), - CONTRACT_SIGNATURE_REQUESTED(Phase.INIT), // TODO (woodser): add more states for initializing multisig, etc to support trade initialization notifications + MULTISIG_PREPARED(Phase.INIT), + MULTISIG_MADE(Phase.INIT), + MULTISIG_COMPLETED(Phase.INIT), + CONTRACT_SIGNATURE_REQUESTED(Phase.INIT), CONTRACT_SIGNED(Phase.INIT), // At first part maker/taker have different roles @@ -894,7 +897,6 @@ public abstract class Trade implements Tradable, Model { // create block listener depositTxListener = new MoneroWalletListener() { - Long unlockHeight = null; @Override @@ -903,6 +905,9 @@ public abstract class Trade implements Tradable, Model { // ignore if no longer listening if (depositTxListener == null) return; + // use latest height + height = havenoWallet.getHeight(); + // ignore if before unlock height if (unlockHeight != null && height < unlockHeight) return; @@ -921,7 +926,7 @@ public abstract class Trade implements Tradable, Model { if (unlockHeight == null && txs.size() == 2 && txs.get(0).isConfirmed() && txs.get(1).isConfirmed()) { unlockHeight = Math.max(txs.get(0).getHeight(), txs.get(1).getHeight()) + XmrWalletService.NUM_BLOCKS_UNLOCK; } - + // check if deposit txs unlocked if (unlockHeight != null && height >= unlockHeight) { log.info("Multisig deposits unlocked for trade {}", getId()); diff --git a/core/src/main/java/bisq/core/trade/protocol/ArbitratorProtocol.java b/core/src/main/java/bisq/core/trade/protocol/ArbitratorProtocol.java index 60163dcd80..e8fb99589c 100644 --- a/core/src/main/java/bisq/core/trade/protocol/ArbitratorProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/ArbitratorProtocol.java @@ -3,16 +3,15 @@ package bisq.core.trade.protocol; import bisq.core.trade.ArbitratorTrade; import bisq.core.trade.Trade; import bisq.core.trade.messages.DepositRequest; -import bisq.core.trade.messages.InitMultisigRequest; +import bisq.core.trade.messages.DepositResponse; import bisq.core.trade.messages.InitTradeRequest; -import bisq.core.trade.messages.SignContractRequest; +import bisq.core.trade.messages.PaymentAccountPayloadRequest; +import bisq.core.trade.messages.SignContractResponse; import bisq.core.trade.protocol.tasks.ApplyFilter; import bisq.core.trade.protocol.tasks.ArbitratorSendsInitTradeAndMultisigRequests; import bisq.core.trade.protocol.tasks.ArbitratorProcessesDepositRequest; -import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest; import bisq.core.trade.protocol.tasks.ArbitratorProcessesReserveTx; import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest; -import bisq.core.trade.protocol.tasks.ProcessSignContractRequest; import bisq.core.util.Validator; import bisq.network.p2p.NodeAddress; import bisq.common.handlers.ErrorMessageHandler; @@ -59,60 +58,12 @@ public class ArbitratorProtocol extends DisputeProtocol { } @Override - public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender) { - System.out.println("ArbitratorProtocol.handleInitMultisigRequest()"); - synchronized (trade) { - latchTrade(); - Validator.checkTradeId(processModel.getOfferId(), request); - processModel.setTradeMessage(request); - expect(anyPhase(Trade.Phase.INIT) - .with(request) - .from(sender)) - .setup(tasks( - ProcessInitMultisigRequest.class) - .using(new TradeTaskRunner(trade, - () -> { - startTimeout(TRADE_TIMEOUT); - handleTaskRunnerSuccess(sender, request); - }, - errorMessage -> { - handleTaskRunnerFault(sender, request, errorMessage); - })) - .withTimeout(TRADE_TIMEOUT)) - .executeTasks(true); - awaitTradeLatch(); - } - } - - @Override - public void handleSignContractRequest(SignContractRequest message, NodeAddress sender) { - System.out.println("ArbitratorProtocol.handleSignContractRequest()"); - synchronized (trade) { - latchTrade(); - Validator.checkTradeId(processModel.getOfferId(), message); - processModel.setTradeMessage(message); // TODO (woodser): synchronize access since concurrent requests processed - 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(true); - awaitTradeLatch(); - } + public void handleSignContractResponse(SignContractResponse message, NodeAddress sender) { + log.warn("Arbitrator ignoring SignContractResponse"); } public void handleDepositRequest(DepositRequest request, NodeAddress sender) { - System.out.println("ArbitratorProtocol.handleDepositRequest()"); + System.out.println("ArbitratorProtocol.handleDepositRequest() " + trade.getId()); synchronized (trade) { latchTrade(); Validator.checkTradeId(processModel.getOfferId(), request); @@ -138,6 +89,16 @@ public class ArbitratorProtocol extends DisputeProtocol { awaitTradeLatch(); } } + + @Override + public void handleDepositResponse(DepositResponse response, NodeAddress sender) { + log.warn("Arbitrator ignoring DepositResponse"); + } + + @Override + public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender) { + log.warn("Arbitrator ignoring PaymentAccountPayloadRequest"); + } /////////////////////////////////////////////////////////////////////////////////////////// // Message dispatcher diff --git a/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java index a8637a9655..23f2b1f759 100644 --- a/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java @@ -28,30 +28,22 @@ import bisq.core.trade.messages.PaymentAccountPayloadRequest; import bisq.core.trade.messages.PaymentReceivedMessage; import bisq.core.trade.messages.SignContractRequest; import bisq.core.trade.messages.SignContractResponse; -import bisq.core.trade.protocol.tasks.ProcessDepositResponse; -import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest; import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest; -import bisq.core.trade.protocol.tasks.ProcessPaymentAccountPayloadRequest; -import bisq.core.trade.protocol.tasks.ProcessSignContractRequest; -import bisq.core.trade.protocol.tasks.ProcessSignContractResponse; -import bisq.core.trade.protocol.tasks.SendSignContractRequestAfterMultisig; import bisq.core.trade.protocol.tasks.TradeTask; import bisq.core.trade.protocol.tasks.buyer.BuyerFinalizesDelayedPayoutTx; import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest; import bisq.core.trade.protocol.tasks.buyer.BuyerSendsDelayedPayoutTxSignatureResponse; import bisq.core.trade.protocol.tasks.buyer.BuyerSignsDelayedPayoutTx; import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesPreparedDelayedPayoutTx; -import bisq.core.trade.protocol.tasks.maker.MakerRemovesOpenOffer; +import bisq.core.trade.protocol.tasks.maker.MaybeRemoveOpenOffer; import bisq.core.trade.protocol.tasks.maker.MakerSendsInitTradeRequestIfUnreserved; import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment; -import bisq.core.util.Validator; import bisq.network.p2p.NodeAddress; import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ResultHandler; import lombok.extern.slf4j.Slf4j; -import org.fxmisc.easybind.EasyBind; @Slf4j public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol { @@ -68,8 +60,6 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol // MakerProtocol /////////////////////////////////////////////////////////////////////////////////////////// - // TODO (woodser): these methods are duplicated with SellerAsMakerProtocol due to single inheritance - @Override public void handleInitTradeRequest(InitTradeRequest message, NodeAddress peer, @@ -99,165 +89,30 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol awaitTradeLatch(); } } - + @Override public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender) { - System.out.println(getClass().getCanonicalName() + ".handleInitMultisigRequest()"); - synchronized (trade) { - latchTrade(); - Validator.checkTradeId(processModel.getOfferId(), request); - processModel.setTradeMessage(request); - expect(anyPhase(Trade.Phase.INIT) - .with(request) - .from(sender)) - .setup(tasks( - ProcessInitMultisigRequest.class, - SendSignContractRequestAfterMultisig.class) - .using(new TradeTaskRunner(trade, - () -> { - startTimeout(TRADE_TIMEOUT); - handleTaskRunnerSuccess(sender, request); - }, - errorMessage -> { - handleTaskRunnerFault(sender, request, errorMessage); - })) - .withTimeout(TRADE_TIMEOUT)) - .executeTasks(true); - awaitTradeLatch(); - } + super.handleInitMultisigRequest(request, sender); } - + @Override public void handleSignContractRequest(SignContractRequest message, NodeAddress sender) { - System.out.println(getClass().getCanonicalName() + ".handleSignContractResponse() " + trade.getId()); - synchronized (trade) { - Validator.checkTradeId(processModel.getOfferId(), message); - 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 - }); - } - } + super.handleSignContractRequest(message, sender); } @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( - // TODO (woodser): validate request - ProcessSignContractResponse.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 response after contract signed - EasyBind.subscribe(trade.stateProperty(), state -> { - if (state == Trade.State.CONTRACT_SIGNED) new Thread(() -> handleSignContractResponse(message, sender)).start(); // process notification without trade lock - }); - } - } + super.handleSignContractResponse(message, sender); } - + @Override public void handleDepositResponse(DepositResponse response, NodeAddress sender) { - System.out.println(getClass().getCanonicalName() + ".handleDepositResponse()"); - synchronized (trade) { - latchTrade(); - Validator.checkTradeId(processModel.getOfferId(), response); - processModel.setTradeMessage(response); - expect(state(Trade.State.MAKER_SENT_PUBLISH_DEPOSIT_TX_REQUEST) - .with(response) - .from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress() - .setup(tasks( - // TODO (woodser): validate request - ProcessDepositResponse.class) - .using(new TradeTaskRunner(trade, - () -> { - startTimeout(TRADE_TIMEOUT); - handleTaskRunnerSuccess(sender, response); - }, - errorMessage -> { - handleTaskRunnerFault(sender, response, errorMessage); - })) - .withTimeout(TRADE_TIMEOUT)) - .executeTasks(true); - awaitTradeLatch(); - } + super.handleDepositResponse(response, sender); } - + @Override public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender) { - System.out.println(getClass().getCanonicalName() + ".handlePaymentAccountPayloadRequest() " + trade.getId()); - synchronized (trade) { - Validator.checkTradeId(processModel.getOfferId(), request); - if (trade.getState() == Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) { - latchTrade(); - Validator.checkTradeId(processModel.getOfferId(), request); - processModel.setTradeMessage(request); - expect(state(Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) - .with(request) - .from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress() - .setup(tasks( - // TODO (woodser): validate request - ProcessPaymentAccountPayloadRequest.class, - MakerRemovesOpenOffer.class) - .using(new TradeTaskRunner(trade, - () -> { - stopTimeout(); - this.errorMessageHandler = null; - handleTaskRunnerSuccess(sender, request); - }, - errorMessage -> { - handleTaskRunnerFault(sender, request, errorMessage); - })) - .withTimeout(TRADE_TIMEOUT)) - .executeTasks(true); - awaitTradeLatch(); - } else { - EasyBind.subscribe(trade.stateProperty(), state -> { - if (state == Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) new Thread(() -> handlePaymentAccountPayloadRequest(request, sender)).start(); // process notification without trade lock - }); - } - } + super.handlePaymentAccountPayloadRequest(request, sender); } /////////////////////////////////////////////////////////////////////////////////////////// @@ -297,7 +152,7 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol .with(message) .from(peer)) .setup(tasks( - MakerRemovesOpenOffer.class, + MaybeRemoveOpenOffer.class, BuyerProcessDelayedPayoutTxSignatureRequest.class, BuyerVerifiesPreparedDelayedPayoutTx.class, BuyerSignsDelayedPayoutTx.class, diff --git a/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java index 9597bbe537..1873ef97e5 100644 --- a/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java @@ -33,12 +33,6 @@ import bisq.core.trade.messages.SignContractRequest; import bisq.core.trade.messages.SignContractResponse; import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.protocol.tasks.ApplyFilter; -import bisq.core.trade.protocol.tasks.ProcessDepositResponse; -import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest; -import bisq.core.trade.protocol.tasks.ProcessPaymentAccountPayloadRequest; -import bisq.core.trade.protocol.tasks.ProcessSignContractRequest; -import bisq.core.trade.protocol.tasks.ProcessSignContractResponse; -import bisq.core.trade.protocol.tasks.SendSignContractRequestAfterMultisig; import bisq.core.trade.protocol.tasks.TradeTask; import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness; import bisq.core.trade.protocol.tasks.buyer.BuyerFinalizesDelayedPayoutTx; @@ -54,13 +48,11 @@ import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx; import bisq.core.trade.protocol.tasks.taker.TakerReservesTradeFunds; import bisq.core.trade.protocol.tasks.taker.TakerSendsInitTradeRequestToArbitrator; import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment; -import bisq.core.util.Validator; import bisq.network.p2p.NodeAddress; import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ResultHandler; import lombok.extern.slf4j.Slf4j; -import org.fxmisc.easybind.EasyBind; import static com.google.common.base.Preconditions.checkNotNull; @@ -86,8 +78,6 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol // Take offer /////////////////////////////////////////////////////////////////////////////////////////// - // TODO (woodser): these methods are duplicated with SellerAsTakerProtocol due to single inheritance - @Override public void onTakeOffer(TradeResultHandler tradeResultHandler, ErrorMessageHandler errorMessageHandler) { @@ -119,161 +109,27 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol @Override public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender) { - System.out.println(getClass().getCanonicalName() + ".handleInitMultisigRequest()"); - synchronized (trade) { - latchTrade(); - Validator.checkTradeId(processModel.getOfferId(), request); - processModel.setTradeMessage(request); - expect(anyPhase(Trade.Phase.INIT) - .with(request) - .from(sender)) - .setup(tasks( - ProcessInitMultisigRequest.class, - SendSignContractRequestAfterMultisig.class) - .using(new TradeTaskRunner(trade, - () -> { - startTimeout(TRADE_TIMEOUT); - handleTaskRunnerSuccess(sender, request); - }, - errorMessage -> { - handleTaskRunnerFault(sender, request, errorMessage); - })) - .withTimeout(TRADE_TIMEOUT)) - .executeTasks(true); - awaitTradeLatch(); - } + super.handleInitMultisigRequest(request, sender); } @Override public void handleSignContractRequest(SignContractRequest message, NodeAddress sender) { - System.out.println(getClass().getCanonicalName() + ".handleSignContractResponse() " + trade.getId()); - synchronized (trade) { - Validator.checkTradeId(processModel.getOfferId(), message); - 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 - }); - } - } + super.handleSignContractRequest(message, sender); } @Override public void handleSignContractResponse(SignContractResponse message, NodeAddress sender) { - System.out.println(getClass().getCanonicalName() + ".handleSignContractResponse()"); - 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( - // TODO (woodser): validate request - ProcessSignContractResponse.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 { - EasyBind.subscribe(trade.stateProperty(), state -> { - if (state == Trade.State.CONTRACT_SIGNED) new Thread(() -> handleSignContractResponse(message, sender)).start(); // process notification without trade lock - }); - } - } + super.handleSignContractResponse(message, sender); } @Override public void handleDepositResponse(DepositResponse response, NodeAddress sender) { - System.out.println(getClass().getCanonicalName() + ".handleDepositResponse()"); - synchronized (trade) { - latchTrade(); - Validator.checkTradeId(processModel.getOfferId(), response); - processModel.setTradeMessage(response); - expect(state(Trade.State.MAKER_SENT_PUBLISH_DEPOSIT_TX_REQUEST) - .with(response) - .from(sender)) - .setup(tasks( - // TODO (woodser): validate request - ProcessDepositResponse.class) - .using(new TradeTaskRunner(trade, - () -> { - startTimeout(TRADE_TIMEOUT); - handleTaskRunnerSuccess(sender, response); - }, - errorMessage -> { - handleTaskRunnerFault(sender, response, errorMessage); - })) - .withTimeout(TRADE_TIMEOUT)) - .executeTasks(true); - awaitTradeLatch(); - } + super.handleDepositResponse(response, sender); } @Override public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender) { - System.out.println(getClass().getCanonicalName() + ".handlePaymentAccountPayloadRequest()"); - synchronized (trade) { - Validator.checkTradeId(processModel.getOfferId(), request); - if (trade.getState() == Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) { - latchTrade(); - Validator.checkTradeId(processModel.getOfferId(), request); - processModel.setTradeMessage(request); - expect(state(Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) - .with(request) - .from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress() - .setup(tasks( - // TODO (woodser): validate request - ProcessPaymentAccountPayloadRequest.class) - .using(new TradeTaskRunner(trade, - () -> { - stopTimeout(); - this.errorMessageHandler = null; - tradeResultHandler.handleResult(trade); // trade is initialized - handleTaskRunnerSuccess(sender, request); - }, - errorMessage -> { - handleTaskRunnerFault(sender, request, errorMessage); - })) - .withTimeout(TRADE_TIMEOUT)) - .executeTasks(true); - awaitTradeLatch(); - } else { - EasyBind.subscribe(trade.stateProperty(), state -> { - if (state == Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) new Thread(() -> handlePaymentAccountPayloadRequest(request, sender)).start(); // process notification without trade lock - }); - } - } + super.handlePaymentAccountPayloadRequest(request, sender); } /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java index 8e50156306..e23aba99f3 100644 --- a/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java @@ -142,8 +142,8 @@ public abstract class BuyerProtocol extends DisputeProtocol { .using(new TradeTaskRunner(trade, () -> { this.errorMessageHandler = null; - resultHandler.handleResult(); handleTaskRunnerSuccess(event); + resultHandler.handleResult(); }, (errorMessage) -> { handleTaskRunnerFault(event, errorMessage); diff --git a/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java b/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java index aabf9dfb78..7935fde897 100644 --- a/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java +++ b/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java @@ -183,10 +183,6 @@ public class ProcessModel implements Model, PersistablePayload { @Setter private String multisigAddress; @Nullable - @Getter - @Setter - private boolean multisigSetupComplete; // TODO (woodser): redundant with multisigAddress existing, remove - // We want to indicate the user the state of the message delivery of the // PaymentSentMessage. As well we do an automatic re-send in case it was not ACKed yet. @@ -247,7 +243,6 @@ public class ProcessModel implements Model, PersistablePayload { Optional.ofNullable(preparedMultisigHex).ifPresent(e -> builder.setPreparedMultisigHex(preparedMultisigHex)); Optional.ofNullable(madeMultisigHex).ifPresent(e -> builder.setMadeMultisigHex(madeMultisigHex)); Optional.ofNullable(multisigAddress).ifPresent(e -> builder.setMultisigAddress(multisigAddress)); - Optional.ofNullable(multisigSetupComplete).ifPresent(e -> builder.setMultisigSetupComplete(multisigSetupComplete)); return builder.build(); } @@ -279,7 +274,6 @@ public class ProcessModel implements Model, PersistablePayload { processModel.setPreparedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getPreparedMultisigHex())); processModel.setMadeMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getMadeMultisigHex())); processModel.setMultisigAddress(ProtoUtil.stringOrNullFromProto(proto.getMultisigAddress())); - processModel.setMultisigSetupComplete(proto.getMultisigSetupComplete()); String paymentStartedMessageStateString = ProtoUtil.stringOrNullFromProto(proto.getPaymentStartedMessageState()); MessageState paymentStartedMessageState = ProtoUtil.enumFromProto(MessageState.class, paymentStartedMessageStateString); diff --git a/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java index 4cfb4f13e8..69d415ef85 100644 --- a/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java @@ -21,23 +21,17 @@ package bisq.core.trade.protocol; import bisq.core.trade.SellerAsMakerTrade; import bisq.core.trade.Trade; import bisq.core.trade.messages.PaymentSentMessage; +import bisq.core.trade.messages.SignContractRequest; +import bisq.core.trade.messages.SignContractResponse; import bisq.core.trade.messages.DepositResponse; import bisq.core.trade.messages.DepositTxMessage; import bisq.core.trade.messages.InitMultisigRequest; import bisq.core.trade.messages.InitTradeRequest; import bisq.core.trade.messages.PaymentAccountPayloadRequest; -import bisq.core.trade.messages.SignContractRequest; -import bisq.core.trade.messages.SignContractResponse; import bisq.core.trade.messages.TradeMessage; -import bisq.core.trade.protocol.tasks.ProcessDepositResponse; -import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest; import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest; -import bisq.core.trade.protocol.tasks.ProcessPaymentAccountPayloadRequest; -import bisq.core.trade.protocol.tasks.ProcessSignContractRequest; -import bisq.core.trade.protocol.tasks.ProcessSignContractResponse; -import bisq.core.trade.protocol.tasks.SendSignContractRequestAfterMultisig; import bisq.core.trade.protocol.tasks.TradeTask; -import bisq.core.trade.protocol.tasks.maker.MakerRemovesOpenOffer; +import bisq.core.trade.protocol.tasks.maker.MaybeRemoveOpenOffer; import bisq.core.trade.protocol.tasks.maker.MakerSendsInitTradeRequestIfUnreserved; import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment; import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx; @@ -45,13 +39,11 @@ import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureR import bisq.core.trade.protocol.tasks.seller.SellerSignsDelayedPayoutTx; import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerFinalizesDepositTx; import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerProcessDepositTxMessage; -import bisq.core.util.Validator; import bisq.network.p2p.NodeAddress; import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ResultHandler; import lombok.extern.slf4j.Slf4j; -import org.fxmisc.easybind.EasyBind; @Slf4j public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtocol { @@ -68,8 +60,6 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc // MakerProtocol /////////////////////////////////////////////////////////////////////////////////////////// - // TODO (woodser): these methods are duplicated with BuyerAsMakerProtocol due to single inheritance - @Override public void handleInitTradeRequest(InitTradeRequest message, NodeAddress peer, @@ -99,164 +89,30 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc awaitTradeLatch(); } } - + @Override public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender) { - System.out.println(getClass().getCanonicalName() + ".handleInitMultisigRequest()"); - synchronized (trade) { - latchTrade(); - Validator.checkTradeId(processModel.getOfferId(), request); - processModel.setTradeMessage(request); - expect(anyPhase(Trade.Phase.INIT) - .with(request) - .from(sender)) - .setup(tasks( - ProcessInitMultisigRequest.class, - SendSignContractRequestAfterMultisig.class) - .using(new TradeTaskRunner(trade, - () -> { - startTimeout(TRADE_TIMEOUT); - handleTaskRunnerSuccess(sender, request); - }, - errorMessage -> { - handleTaskRunnerFault(sender, request, errorMessage); - })) - .withTimeout(TRADE_TIMEOUT)) - .executeTasks(true); - awaitTradeLatch(); - } + super.handleInitMultisigRequest(request, sender); } - + @Override public void handleSignContractRequest(SignContractRequest message, NodeAddress sender) { - System.out.println(getClass().getCanonicalName() + ".handleSignContractResponse() " + trade.getId()); - synchronized (trade) { - Validator.checkTradeId(processModel.getOfferId(), message); - 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 - }); - } - } + super.handleSignContractRequest(message, sender); } @Override public void handleSignContractResponse(SignContractResponse message, NodeAddress sender) { - System.out.println(getClass().getCanonicalName() + ".handleSignContractResponse()"); - 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( - // TODO (woodser): validate request - ProcessSignContractResponse.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 { - EasyBind.subscribe(trade.stateProperty(), state -> { - if (state == Trade.State.CONTRACT_SIGNED) new Thread(() -> handleSignContractResponse(message, sender)).start(); // process notification without trade lock - }); - } - } + super.handleSignContractResponse(message, sender); } - + @Override public void handleDepositResponse(DepositResponse response, NodeAddress sender) { - System.out.println(getClass().getCanonicalName() + ".handleDepositResponse()"); - synchronized (trade) { - latchTrade(); - Validator.checkTradeId(processModel.getOfferId(), response); - processModel.setTradeMessage(response); - expect(state(Trade.State.MAKER_SENT_PUBLISH_DEPOSIT_TX_REQUEST) - .with(response) - .from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress() - .setup(tasks( - // TODO (woodser): validate request - ProcessDepositResponse.class) - .using(new TradeTaskRunner(trade, - () -> { - startTimeout(TRADE_TIMEOUT); - handleTaskRunnerSuccess(sender, response); - }, - errorMessage -> { - handleTaskRunnerFault(sender, response, errorMessage); - })) - .withTimeout(TRADE_TIMEOUT)) - .executeTasks(true); - awaitTradeLatch(); - } + super.handleDepositResponse(response, sender); } - + @Override public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender) { - System.out.println(getClass().getCanonicalName() + ".handlePaymentAccountPayloadRequest()"); - synchronized (trade) { - Validator.checkTradeId(processModel.getOfferId(), request); - if (trade.getState() == Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) { - latchTrade(); - Validator.checkTradeId(processModel.getOfferId(), request); - processModel.setTradeMessage(request); - expect(state(Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) - .with(request) - .from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress() - .setup(tasks( - // TODO (woodser): validate request - ProcessPaymentAccountPayloadRequest.class, - MakerRemovesOpenOffer.class) - .using(new TradeTaskRunner(trade, - () -> { - stopTimeout(); - this.errorMessageHandler = null; - handleTaskRunnerSuccess(sender, request); - }, - errorMessage -> { - handleTaskRunnerFault(sender, request, errorMessage); - })) - .withTimeout(TRADE_TIMEOUT)) - .executeTasks(true); - awaitTradeLatch(); - } else { - EasyBind.subscribe(trade.stateProperty(), state -> { - if (state == Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) new Thread(() -> handlePaymentAccountPayloadRequest(request, sender)).start(); // process notification without trade lock - }); - } - } + super.handlePaymentAccountPayloadRequest(request, sender); } /////////////////////////////////////////////////////////////////////////////////////////// @@ -301,7 +157,7 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc .with(message) .from(peer)) .setup(tasks( - MakerRemovesOpenOffer.class, + MaybeRemoveOpenOffer.class, SellerAsMakerProcessDepositTxMessage.class, SellerAsMakerFinalizesDepositTx.class, SellerCreatesDelayedPayoutTx.class, diff --git a/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java index caa6be17dc..2aa35196b1 100644 --- a/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java @@ -23,20 +23,14 @@ import bisq.core.trade.SellerAsTakerTrade; import bisq.core.trade.Trade; import bisq.core.trade.handlers.TradeResultHandler; import bisq.core.trade.messages.PaymentSentMessage; +import bisq.core.trade.messages.SignContractRequest; +import bisq.core.trade.messages.SignContractResponse; import bisq.core.trade.messages.DepositResponse; import bisq.core.trade.messages.InitMultisigRequest; import bisq.core.trade.messages.InputsForDepositTxResponse; import bisq.core.trade.messages.PaymentAccountPayloadRequest; -import bisq.core.trade.messages.SignContractRequest; -import bisq.core.trade.messages.SignContractResponse; import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.protocol.tasks.ApplyFilter; -import bisq.core.trade.protocol.tasks.ProcessDepositResponse; -import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest; -import bisq.core.trade.protocol.tasks.ProcessPaymentAccountPayloadRequest; -import bisq.core.trade.protocol.tasks.ProcessSignContractRequest; -import bisq.core.trade.protocol.tasks.ProcessSignContractResponse; -import bisq.core.trade.protocol.tasks.SendSignContractRequestAfterMultisig; import bisq.core.trade.protocol.tasks.TradeTask; import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness; import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx; @@ -48,14 +42,12 @@ import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx; import bisq.core.trade.protocol.tasks.taker.TakerReservesTradeFunds; import bisq.core.trade.protocol.tasks.taker.TakerSendsInitTradeRequestToArbitrator; import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment; -import bisq.core.util.Validator; import bisq.network.p2p.NodeAddress; import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ResultHandler; import lombok.extern.slf4j.Slf4j; -import org.fxmisc.easybind.EasyBind; import static com.google.common.base.Preconditions.checkNotNull; @@ -79,8 +71,6 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc // Take offer /////////////////////////////////////////////////////////////////////////////////////////// - // TODO (woodser): these methods are duplicated with BuyerAsTakerProtocol due to single inheritance - @Override public void onTakeOffer(TradeResultHandler tradeResultHandler, ErrorMessageHandler errorMessageHandler) { @@ -112,164 +102,29 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc @Override public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender) { - System.out.println(getClass().getCanonicalName() + ".handleInitMultisigRequest()"); - synchronized (trade) { - latchTrade(); - Validator.checkTradeId(processModel.getOfferId(), request); - processModel.setTradeMessage(request); - expect(anyPhase(Trade.Phase.INIT) - .with(request) - .from(sender)) - .setup(tasks( - ProcessInitMultisigRequest.class, - SendSignContractRequestAfterMultisig.class) - .using(new TradeTaskRunner(trade, - () -> { - startTimeout(TRADE_TIMEOUT); - handleTaskRunnerSuccess(sender, request); - }, - errorMessage -> { - handleTaskRunnerFault(sender, request, errorMessage); - })) - .withTimeout(TRADE_TIMEOUT)) - .executeTasks(true); - awaitTradeLatch(); - } + super.handleInitMultisigRequest(request, sender); } @Override public void handleSignContractRequest(SignContractRequest message, NodeAddress sender) { - System.out.println(getClass().getCanonicalName() + ".handleSignContractResponse() " + trade.getId()); - synchronized (trade) { - Validator.checkTradeId(processModel.getOfferId(), message); - 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 - }); - } - } + super.handleSignContractRequest(message, sender); } @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( - // TODO (woodser): validate request - ProcessSignContractResponse.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 { - EasyBind.subscribe(trade.stateProperty(), state -> { - if (state == Trade.State.CONTRACT_SIGNED) new Thread(() -> handleSignContractResponse(message, sender)).start(); // process notification without trade lock - }); - } - } + super.handleSignContractResponse(message, sender); } @Override public void handleDepositResponse(DepositResponse response, NodeAddress sender) { - System.out.println(getClass().getCanonicalName() + ".handleDepositResponse()"); - synchronized (trade) { - latchTrade(); - Validator.checkTradeId(processModel.getOfferId(), response); - processModel.setTradeMessage(response); - expect(state(Trade.State.MAKER_SENT_PUBLISH_DEPOSIT_TX_REQUEST) - .with(response) - .from(sender)) - .setup(tasks( - // TODO (woodser): validate request - ProcessDepositResponse.class) - .using(new TradeTaskRunner(trade, - () -> { - startTimeout(TRADE_TIMEOUT); - handleTaskRunnerSuccess(sender, response); - }, - errorMessage -> { - handleTaskRunnerFault(sender, response, errorMessage); - })) - .withTimeout(TRADE_TIMEOUT)) - .executeTasks(true); - awaitTradeLatch(); - } + super.handleDepositResponse(response, sender); } @Override public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender) { - System.out.println(getClass().getCanonicalName() + ".handlePaymentAccountPayloadRequest() " + trade.getId()); - synchronized (trade) { - Validator.checkTradeId(processModel.getOfferId(), request); - if (trade.getState() == Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) { - latchTrade(); - Validator.checkTradeId(processModel.getOfferId(), request); - processModel.setTradeMessage(request); - expect(state(Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) - .with(request) - .from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress() - .setup(tasks( - // TODO (woodser): validate request - ProcessPaymentAccountPayloadRequest.class) - .using(new TradeTaskRunner(trade, - () -> { - stopTimeout(); - this.errorMessageHandler = null; - tradeResultHandler.handleResult(trade); // trade is initialized - handleTaskRunnerSuccess(sender, request); - }, - errorMessage -> { - handleTaskRunnerFault(sender, request, errorMessage); - })) - .withTimeout(TRADE_TIMEOUT)) - .executeTasks(true); - awaitTradeLatch(); - } else { - EasyBind.subscribe(trade.stateProperty(), state -> { - if (state == Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) new Thread(() -> handlePaymentAccountPayloadRequest(request, sender)).start(); // process notification without trade lock - }); - } - } + super.handlePaymentAccountPayloadRequest(request, sender); } - /////////////////////////////////////////////////////////////////////////////////////////// // Incoming message when buyer has clicked payment started button /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java index d2b7fac1e5..949f92d56f 100644 --- a/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java @@ -136,13 +136,13 @@ public abstract class SellerProtocol extends DisputeProtocol { getVerifyPeersFeePaymentClass(), SellerPreparesPaymentReceivedMessage.class, SellerSendsPaymentReceivedMessage.class) - .using(new TradeTaskRunner(trade, () -> { - this.errorMessageHandler = null; - resultHandler.handleResult(); - handleTaskRunnerSuccess(event); - }, (errorMessage) -> { - handleTaskRunnerFault(event, errorMessage); - }))) + .using(new TradeTaskRunner(trade, () -> { + this.errorMessageHandler = null; + handleTaskRunnerSuccess(event); + resultHandler.handleResult(); + }, (errorMessage) -> { + handleTaskRunnerFault(event, errorMessage); + }))) .run(() -> trade.setState(Trade.State.SELLER_CONFIRMED_IN_UI_PAYMENT_RECEIPT)) .executeTasks(); awaitTradeLatch(); diff --git a/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java b/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java index a3d0568485..fba55daa80 100644 --- a/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java @@ -23,12 +23,22 @@ import bisq.core.trade.TradeManager; import bisq.core.trade.TradeUtils; import bisq.core.trade.handlers.TradeResultHandler; import bisq.core.trade.messages.PaymentSentMessage; +import bisq.core.trade.messages.DepositResponse; import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage; import bisq.core.trade.messages.InitMultisigRequest; +import bisq.core.trade.messages.PaymentAccountPayloadRequest; import bisq.core.trade.messages.SignContractRequest; +import bisq.core.trade.messages.SignContractResponse; import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.messages.UpdateMultisigRequest; +import bisq.core.trade.protocol.tasks.MaybeSendSignContractRequest; +import bisq.core.trade.protocol.tasks.ProcessDepositResponse; +import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest; +import bisq.core.trade.protocol.tasks.ProcessPaymentAccountPayloadRequest; +import bisq.core.trade.protocol.tasks.ProcessSignContractRequest; +import bisq.core.trade.protocol.tasks.ProcessSignContractResponse; import bisq.core.trade.protocol.tasks.ProcessUpdateMultisigRequest; +import bisq.core.trade.protocol.tasks.maker.MaybeRemoveOpenOffer; import bisq.core.util.Validator; import bisq.network.p2p.AckMessage; @@ -54,7 +64,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import lombok.extern.slf4j.Slf4j; - +import org.fxmisc.easybind.EasyBind; import javax.annotation.Nullable; @Slf4j @@ -213,8 +223,162 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D /////////////////////////////////////////////////////////////////////////////////////////// protected abstract void onTradeMessage(TradeMessage message, NodeAddress peer); - public abstract void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress peer); - public abstract void handleSignContractRequest(SignContractRequest request, NodeAddress peer); + + public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender) { + System.out.println(getClass().getCanonicalName() + ".handleInitMultisigRequest()"); + synchronized (trade) { + latchTrade(); + Validator.checkTradeId(processModel.getOfferId(), request); + processModel.setTradeMessage(request); + expect(anyPhase(Trade.Phase.INIT) + .with(request) + .from(sender)) + .setup(tasks( + ProcessInitMultisigRequest.class, + MaybeSendSignContractRequest.class) + .using(new TradeTaskRunner(trade, + () -> { + startTimeout(TRADE_TIMEOUT); + handleTaskRunnerSuccess(sender, request); + }, + errorMessage -> { + handleTaskRunnerFault(sender, request, errorMessage); + })) + .withTimeout(TRADE_TIMEOUT)) + .executeTasks(true); + awaitTradeLatch(); + } + } + + public void handleSignContractRequest(SignContractRequest message, NodeAddress sender) { + System.out.println(getClass().getCanonicalName() + ".handleSignContractResponse() " + trade.getId()); + synchronized (trade) { + Validator.checkTradeId(processModel.getOfferId(), message); + if (trade.getState() == Trade.State.MULTISIG_COMPLETED || trade.getState() == Trade.State.CONTRACT_SIGNATURE_REQUESTED) { + latchTrade(); + Validator.checkTradeId(processModel.getOfferId(), message); + processModel.setTradeMessage(message); + expect(anyState(Trade.State.MULTISIG_COMPLETED, 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 multisig created + EasyBind.subscribe(trade.stateProperty(), state -> { + if (state == Trade.State.MULTISIG_COMPLETED) new Thread(() -> handleSignContractRequest(message, sender)).start(); // process notification without trade lock + }); + } + } + } + + 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( + // TODO (woodser): validate request + ProcessSignContractResponse.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 response after contract signed + EasyBind.subscribe(trade.stateProperty(), state -> { + if (state == Trade.State.CONTRACT_SIGNED) new Thread(() -> handleSignContractResponse(message, sender)).start(); // process notification without trade lock + }); + } + } + } + + public void handleDepositResponse(DepositResponse response, NodeAddress sender) { + System.out.println(getClass().getCanonicalName() + ".handleDepositResponse()"); + synchronized (trade) { + latchTrade(); + Validator.checkTradeId(processModel.getOfferId(), response); + processModel.setTradeMessage(response); + expect(state(Trade.State.MAKER_SENT_PUBLISH_DEPOSIT_TX_REQUEST) + .with(response) + .from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress() + .setup(tasks( + // TODO (woodser): validate request + ProcessDepositResponse.class) + .using(new TradeTaskRunner(trade, + () -> { + startTimeout(TRADE_TIMEOUT); + handleTaskRunnerSuccess(sender, response); + }, + errorMessage -> { + handleTaskRunnerFault(sender, response, errorMessage); + })) + .withTimeout(TRADE_TIMEOUT)) + .executeTasks(true); + awaitTradeLatch(); + } + } + + public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender) { + System.out.println(getClass().getCanonicalName() + ".handlePaymentAccountPayloadRequest()"); + synchronized (trade) { + Validator.checkTradeId(processModel.getOfferId(), request); + if (trade.getState() == Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) { + latchTrade(); + Validator.checkTradeId(processModel.getOfferId(), request); + processModel.setTradeMessage(request); + expect(state(Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) + .with(request) + .from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress() + .setup(tasks( + // TODO (woodser): validate request + ProcessPaymentAccountPayloadRequest.class, + MaybeRemoveOpenOffer.class) + .using(new TradeTaskRunner(trade, + () -> { + stopTimeout(); + this.errorMessageHandler = null; + handleTaskRunnerSuccess(sender, request); + if (tradeResultHandler != null) tradeResultHandler.handleResult(trade); // trade is initialized + }, + errorMessage -> { + handleTaskRunnerFault(sender, request, errorMessage); + })) + .withTimeout(TRADE_TIMEOUT)) + .executeTasks(true); + awaitTradeLatch(); + } else { + EasyBind.subscribe(trade.stateProperty(), state -> { + if (state == Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) new Thread(() -> handlePaymentAccountPayloadRequest(request, sender)).start(); // process notification without trade lock + }); + } + } + } // TODO (woodser): update to use fluent for consistency public void handleUpdateMultisigRequest(UpdateMultisigRequest message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) { @@ -237,7 +401,6 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D awaitTradeLatch(); } - /////////////////////////////////////////////////////////////////////////////////////////// // FluentProtocol /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/SendSignContractRequestAfterMultisig.java b/core/src/main/java/bisq/core/trade/protocol/tasks/MaybeSendSignContractRequest.java similarity index 91% rename from core/src/main/java/bisq/core/trade/protocol/tasks/SendSignContractRequestAfterMultisig.java rename to core/src/main/java/bisq/core/trade/protocol/tasks/MaybeSendSignContractRequest.java index 477b73e6a4..2ed19d8d49 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/SendSignContractRequestAfterMultisig.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/MaybeSendSignContractRequest.java @@ -20,6 +20,7 @@ package bisq.core.trade.protocol.tasks; import bisq.common.app.Version; import bisq.common.taskrunner.TaskRunner; import bisq.core.btc.model.XmrAddressEntry; +import bisq.core.trade.ArbitratorTrade; import bisq.core.trade.Trade; import bisq.core.trade.Trade.State; import bisq.core.trade.messages.SignContractRequest; @@ -32,13 +33,13 @@ import monero.wallet.model.MoneroTxWallet; // TODO (woodser): separate classes for deposit tx creation and contract request, or combine into ProcessInitMultisigRequest @Slf4j -public class SendSignContractRequestAfterMultisig extends TradeTask { +public class MaybeSendSignContractRequest extends TradeTask { private boolean ack1 = false; // TODO (woodser) these represent onArrived(), not the ack private boolean ack2 = false; @SuppressWarnings({"unused"}) - public SendSignContractRequestAfterMultisig(TaskRunner taskHandler, Trade trade) { + public MaybeSendSignContractRequest(TaskRunner taskHandler, Trade trade) { super(taskHandler, trade); } @@ -46,11 +47,17 @@ public class SendSignContractRequestAfterMultisig extends TradeTask { protected void run() { try { runInterceptHook(); + + // skip if arbitrator + if (trade instanceof ArbitratorTrade) { + complete(); + return; + } // skip if multisig wallet not complete - if (!processModel.isMultisigSetupComplete()) { + if (processModel.getMultisigAddress() == null) { complete(); - return; // TODO: woodser: this does not ack original request? + return; } // skip if deposit tx already created @@ -71,7 +78,7 @@ public class SendSignContractRequestAfterMultisig extends TradeTask { // TODO (woodser): save frozen key images and unfreeze if trade fails before deposited to multisig // save process state - processModel.setDepositTxXmr(depositTx); + processModel.setDepositTxXmr(depositTx); // TODO: trade.getSelf().setDepositTx() trade.getSelf().setDepositTxHash(depositTx.getHash()); trade.getSelf().setPayoutAddressString(trade.getXmrWalletService().getAddressEntry(processModel.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).get().getAddressString()); // TODO (woodser): allow custom payout address? diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessInitMultisigRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessInitMultisigRequest.java index bb01c455e5..dfe516ff6b 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessInitMultisigRequest.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessInitMultisigRequest.java @@ -23,9 +23,7 @@ import bisq.core.trade.MakerTrade; import bisq.core.trade.TakerTrade; import bisq.core.trade.Trade; import bisq.core.trade.messages.InitMultisigRequest; -import bisq.core.trade.protocol.TradeListener; import bisq.core.trade.protocol.TradingPeer; -import bisq.network.p2p.AckMessage; import bisq.network.p2p.NodeAddress; import bisq.network.p2p.SendDirectMessageListener; @@ -92,8 +90,9 @@ public class ProcessInitMultisigRequest extends TradeTask { log.info("Preparing multisig wallet for trade {}", trade.getId()); multisigWallet = xmrWalletService.createMultisigWallet(trade.getId()); processModel.setPreparedMultisigHex(multisigWallet.prepareMultisig()); + trade.setStateIfValidTransitionTo(Trade.State.MULTISIG_PREPARED); updateParticipants = true; - } else if (!processModel.isMultisigSetupComplete()) { + } else if (processModel.getMultisigAddress() == null) { multisigWallet = xmrWalletService.getMultisigWallet(trade.getId()); } @@ -103,15 +102,16 @@ public class ProcessInitMultisigRequest extends TradeTask { log.info("Making multisig wallet for trade {}", trade.getId()); MoneroMultisigInitResult result = multisigWallet.makeMultisig(Arrays.asList(peers[0].getPreparedMultisigHex(), peers[1].getPreparedMultisigHex()), 2, xmrWalletService.getWalletPassword()); // TODO (woodser): xmrWalletService.makeMultisig(tradeId, multisigHexes, threshold)? processModel.setMadeMultisigHex(result.getMultisigHex()); + trade.setStateIfValidTransitionTo(Trade.State.MULTISIG_MADE); updateParticipants = true; } // exchange multisig keys if applicable - if (!processModel.isMultisigSetupComplete() && peers[0].getMadeMultisigHex() != null && peers[1].getMadeMultisigHex() != null) { + if (processModel.getMultisigAddress() == null && peers[0].getMadeMultisigHex() != null && peers[1].getMadeMultisigHex() != null) { log.info("Exchanging multisig wallet keys for trade {}", trade.getId()); multisigWallet.exchangeMultisigKeys(Arrays.asList(peers[0].getMadeMultisigHex(), peers[1].getMadeMultisigHex()), xmrWalletService.getWalletPassword()); - processModel.setMultisigSetupComplete(true); // TODO: (woodser): remove this field? processModel.setMultisigAddress(multisigWallet.getPrimaryAddress()); + trade.setStateIfValidTransitionTo(Trade.State.MULTISIG_COMPLETED); processModel.getProvider().getXmrWalletService().closeMultisigWallet(trade.getId()); // save and close multisig wallet once it's created } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerRemovesOpenOffer.java b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MaybeRemoveOpenOffer.java similarity index 77% rename from core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerRemovesOpenOffer.java rename to core/src/main/java/bisq/core/trade/protocol/tasks/maker/MaybeRemoveOpenOffer.java index 527f71c09c..1352953bb3 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerRemovesOpenOffer.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MaybeRemoveOpenOffer.java @@ -17,6 +17,7 @@ package bisq.core.trade.protocol.tasks.maker; +import bisq.core.trade.MakerTrade; import bisq.core.trade.Trade; import bisq.core.trade.protocol.tasks.TradeTask; @@ -27,8 +28,8 @@ import lombok.extern.slf4j.Slf4j; import static com.google.common.base.Preconditions.checkNotNull; @Slf4j -public class MakerRemovesOpenOffer extends TradeTask { - public MakerRemovesOpenOffer(TaskRunner taskHandler, Trade trade) { +public class MaybeRemoveOpenOffer extends TradeTask { + public MaybeRemoveOpenOffer(TaskRunner taskHandler, Trade trade) { super(taskHandler, trade); } @@ -36,8 +37,10 @@ public class MakerRemovesOpenOffer extends TradeTask { protected void run() { try { runInterceptHook(); - - processModel.getOpenOfferManager().closeOpenOffer(checkNotNull(trade.getOffer())); + + if (trade instanceof MakerTrade) { + processModel.getOpenOfferManager().closeOpenOffer(checkNotNull(trade.getOffer())); + } complete(); } catch (Throwable t) { diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcTradesService.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcTradesService.java index cd3e4cb193..7da9e6ddc1 100644 --- a/daemon/src/main/java/bisq/daemon/grpc/GrpcTradesService.java +++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcTradesService.java @@ -128,8 +128,7 @@ class GrpcTradesService extends TradesImplBase { responseObserver.onCompleted(); }, errorMessage -> { - if (!errorMessageHandler.isErrorHandled()) - errorMessageHandler.handleErrorMessage(errorMessage); + if (!errorMessageHandler.isErrorHandled()) errorMessageHandler.handleErrorMessage(errorMessage); }); } catch (Throwable cause) { cause.printStackTrace(); @@ -169,7 +168,7 @@ class GrpcTradesService extends TradesImplBase { responseObserver.onCompleted(); }, errorMessage -> { - if (!errorMessageHandler.isErrorHandled()) errorMessageHandler.handleErrorMessage(errorMessage); + if (!errorMessageHandler.isErrorHandled()) errorMessageHandler.handleErrorMessage(errorMessage); }); } catch (Throwable cause) { cause.printStackTrace(); diff --git a/desktop/src/main/java/bisq/desktop/main/debug/DebugView.java b/desktop/src/main/java/bisq/desktop/main/debug/DebugView.java index 8f8fcc7297..befa94773a 100644 --- a/desktop/src/main/java/bisq/desktop/main/debug/DebugView.java +++ b/desktop/src/main/java/bisq/desktop/main/debug/DebugView.java @@ -43,7 +43,7 @@ import bisq.core.trade.protocol.tasks.buyer_as_maker.BuyerAsMakerSendsInputsForD import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerCreatesDepositTxInputs; import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSendsDepositTxMessage; import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSignsDepositTx; -import bisq.core.trade.protocol.tasks.maker.MakerRemovesOpenOffer; +import bisq.core.trade.protocol.tasks.maker.MaybeRemoveOpenOffer; import bisq.core.trade.protocol.tasks.maker.MakerSetsLockTime; import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment; import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx; @@ -157,7 +157,7 @@ public class DebugView extends InitializableView { BuyerAsMakerSendsInputsForDepositTxResponse.class, BuyerProcessDelayedPayoutTxSignatureRequest.class, - MakerRemovesOpenOffer.class, + MaybeRemoveOpenOffer.class, BuyerVerifiesPreparedDelayedPayoutTx.class, BuyerSignsDelayedPayoutTx.class, BuyerSendsDelayedPayoutTxSignatureResponse.class, @@ -215,7 +215,7 @@ public class DebugView extends InitializableView { SellerAsMakerSendsInputsForDepositTxResponse.class, //SellerAsMakerProcessDepositTxMessage.class, - MakerRemovesOpenOffer.class, + MaybeRemoveOpenOffer.class, SellerAsMakerFinalizesDepositTx.class, SellerCreatesDelayedPayoutTx.class, SellerSendDelayedPayoutTxSignatureRequest.class, diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java index 207e379284..eacab9fbfd 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java @@ -403,6 +403,7 @@ public class PendingTradesViewModel extends ActivatableWithDataModel