diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/Trade.java index 55d1e4bb05..e65d405c56 100644 --- a/core/src/main/java/bisq/core/trade/Trade.java +++ b/core/src/main/java/bisq/core/trade/Trade.java @@ -770,14 +770,15 @@ public abstract class Trade implements Tradable, Model { } /** - * Verify and sign a payout tx. + * Verify a payout tx. * * @param payoutTxHex is the payout tx hex to verify - * @return String the signed payout tx hex + * @param sign signs the payout tx if true + * @param publish publishes the signed payout tx if true */ - public void verifySignAndPublishPayoutTx(String payoutTxHex) { + public void verifyPayoutTx(String payoutTxHex, boolean sign, boolean publish) { log.info("Verifying payout tx"); - + // gather relevant info XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); MoneroWallet multisigWallet = walletService.getMultisigWallet(getId()); @@ -787,9 +788,9 @@ public abstract class Trade implements Tradable, Model { BigInteger tradeAmount = ParsingUtils.coinToAtomicUnits(getAmount()); // parse payout tx - MoneroTxSet parsedTxSet = multisigWallet.describeTxSet(new MoneroTxSet().setMultisigTxHex(payoutTxHex)); - if (parsedTxSet.getTxs() == null || parsedTxSet.getTxs().size() != 1) throw new RuntimeException("Bad payout tx"); // TODO (woodser): test nack - MoneroTxWallet payoutTx = parsedTxSet.getTxs().get(0); + MoneroTxSet describedTxSet = multisigWallet.describeTxSet(new MoneroTxSet().setMultisigTxHex(payoutTxHex)); + if (describedTxSet.getTxs() == null || describedTxSet.getTxs().size() != 1) throw new RuntimeException("Bad payout tx"); // TODO (woodser): test nack + MoneroTxWallet payoutTx = describedTxSet.getTxs().get(0); // verify payout tx has exactly 2 destinations if (payoutTx.getOutgoingTransfer() == null || payoutTx.getOutgoingTransfer().getDestinations() == null || payoutTx.getOutgoingTransfer().getDestinations().size() != 2) throw new RuntimeException("Payout tx does not have exactly two destinations"); @@ -819,21 +820,25 @@ public abstract class Trade implements Tradable, Model { if (!sellerPayoutDestination.getAmount().equals(expectedSellerPayout)) throw new RuntimeException("Seller destination amount is not deposit amount - trade amount - 1/2 tx costs, " + sellerPayoutDestination.getAmount() + " vs " + expectedSellerPayout); // TODO (woodser): verify fee is reasonable (e.g. within 2x of fee estimate tx) - + // sign payout tx - MoneroMultisigSignResult result = multisigWallet.signMultisigTxHex(payoutTxHex); - if (result.getSignedMultisigTxHex() == null) throw new RuntimeException("Error signing payout tx"); - String signedPayoutTxHex = result.getSignedMultisigTxHex(); - - // submit payout tx - multisigWallet.submitMultisigTxHex(signedPayoutTxHex); - walletService.closeMultisigWallet(getId()); - + if (sign) { + MoneroMultisigSignResult result = multisigWallet.signMultisigTxHex(payoutTxHex); + if (result.getSignedMultisigTxHex() == null) throw new RuntimeException("Error signing payout tx"); + payoutTxHex = result.getSignedMultisigTxHex(); + } + // update trade state - getSelf().setPayoutTxHex(signedPayoutTxHex); - setPayoutTx(parsedTxSet.getTxs().get(0)); - setPayoutTxId(parsedTxSet.getTxs().get(0).getHash()); - setState(isBuyer() ? Trade.State.BUYER_PUBLISHED_PAYOUT_TX : Trade.State.SELLER_PUBLISHED_PAYOUT_TX); + getSelf().setPayoutTxHex(payoutTxHex); + setPayoutTx(describedTxSet.getTxs().get(0)); + setPayoutTxId(describedTxSet.getTxs().get(0).getHash()); + + // submit payout tx + if (publish) { + multisigWallet.submitMultisigTxHex(payoutTxHex); + setState(isArbitrator() ? Trade.State.WITHDRAW_COMPLETED : isBuyer() ? Trade.State.BUYER_PUBLISHED_PAYOUT_TX : Trade.State.SELLER_PUBLISHED_PAYOUT_TX); + } + walletService.closeMultisigWallet(getId()); } /** @@ -1114,6 +1119,10 @@ public abstract class Trade implements Tradable, Model { // Getter /////////////////////////////////////////////////////////////////////////////////////////// + public boolean isArbitrator() { + return this instanceof ArbitratorTrade; + } + public boolean isBuyer() { return getBuyer() == getSelf(); } diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index d33051aaea..28d0db3935 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -541,7 +541,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi //System.out.println("TradeManager trade.setTradingPeerNodeAddress(): " + sender); //trade.setTradingPeerNodeAddress(sender); // TODO (woodser): what if maker's address changes while offer open, or taker's address changes after multisig deposit available? need to verify and update. see OpenOfferManager.maybeUpdatePersistedOffers() - trade.setArbitratorPubKeyRing(user.getAcceptedArbitratorByAddress(sender).getPubKeyRing()); + trade.setArbitratorPubKeyRing(arbitrator.getPubKeyRing()); trade.setMakerPubKeyRing(trade.getOffer().getPubKeyRing()); initTradeAndProtocol(trade, getTradeProtocol(trade)); trade.getSelf().setReserveTxHash(openOffer.getReserveTxHash()); // TODO (woodser): initialize in initTradeAndProtocol? diff --git a/core/src/main/java/bisq/core/trade/TradeUtils.java b/core/src/main/java/bisq/core/trade/TradeUtils.java index cec9f24ca2..808c4b9df8 100644 --- a/core/src/main/java/bisq/core/trade/TradeUtils.java +++ b/core/src/main/java/bisq/core/trade/TradeUtils.java @@ -74,8 +74,8 @@ public class TradeUtils { /** * Check if the arbitrator signature for an offer is valid. * - * @param arbitrator is the possible original arbitrator * @param signedOfferPayload is a signed offer payload + * @param arbitrator is the possible original arbitrator * @return true if the arbitrator's signature is valid for the offer */ public static boolean isArbitratorSignatureValid(OfferPayload signedOfferPayload, Arbitrator arbitrator) { diff --git a/core/src/main/java/bisq/core/trade/messages/PayoutTxPublishedMessage.java b/core/src/main/java/bisq/core/trade/messages/PayoutTxPublishedMessage.java index 596e11a4e7..97c12e1ccd 100644 --- a/core/src/main/java/bisq/core/trade/messages/PayoutTxPublishedMessage.java +++ b/core/src/main/java/bisq/core/trade/messages/PayoutTxPublishedMessage.java @@ -38,7 +38,7 @@ import javax.annotation.Nullable; @Value public final class PayoutTxPublishedMessage extends TradeMailboxMessage { private final NodeAddress senderNodeAddress; - private final String payoutTxHex; + private final String signedPayoutTxHex; // Added in v1.4.0 @Nullable @@ -47,13 +47,13 @@ public final class PayoutTxPublishedMessage extends TradeMailboxMessage { public PayoutTxPublishedMessage(String tradeId, NodeAddress senderNodeAddress, @Nullable SignedWitness signedWitness, - String payoutTxHex) { + String signedPayoutTxHex) { this(tradeId, senderNodeAddress, signedWitness, UUID.randomUUID().toString(), Version.getP2PMessageVersion(), - payoutTxHex); + signedPayoutTxHex); } @@ -66,11 +66,11 @@ public final class PayoutTxPublishedMessage extends TradeMailboxMessage { @Nullable SignedWitness signedWitness, String uid, String messageVersion, - String payoutTxHex) { + String signedPayoutTxHex) { super(messageVersion, tradeId, uid); this.senderNodeAddress = senderNodeAddress; this.signedWitness = signedWitness; - this.payoutTxHex = payoutTxHex; + this.signedPayoutTxHex = signedPayoutTxHex; } @Override @@ -79,7 +79,7 @@ public final class PayoutTxPublishedMessage extends TradeMailboxMessage { .setTradeId(tradeId) .setSenderNodeAddress(senderNodeAddress.toProtoMessage()) .setUid(uid) - .setPayoutTxHex(payoutTxHex); + .setSignedPayoutTxHex(signedPayoutTxHex); Optional.ofNullable(signedWitness).ifPresent(signedWitness -> builder.setSignedWitness(signedWitness.toProtoSignedWitness())); return getNetworkEnvelopeBuilder().setPayoutTxPublishedMessage(builder).build(); } @@ -96,7 +96,7 @@ public final class PayoutTxPublishedMessage extends TradeMailboxMessage { signedWitness, proto.getUid(), messageVersion, - proto.getPayoutTxHex()); + proto.getSignedPayoutTxHex()); } @Override @@ -104,7 +104,7 @@ public final class PayoutTxPublishedMessage extends TradeMailboxMessage { return "PayoutTxPublishedMessage{" + "\n senderNodeAddress=" + senderNodeAddress + ",\n signedWitness=" + signedWitness + - ",\n payoutTxHex=" + payoutTxHex + + ",\n signedPayoutTxHex=" + signedPayoutTxHex + "\n} " + super.toString(); } } 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 a1f33c6cd9..ef04322837 100644 --- a/core/src/main/java/bisq/core/trade/protocol/ArbitratorProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/ArbitratorProtocol.java @@ -1,5 +1,6 @@ package bisq.core.trade.protocol; +import bisq.common.handlers.ErrorMessageHandler; import bisq.core.trade.ArbitratorTrade; import bisq.core.trade.Trade; import bisq.core.trade.messages.DepositRequest; @@ -7,17 +8,18 @@ import bisq.core.trade.messages.DepositResponse; import bisq.core.trade.messages.InitTradeRequest; import bisq.core.trade.messages.PaymentAccountKeyRequest; import bisq.core.trade.messages.SignContractResponse; +import bisq.core.trade.messages.PayoutTxPublishedMessage; +import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.protocol.FluentProtocol.Condition; import bisq.core.trade.protocol.tasks.ApplyFilter; -import bisq.core.trade.protocol.tasks.ArbitratorSendsInitTradeOrMultisigRequests; import bisq.core.trade.protocol.tasks.ArbitratorProcessesDepositRequest; import bisq.core.trade.protocol.tasks.ArbitratorProcessesPaymentAccountKeyRequest; import bisq.core.trade.protocol.tasks.ArbitratorProcessesReserveTx; +import bisq.core.trade.protocol.tasks.ArbitratorProcessPayoutTxPublishedMessage; +import bisq.core.trade.protocol.tasks.ArbitratorSendsInitTradeOrMultisigRequests; import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest; import bisq.core.util.Validator; import bisq.network.p2p.NodeAddress; -import bisq.common.handlers.ErrorMessageHandler; - import lombok.extern.slf4j.Slf4j; @Slf4j @@ -27,6 +29,22 @@ public class ArbitratorProtocol extends DisputeProtocol { super(trade); } + @Override + protected void onTradeMessage(TradeMessage message, NodeAddress peer) { + super.onTradeMessage(message, peer); + if (message instanceof PayoutTxPublishedMessage) { + handle((PayoutTxPublishedMessage) message, peer); + } + } + + @Override + public void onMailboxMessage(TradeMessage message, NodeAddress peer) { + super.onMailboxMessage(message, peer); + if (message instanceof PayoutTxPublishedMessage) { + handle((PayoutTxPublishedMessage) message, peer); + } + } + /////////////////////////////////////////////////////////////////////////////////////////// // Incoming messages /////////////////////////////////////////////////////////////////////////////////////////// @@ -94,7 +112,7 @@ public class ArbitratorProtocol extends DisputeProtocol { @Override public void handleDepositResponse(DepositResponse response, NodeAddress sender) { - log.warn("Arbitrator ignoring DepositResponse"); + log.warn("Arbitrator ignoring DepositResponse for trade " + response.getTradeId()); } public void handlePaymentAccountKeyRequest(PaymentAccountKeyRequest request, NodeAddress sender) { @@ -121,15 +139,30 @@ public class ArbitratorProtocol extends DisputeProtocol { awaitTradeLatch(); } } - - /////////////////////////////////////////////////////////////////////////////////////////// - // Message dispatcher - /////////////////////////////////////////////////////////////////////////////////////////// - -// @Override -// protected void onTradeMessage(TradeMessage message, NodeAddress peer) { -// if (message instanceof InitTradeRequest) { -// handleInitTradeRequest((InitTradeRequest) message, peer); -// } -// } + + protected void handle(PayoutTxPublishedMessage request, NodeAddress peer) { + System.out.println("ArbitratorProtocol.handle(PayoutTxPublishedMessage)"); + new Thread(() -> { + synchronized (trade) { + if (trade.isCompleted()) return; // ignore subsequent requests + latchTrade(); + Validator.checkTradeId(processModel.getOfferId(), request); + processModel.setTradeMessage(request); + expect(phase(Trade.Phase.DEPOSITS_PUBLISHED) + .with(request) + .from(peer)) + .setup(tasks( + ArbitratorProcessPayoutTxPublishedMessage.class) + .using(new TradeTaskRunner(trade, + () -> { + handleTaskRunnerSuccess(peer, request); + }, + errorMessage -> { + handleTaskRunnerFault(peer, request, errorMessage); + }))) + .executeTasks(true); + awaitTradeLatch(); + } + }).start(); + } } 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 e0a6143a56..2f4a2baa5f 100644 --- a/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java @@ -25,6 +25,7 @@ import bisq.core.trade.messages.SignContractResponse; import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.protocol.FluentProtocol.Condition; import bisq.core.trade.protocol.tasks.ApplyFilter; +import bisq.core.trade.protocol.tasks.BuyerSendsPayoutTxPublishedMessage; import bisq.core.trade.protocol.tasks.BuyerPreparesPaymentSentMessage; import bisq.core.trade.protocol.tasks.BuyerProcessesPaymentReceivedMessage; import bisq.core.trade.protocol.tasks.BuyerSendsPaymentAccountKeyRequestToArbitrator; @@ -189,7 +190,8 @@ public abstract class BuyerProtocol extends DisputeProtocol { .with(message) .from(peer)) .setup(tasks( - BuyerProcessesPaymentReceivedMessage.class) + BuyerProcessesPaymentReceivedMessage.class, + BuyerSendsPayoutTxPublishedMessage.class) .using(new TradeTaskRunner(trade, () -> { handleTaskRunnerSuccess(peer, message); diff --git a/core/src/main/java/bisq/core/trade/protocol/DisputeProtocol.java b/core/src/main/java/bisq/core/trade/protocol/DisputeProtocol.java index 3b77fb2baa..4129e468d5 100644 --- a/core/src/main/java/bisq/core/trade/protocol/DisputeProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/DisputeProtocol.java @@ -51,6 +51,31 @@ public abstract class DisputeProtocol extends TradeProtocol { } + /////////////////////////////////////////////////////////////////////////////////////////// + // Dispatcher + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + protected void onTradeMessage(TradeMessage message, NodeAddress peer) { + super.onTradeMessage(message, peer); + if (message instanceof MediatedPayoutTxSignatureMessage) { + handle((MediatedPayoutTxSignatureMessage) message, peer); + } else if (message instanceof MediatedPayoutTxPublishedMessage) { + handle((MediatedPayoutTxPublishedMessage) message, peer); + } + } + + @Override + protected void onMailboxMessage(TradeMessage message, NodeAddress peer) { + super.onMailboxMessage(message, peer); + if (message instanceof MediatedPayoutTxSignatureMessage) { + handle((MediatedPayoutTxSignatureMessage) message, peer); + } else if (message instanceof MediatedPayoutTxPublishedMessage) { + handle((MediatedPayoutTxPublishedMessage) message, peer); + } + } + + /////////////////////////////////////////////////////////////////////////////////////////// // User interaction: Trader accepts mediation result /////////////////////////////////////////////////////////////////////////////////////////// @@ -131,53 +156,4 @@ public abstract class DisputeProtocol extends TradeProtocol { .setup(tasks(ProcessMediatedPayoutTxPublishedMessage.class)) .executeTasks(); } - - /////////////////////////////////////////////////////////////////////////////////////////// - // Delayed payout tx - /////////////////////////////////////////////////////////////////////////////////////////// - -// public void onPublishDelayedPayoutTx(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { -// DisputeEvent event = DisputeEvent.ARBITRATION_REQUESTED; -// expect(anyPhase(Trade.Phase.DEPOSITS_CONFIRMED, -// Trade.Phase.FIAT_SENT, -// Trade.Phase.FIAT_RECEIVED) -// .with(event) -// .preCondition(trade.getDelayedPayoutTx() != null)) -// .setup(tasks(PublishedDelayedPayoutTx.class, -// SendPeerPublishedDelayedPayoutTxMessage.class) -// .using(new TradeTaskRunner(trade, -// () -> { -// resultHandler.handleResult(); -// handleTaskRunnerSuccess(event); -// }, -// errorMessage -> { -// errorMessageHandler.handleErrorMessage(errorMessage); -// handleTaskRunnerFault(event, errorMessage); -// }))) -// .executeTasks(); -// } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Dispatcher - /////////////////////////////////////////////////////////////////////////////////////////// - - @Override - protected void onTradeMessage(TradeMessage message, NodeAddress peer) { - if (message instanceof MediatedPayoutTxSignatureMessage) { - handle((MediatedPayoutTxSignatureMessage) message, peer); - } else if (message instanceof MediatedPayoutTxPublishedMessage) { - handle((MediatedPayoutTxPublishedMessage) message, peer); - } - } - - @Override - protected void onMailboxMessage(TradeMessage message, NodeAddress peer) { - super.onMailboxMessage(message, peer); - if (message instanceof MediatedPayoutTxSignatureMessage) { - handle((MediatedPayoutTxSignatureMessage) message, peer); - } else if (message instanceof MediatedPayoutTxPublishedMessage) { - handle((MediatedPayoutTxPublishedMessage) message, peer); - } - } } 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 9fc6e6f184..5b3f45b90e 100644 --- a/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java @@ -19,12 +19,12 @@ package bisq.core.trade.protocol; import bisq.core.trade.SellerTrade; import bisq.core.trade.Trade; -import bisq.core.trade.messages.DepositResponse; import bisq.core.trade.messages.PaymentSentMessage; import bisq.core.trade.messages.SignContractResponse; import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.protocol.FluentProtocol.Condition; import bisq.core.trade.protocol.tasks.ApplyFilter; +import bisq.core.trade.protocol.tasks.SellerMaybeSendsPayoutTxPublishedMessage; import bisq.core.trade.protocol.tasks.SellerPreparesPaymentReceivedMessage; import bisq.core.trade.protocol.tasks.SellerProcessesPaymentSentMessage; import bisq.core.trade.protocol.tasks.SellerSendsPaymentReceivedMessage; @@ -155,6 +155,7 @@ public abstract class SellerProtocol extends DisputeProtocol { .setup(tasks( ApplyFilter.class, SellerPreparesPaymentReceivedMessage.class, + SellerMaybeSendsPayoutTxPublishedMessage.class, SellerSendsPaymentReceivedMessage.class) .using(new TradeTaskRunner(trade, () -> { this.errorMessageHandler = null; 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 7402d4d3ce..6cf72cad35 100644 --- a/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java @@ -86,6 +86,19 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D } + /////////////////////////////////////////////////////////////////////////////////////////// + // Dispatcher + /////////////////////////////////////////////////////////////////////////////////////////// + + protected void onTradeMessage(TradeMessage message, NodeAddress peerNodeAddress) { + log.info("Received {} as TradeMessage from {} with tradeId {} and uid {}", message.getClass().getSimpleName(), peerNodeAddress, message.getTradeId(), message.getUid()); + } + + protected void onMailboxMessage(TradeMessage message, NodeAddress peerNodeAddress) { + log.info("Received {} as MailboxMessage from {} with tradeId {} and uid {}", message.getClass().getSimpleName(), peerNodeAddress, message.getTradeId(), message.getUid()); + } + + /////////////////////////////////////////////////////////////////////////////////////////// // API /////////////////////////////////////////////////////////////////////////////////////////// @@ -116,11 +129,6 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D log.info("Withdraw completed"); } - protected void onMailboxMessage(TradeMessage message, NodeAddress peerNodeAddress) { - log.info("Received {} as MailboxMessage from {} with tradeId {} and uid {}", - message.getClass().getSimpleName(), peerNodeAddress, message.getTradeId(), message.getUid()); - } - /////////////////////////////////////////////////////////////////////////////////////////// // DecryptedDirectMessageListener @@ -218,8 +226,6 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D // Abstract /////////////////////////////////////////////////////////////////////////////////////////// - protected abstract void onTradeMessage(TradeMessage message, NodeAddress peer); - public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender) { System.out.println(getClass().getCanonicalName() + ".handleInitMultisigRequest()"); synchronized (trade) { diff --git a/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java b/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java index eaa67fe9e1..792b1e6f5d 100644 --- a/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java +++ b/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java @@ -147,8 +147,7 @@ public final class TradingPeer implements PersistablePayload { Optional.ofNullable(signature).ifPresent(e -> builder.setSignature(ByteString.copyFrom(e))); Optional.ofNullable(pubKeyRing).ifPresent(e -> builder.setPubKeyRing(e.toProtoMessage())); Optional.ofNullable(multiSigPubKey).ifPresent(e -> builder.setMultiSigPubKey(ByteString.copyFrom(e))); - Optional.ofNullable(rawTransactionInputs).ifPresent(e -> builder.addAllRawTransactionInputs( - ProtoUtil.collectionToProto(e, protobuf.RawTransactionInput.class))); + Optional.ofNullable(rawTransactionInputs).ifPresent(e -> builder.addAllRawTransactionInputs(ProtoUtil.collectionToProto(e, protobuf.RawTransactionInput.class))); Optional.ofNullable(changeOutputAddress).ifPresent(builder::setChangeOutputAddress); Optional.ofNullable(accountAgeWitnessNonce).ifPresent(e -> builder.setAccountAgeWitnessNonce(ByteString.copyFrom(e))); Optional.ofNullable(accountAgeWitnessSignature).ifPresent(e -> builder.setAccountAgeWitnessSignature(ByteString.copyFrom(e))); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorProcessPayoutTxPublishedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorProcessPayoutTxPublishedMessage.java new file mode 100644 index 0000000000..db1091866d --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorProcessPayoutTxPublishedMessage.java @@ -0,0 +1,53 @@ +/* + * 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 + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Haveno is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Haveno. If not, see . + */ + +package bisq.core.trade.protocol.tasks; + + +import bisq.common.taskrunner.TaskRunner; +import bisq.core.trade.Trade; +import bisq.core.trade.messages.PayoutTxPublishedMessage; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class ArbitratorProcessPayoutTxPublishedMessage extends TradeTask { + + @SuppressWarnings({"unused"}) + public ArbitratorProcessPayoutTxPublishedMessage(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + PayoutTxPublishedMessage request = (PayoutTxPublishedMessage) processModel.getTradeMessage(); + + // verify and publish payout tx + trade.verifyPayoutTx(request.getSignedPayoutTxHex(), false, true); + + // TODO: publish signed witness data? + //request.getSignedWitness() + + // close arbitrator trade + processModel.getTradeManager().onTradeCompleted(trade); + complete(); + } catch (Throwable t) { + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerProcessesPaymentReceivedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerProcessesPaymentReceivedMessage.java index 37cd304d3f..079c5c60d1 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerProcessesPaymentReceivedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerProcessesPaymentReceivedMessage.java @@ -71,7 +71,7 @@ public class BuyerProcessesPaymentReceivedMessage extends TradeTask { walletService.closeMultisigWallet(trade.getId()); } else { log.info("Buyer verifying, signing, and publishing seller's payout tx"); - trade.verifySignAndPublishPayoutTx(message.getPayoutTxHex()); + trade.verifyPayoutTx(message.getPayoutTxHex(), true, true); trade.setState(Trade.State.BUYER_PUBLISHED_PAYOUT_TX); // TODO (woodser): send PayoutTxPublishedMessage to arbitrator and seller } @@ -79,6 +79,7 @@ public class BuyerProcessesPaymentReceivedMessage extends TradeTask { log.info("We got the payout tx already set from BuyerSetupPayoutTxListener and do nothing here. trade ID={}", trade.getId()); } + // TODO: remove witness SignedWitness signedWitness = message.getSignedWitness(); if (signedWitness != null) { // We received the signedWitness from the seller and publish the data to the network. diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerSendsPayoutTxPublishedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerSendsPayoutTxPublishedMessage.java new file mode 100644 index 0000000000..75d7d36561 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerSendsPayoutTxPublishedMessage.java @@ -0,0 +1,81 @@ +/* + * 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 + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Haveno is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Haveno. If not, see . + */ + +package bisq.core.trade.protocol.tasks; + +import static com.google.common.base.Preconditions.checkNotNull; + +import bisq.common.crypto.PubKeyRing; +import bisq.common.taskrunner.TaskRunner; +import bisq.core.payment.PaymentAccount; +import bisq.core.trade.Trade; +import bisq.core.trade.messages.PayoutTxPublishedMessage; +import bisq.core.trade.messages.TradeMailboxMessage; +import bisq.network.p2p.NodeAddress; +import java.util.UUID; +import lombok.EqualsAndHashCode; +import lombok.extern.slf4j.Slf4j; + +@EqualsAndHashCode(callSuper = true) +@Slf4j +public class BuyerSendsPayoutTxPublishedMessage extends SendMailboxMessageTask { + + public BuyerSendsPayoutTxPublishedMessage(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected NodeAddress getReceiverNodeAddress() { + return trade.getArbitratorNodeAddress(); + } + + @Override + protected PubKeyRing getReceiverPubKeyRing() { + return trade.getArbitratorPubKeyRing(); + } + + @Override + protected TradeMailboxMessage getTradeMailboxMessage(String tradeId) { + checkNotNull(trade.getSelf().getPayoutTxHex(), "Payout tx must not be null"); + return new PayoutTxPublishedMessage( + tradeId, + processModel.getMyNodeAddress(), + null, // TODO: send witness data? + trade.getSelf().getPayoutTxHex() + ); + } + + @Override + protected void setStateSent() { + log.info("Buyer sent PayoutTxPublishedMessage: tradeId={} at arbitrator {}", trade.getId(), getReceiverNodeAddress()); + } + + @Override + protected void setStateArrived() { + log.info("Buyer's PayoutTxPublishedMessage arrived: tradeId={} at arbitrator {}", trade.getId(), getReceiverNodeAddress()); + } + + @Override + protected void setStateStoredInMailbox() { + log.info("Buyer's PayoutTxPublishedMessage stored in mailbox: tradeId={} at arbitrator {}", trade.getId(), getReceiverNodeAddress()); + } + + @Override + protected void setStateFault() { + log.error("Buyer's PayoutTxPublishedMessage failed: tradeId={} at arbitrator {}", trade.getId(), getReceiverNodeAddress()); + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/SellerMaybeSendsPayoutTxPublishedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/SellerMaybeSendsPayoutTxPublishedMessage.java new file mode 100644 index 0000000000..9cf343a5e3 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/SellerMaybeSendsPayoutTxPublishedMessage.java @@ -0,0 +1,96 @@ +/* + * 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 + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Haveno is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Haveno. If not, see . + */ + +package bisq.core.trade.protocol.tasks; + +import static com.google.common.base.Preconditions.checkNotNull; + +import bisq.common.crypto.PubKeyRing; +import bisq.common.taskrunner.TaskRunner; +import bisq.core.trade.Trade; +import bisq.core.trade.messages.PayoutTxPublishedMessage; +import bisq.core.trade.messages.TradeMailboxMessage; +import bisq.network.p2p.NodeAddress; +import lombok.EqualsAndHashCode; +import lombok.extern.slf4j.Slf4j; + +@EqualsAndHashCode(callSuper = true) +@Slf4j +public class SellerMaybeSendsPayoutTxPublishedMessage extends SendMailboxMessageTask { + + public SellerMaybeSendsPayoutTxPublishedMessage(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + // skip if payout tx not published + if (trade.getPhase().ordinal() < Trade.Phase.PAYOUT_PUBLISHED.ordinal()) { + complete(); + return; + } + + super.run(); + } catch (Throwable t) { + failed(t); + } + } + + @Override + protected NodeAddress getReceiverNodeAddress() { + return trade.getArbitratorNodeAddress(); + } + + @Override + protected PubKeyRing getReceiverPubKeyRing() { + return trade.getArbitratorPubKeyRing(); + } + + @Override + protected TradeMailboxMessage getTradeMailboxMessage(String tradeId) { + checkNotNull(trade.getSelf().getPayoutTxHex(), "Payout tx must not be null"); + return new PayoutTxPublishedMessage( + tradeId, + processModel.getMyNodeAddress(), + null, // TODO: send witness data? + trade.getSelf().getPayoutTxHex() + ); + } + + @Override + protected void setStateSent() { + log.info("Seller sent PayoutTxPublishedMessage: tradeId={} at arbitrator {}", trade.getId(), getReceiverNodeAddress()); + } + + @Override + protected void setStateArrived() { + log.info("Seller's PayoutTxPublishedMessage arrived: tradeId={} at arbitrator {}", trade.getId(), getReceiverNodeAddress()); + } + + @Override + protected void setStateStoredInMailbox() { + log.info("Seller's PayoutTxPublishedMessage stored in mailbox: tradeId={} at arbitrator {}", trade.getId(), getReceiverNodeAddress()); + } + + @Override + protected void setStateFault() { + log.error("Seller's PayoutTxPublishedMessage failed: tradeId={} at arbitrator {}", trade.getId(), getReceiverNodeAddress()); + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/SellerPreparesPaymentReceivedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/SellerPreparesPaymentReceivedMessage.java index e3e3e21e83..7ebede4fcf 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/SellerPreparesPaymentReceivedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/SellerPreparesPaymentReceivedMessage.java @@ -40,7 +40,7 @@ public class SellerPreparesPaymentReceivedMessage extends TradeTask { // verify, sign, and publish payout tx if given. otherwise create payout tx if (trade.getBuyer().getPayoutTxHex() != null) { log.info("Seller verifying, signing, and publishing payout tx"); - trade.verifySignAndPublishPayoutTx(trade.getBuyer().getPayoutTxHex()); + trade.verifyPayoutTx(trade.getBuyer().getPayoutTxHex(), true, true); } else { log.info("Seller creating unsigned payout tx"); MoneroTxWallet payoutTx = trade.createPayoutTx(); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/SendMailboxMessageTask.java b/core/src/main/java/bisq/core/trade/protocol/tasks/SendMailboxMessageTask.java index 7c73999b31..4eb7d7c568 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/SendMailboxMessageTask.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/SendMailboxMessageTask.java @@ -23,7 +23,7 @@ import bisq.core.trade.messages.TradeMessage; import bisq.network.p2p.NodeAddress; import bisq.network.p2p.SendMailboxMessageListener; - +import bisq.common.crypto.PubKeyRing; import bisq.common.taskrunner.TaskRunner; import lombok.extern.slf4j.Slf4j; @@ -34,7 +34,15 @@ public abstract class SendMailboxMessageTask extends TradeTask { super(taskHandler, trade); } - protected abstract TradeMailboxMessage getTradeMailboxMessage(String id); + protected NodeAddress getReceiverNodeAddress() { + return trade.getTradingPeerNodeAddress(); + } + + protected PubKeyRing getReceiverPubKeyRing() { + return trade.getTradingPeer().getPubKeyRing(); + } + + protected abstract TradeMailboxMessage getTradeMailboxMessage(String tradeId); protected abstract void setStateSent(); @@ -51,34 +59,31 @@ public abstract class SendMailboxMessageTask extends TradeTask { String id = processModel.getOfferId(); TradeMailboxMessage message = getTradeMailboxMessage(id); setStateSent(); - NodeAddress peersNodeAddress = trade.getTradingPeerNodeAddress(); + NodeAddress peersNodeAddress = getReceiverNodeAddress(); log.info("Send {} to peer {}. tradeId={}, uid={}", message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); processModel.getP2PService().getMailboxMessageService().sendEncryptedMailboxMessage( peersNodeAddress, - trade.getTradingPeer().getPubKeyRing(), + getReceiverPubKeyRing(), message, new SendMailboxMessageListener() { @Override public void onArrived() { - log.info("{} arrived at peer {}. tradeId={}, uid={}", - message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); + log.info("{} arrived at peer {}. tradeId={}, uid={}", message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); setStateArrived(); complete(); } @Override public void onStoredInMailbox() { - log.info("{} stored in mailbox for peer {}. tradeId={}, uid={}", - message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); + log.info("{} stored in mailbox for peer {}. tradeId={}, uid={}", message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); SendMailboxMessageTask.this.onStoredInMailbox(); } @Override public void onFault(String errorMessage) { - log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}", - message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid(), errorMessage); + log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}", message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid(), errorMessage); SendMailboxMessageTask.this.onFault(errorMessage, message); } } diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index e1426d36a5..4f99ce0782 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -79,10 +79,12 @@ message NetworkEnvelope { PaymentSentMessage payment_sent_message = 1011; PaymentReceivedMessage payment_received_message = 1012; PayoutTxPublishedMessage payout_tx_published_message = 1013; - UpdateMultisigRequest update_multisig_request = 1014; - UpdateMultisigResponse update_multisig_response = 1015; ArbitratorPayoutTxRequest arbitrator_payout_tx_request = 1016; ArbitratorPayoutTxResponse arbitrator_payout_tx_response = 1017; + + // TODO: delete these + UpdateMultisigRequest update_multisig_request = 1018; + UpdateMultisigResponse update_multisig_response = 1019; } } @@ -459,8 +461,8 @@ message PayoutTxPublishedMessage { string trade_id = 1; NodeAddress sender_node_address = 2; string uid = 3; - SignedWitness signed_witness = 4; // Added in v1.4.0 - string payout_tx_hex = 5; + SignedWitness signed_witness = 4; + string signed_payout_tx_hex = 5; } message ArbitratorPayoutTxRequest {