close arbitrator trade by sending PayoutTxPublishedMessage

This commit is contained in:
woodser 2022-09-18 20:20:38 -04:00
parent 9975d7398b
commit 64925d0137
17 changed files with 385 additions and 121 deletions

View file

@ -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 * @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"); log.info("Verifying payout tx");
// gather relevant info // gather relevant info
XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); XmrWalletService walletService = processModel.getProvider().getXmrWalletService();
MoneroWallet multisigWallet = walletService.getMultisigWallet(getId()); MoneroWallet multisigWallet = walletService.getMultisigWallet(getId());
@ -787,9 +788,9 @@ public abstract class Trade implements Tradable, Model {
BigInteger tradeAmount = ParsingUtils.coinToAtomicUnits(getAmount()); BigInteger tradeAmount = ParsingUtils.coinToAtomicUnits(getAmount());
// parse payout tx // parse payout tx
MoneroTxSet parsedTxSet = multisigWallet.describeTxSet(new MoneroTxSet().setMultisigTxHex(payoutTxHex)); MoneroTxSet describedTxSet = multisigWallet.describeTxSet(new MoneroTxSet().setMultisigTxHex(payoutTxHex));
if (parsedTxSet.getTxs() == null || parsedTxSet.getTxs().size() != 1) throw new RuntimeException("Bad payout tx"); // TODO (woodser): test nack if (describedTxSet.getTxs() == null || describedTxSet.getTxs().size() != 1) throw new RuntimeException("Bad payout tx"); // TODO (woodser): test nack
MoneroTxWallet payoutTx = parsedTxSet.getTxs().get(0); MoneroTxWallet payoutTx = describedTxSet.getTxs().get(0);
// verify payout tx has exactly 2 destinations // 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"); 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); 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) // TODO (woodser): verify fee is reasonable (e.g. within 2x of fee estimate tx)
// sign payout tx // sign payout tx
MoneroMultisigSignResult result = multisigWallet.signMultisigTxHex(payoutTxHex); if (sign) {
if (result.getSignedMultisigTxHex() == null) throw new RuntimeException("Error signing payout tx"); MoneroMultisigSignResult result = multisigWallet.signMultisigTxHex(payoutTxHex);
String signedPayoutTxHex = result.getSignedMultisigTxHex(); if (result.getSignedMultisigTxHex() == null) throw new RuntimeException("Error signing payout tx");
payoutTxHex = result.getSignedMultisigTxHex();
// submit payout tx }
multisigWallet.submitMultisigTxHex(signedPayoutTxHex);
walletService.closeMultisigWallet(getId());
// update trade state // update trade state
getSelf().setPayoutTxHex(signedPayoutTxHex); getSelf().setPayoutTxHex(payoutTxHex);
setPayoutTx(parsedTxSet.getTxs().get(0)); setPayoutTx(describedTxSet.getTxs().get(0));
setPayoutTxId(parsedTxSet.getTxs().get(0).getHash()); setPayoutTxId(describedTxSet.getTxs().get(0).getHash());
setState(isBuyer() ? Trade.State.BUYER_PUBLISHED_PAYOUT_TX : Trade.State.SELLER_PUBLISHED_PAYOUT_TX);
// 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 // Getter
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public boolean isArbitrator() {
return this instanceof ArbitratorTrade;
}
public boolean isBuyer() { public boolean isBuyer() {
return getBuyer() == getSelf(); return getBuyer() == getSelf();
} }

View file

@ -541,7 +541,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
//System.out.println("TradeManager trade.setTradingPeerNodeAddress(): " + sender); //System.out.println("TradeManager trade.setTradingPeerNodeAddress(): " + sender);
//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() // 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()); trade.setMakerPubKeyRing(trade.getOffer().getPubKeyRing());
initTradeAndProtocol(trade, getTradeProtocol(trade)); initTradeAndProtocol(trade, getTradeProtocol(trade));
trade.getSelf().setReserveTxHash(openOffer.getReserveTxHash()); // TODO (woodser): initialize in initTradeAndProtocol? trade.getSelf().setReserveTxHash(openOffer.getReserveTxHash()); // TODO (woodser): initialize in initTradeAndProtocol?

View file

@ -74,8 +74,8 @@ public class TradeUtils {
/** /**
* Check if the arbitrator signature for an offer is valid. * 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 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 * @return true if the arbitrator's signature is valid for the offer
*/ */
public static boolean isArbitratorSignatureValid(OfferPayload signedOfferPayload, Arbitrator arbitrator) { public static boolean isArbitratorSignatureValid(OfferPayload signedOfferPayload, Arbitrator arbitrator) {

View file

@ -38,7 +38,7 @@ import javax.annotation.Nullable;
@Value @Value
public final class PayoutTxPublishedMessage extends TradeMailboxMessage { public final class PayoutTxPublishedMessage extends TradeMailboxMessage {
private final NodeAddress senderNodeAddress; private final NodeAddress senderNodeAddress;
private final String payoutTxHex; private final String signedPayoutTxHex;
// Added in v1.4.0 // Added in v1.4.0
@Nullable @Nullable
@ -47,13 +47,13 @@ public final class PayoutTxPublishedMessage extends TradeMailboxMessage {
public PayoutTxPublishedMessage(String tradeId, public PayoutTxPublishedMessage(String tradeId,
NodeAddress senderNodeAddress, NodeAddress senderNodeAddress,
@Nullable SignedWitness signedWitness, @Nullable SignedWitness signedWitness,
String payoutTxHex) { String signedPayoutTxHex) {
this(tradeId, this(tradeId,
senderNodeAddress, senderNodeAddress,
signedWitness, signedWitness,
UUID.randomUUID().toString(), UUID.randomUUID().toString(),
Version.getP2PMessageVersion(), Version.getP2PMessageVersion(),
payoutTxHex); signedPayoutTxHex);
} }
@ -66,11 +66,11 @@ public final class PayoutTxPublishedMessage extends TradeMailboxMessage {
@Nullable SignedWitness signedWitness, @Nullable SignedWitness signedWitness,
String uid, String uid,
String messageVersion, String messageVersion,
String payoutTxHex) { String signedPayoutTxHex) {
super(messageVersion, tradeId, uid); super(messageVersion, tradeId, uid);
this.senderNodeAddress = senderNodeAddress; this.senderNodeAddress = senderNodeAddress;
this.signedWitness = signedWitness; this.signedWitness = signedWitness;
this.payoutTxHex = payoutTxHex; this.signedPayoutTxHex = signedPayoutTxHex;
} }
@Override @Override
@ -79,7 +79,7 @@ public final class PayoutTxPublishedMessage extends TradeMailboxMessage {
.setTradeId(tradeId) .setTradeId(tradeId)
.setSenderNodeAddress(senderNodeAddress.toProtoMessage()) .setSenderNodeAddress(senderNodeAddress.toProtoMessage())
.setUid(uid) .setUid(uid)
.setPayoutTxHex(payoutTxHex); .setSignedPayoutTxHex(signedPayoutTxHex);
Optional.ofNullable(signedWitness).ifPresent(signedWitness -> builder.setSignedWitness(signedWitness.toProtoSignedWitness())); Optional.ofNullable(signedWitness).ifPresent(signedWitness -> builder.setSignedWitness(signedWitness.toProtoSignedWitness()));
return getNetworkEnvelopeBuilder().setPayoutTxPublishedMessage(builder).build(); return getNetworkEnvelopeBuilder().setPayoutTxPublishedMessage(builder).build();
} }
@ -96,7 +96,7 @@ public final class PayoutTxPublishedMessage extends TradeMailboxMessage {
signedWitness, signedWitness,
proto.getUid(), proto.getUid(),
messageVersion, messageVersion,
proto.getPayoutTxHex()); proto.getSignedPayoutTxHex());
} }
@Override @Override
@ -104,7 +104,7 @@ public final class PayoutTxPublishedMessage extends TradeMailboxMessage {
return "PayoutTxPublishedMessage{" + return "PayoutTxPublishedMessage{" +
"\n senderNodeAddress=" + senderNodeAddress + "\n senderNodeAddress=" + senderNodeAddress +
",\n signedWitness=" + signedWitness + ",\n signedWitness=" + signedWitness +
",\n payoutTxHex=" + payoutTxHex + ",\n signedPayoutTxHex=" + signedPayoutTxHex +
"\n} " + super.toString(); "\n} " + super.toString();
} }
} }

View file

@ -1,5 +1,6 @@
package bisq.core.trade.protocol; package bisq.core.trade.protocol;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.core.trade.ArbitratorTrade; import bisq.core.trade.ArbitratorTrade;
import bisq.core.trade.Trade; import bisq.core.trade.Trade;
import bisq.core.trade.messages.DepositRequest; 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.InitTradeRequest;
import bisq.core.trade.messages.PaymentAccountKeyRequest; import bisq.core.trade.messages.PaymentAccountKeyRequest;
import bisq.core.trade.messages.SignContractResponse; 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.FluentProtocol.Condition;
import bisq.core.trade.protocol.tasks.ApplyFilter; 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.ArbitratorProcessesDepositRequest;
import bisq.core.trade.protocol.tasks.ArbitratorProcessesPaymentAccountKeyRequest; import bisq.core.trade.protocol.tasks.ArbitratorProcessesPaymentAccountKeyRequest;
import bisq.core.trade.protocol.tasks.ArbitratorProcessesReserveTx; 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.trade.protocol.tasks.ProcessInitTradeRequest;
import bisq.core.util.Validator; import bisq.core.util.Validator;
import bisq.network.p2p.NodeAddress; import bisq.network.p2p.NodeAddress;
import bisq.common.handlers.ErrorMessageHandler;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j
@ -27,6 +29,22 @@ public class ArbitratorProtocol extends DisputeProtocol {
super(trade); 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 // Incoming messages
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -94,7 +112,7 @@ public class ArbitratorProtocol extends DisputeProtocol {
@Override @Override
public void handleDepositResponse(DepositResponse response, NodeAddress sender) { 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) { public void handlePaymentAccountKeyRequest(PaymentAccountKeyRequest request, NodeAddress sender) {
@ -121,15 +139,30 @@ public class ArbitratorProtocol extends DisputeProtocol {
awaitTradeLatch(); awaitTradeLatch();
} }
} }
/////////////////////////////////////////////////////////////////////////////////////////// protected void handle(PayoutTxPublishedMessage request, NodeAddress peer) {
// Message dispatcher System.out.println("ArbitratorProtocol.handle(PayoutTxPublishedMessage)");
/////////////////////////////////////////////////////////////////////////////////////////// new Thread(() -> {
synchronized (trade) {
// @Override if (trade.isCompleted()) return; // ignore subsequent requests
// protected void onTradeMessage(TradeMessage message, NodeAddress peer) { latchTrade();
// if (message instanceof InitTradeRequest) { Validator.checkTradeId(processModel.getOfferId(), request);
// handleInitTradeRequest((InitTradeRequest) message, peer); 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();
}
} }

View file

@ -25,6 +25,7 @@ import bisq.core.trade.messages.SignContractResponse;
import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.FluentProtocol.Condition; import bisq.core.trade.protocol.FluentProtocol.Condition;
import bisq.core.trade.protocol.tasks.ApplyFilter; 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.BuyerPreparesPaymentSentMessage;
import bisq.core.trade.protocol.tasks.BuyerProcessesPaymentReceivedMessage; import bisq.core.trade.protocol.tasks.BuyerProcessesPaymentReceivedMessage;
import bisq.core.trade.protocol.tasks.BuyerSendsPaymentAccountKeyRequestToArbitrator; import bisq.core.trade.protocol.tasks.BuyerSendsPaymentAccountKeyRequestToArbitrator;
@ -189,7 +190,8 @@ public abstract class BuyerProtocol extends DisputeProtocol {
.with(message) .with(message)
.from(peer)) .from(peer))
.setup(tasks( .setup(tasks(
BuyerProcessesPaymentReceivedMessage.class) BuyerProcessesPaymentReceivedMessage.class,
BuyerSendsPayoutTxPublishedMessage.class)
.using(new TradeTaskRunner(trade, .using(new TradeTaskRunner(trade,
() -> { () -> {
handleTaskRunnerSuccess(peer, message); handleTaskRunnerSuccess(peer, message);

View file

@ -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 // User interaction: Trader accepts mediation result
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -131,53 +156,4 @@ public abstract class DisputeProtocol extends TradeProtocol {
.setup(tasks(ProcessMediatedPayoutTxPublishedMessage.class)) .setup(tasks(ProcessMediatedPayoutTxPublishedMessage.class))
.executeTasks(); .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);
}
}
} }

View file

@ -19,12 +19,12 @@ package bisq.core.trade.protocol;
import bisq.core.trade.SellerTrade; import bisq.core.trade.SellerTrade;
import bisq.core.trade.Trade; import bisq.core.trade.Trade;
import bisq.core.trade.messages.DepositResponse;
import bisq.core.trade.messages.PaymentSentMessage; import bisq.core.trade.messages.PaymentSentMessage;
import bisq.core.trade.messages.SignContractResponse; import bisq.core.trade.messages.SignContractResponse;
import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.FluentProtocol.Condition; import bisq.core.trade.protocol.FluentProtocol.Condition;
import bisq.core.trade.protocol.tasks.ApplyFilter; 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.SellerPreparesPaymentReceivedMessage;
import bisq.core.trade.protocol.tasks.SellerProcessesPaymentSentMessage; import bisq.core.trade.protocol.tasks.SellerProcessesPaymentSentMessage;
import bisq.core.trade.protocol.tasks.SellerSendsPaymentReceivedMessage; import bisq.core.trade.protocol.tasks.SellerSendsPaymentReceivedMessage;
@ -155,6 +155,7 @@ public abstract class SellerProtocol extends DisputeProtocol {
.setup(tasks( .setup(tasks(
ApplyFilter.class, ApplyFilter.class,
SellerPreparesPaymentReceivedMessage.class, SellerPreparesPaymentReceivedMessage.class,
SellerMaybeSendsPayoutTxPublishedMessage.class,
SellerSendsPaymentReceivedMessage.class) SellerSendsPaymentReceivedMessage.class)
.using(new TradeTaskRunner(trade, () -> { .using(new TradeTaskRunner(trade, () -> {
this.errorMessageHandler = null; this.errorMessageHandler = null;

View file

@ -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 // API
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -116,11 +129,6 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
log.info("Withdraw completed"); 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 // DecryptedDirectMessageListener
@ -218,8 +226,6 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
// Abstract // Abstract
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
protected abstract void onTradeMessage(TradeMessage message, NodeAddress peer);
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender) { public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handleInitMultisigRequest()"); System.out.println(getClass().getCanonicalName() + ".handleInitMultisigRequest()");
synchronized (trade) { synchronized (trade) {

View file

@ -147,8 +147,7 @@ public final class TradingPeer implements PersistablePayload {
Optional.ofNullable(signature).ifPresent(e -> builder.setSignature(ByteString.copyFrom(e))); Optional.ofNullable(signature).ifPresent(e -> builder.setSignature(ByteString.copyFrom(e)));
Optional.ofNullable(pubKeyRing).ifPresent(e -> builder.setPubKeyRing(e.toProtoMessage())); Optional.ofNullable(pubKeyRing).ifPresent(e -> builder.setPubKeyRing(e.toProtoMessage()));
Optional.ofNullable(multiSigPubKey).ifPresent(e -> builder.setMultiSigPubKey(ByteString.copyFrom(e))); Optional.ofNullable(multiSigPubKey).ifPresent(e -> builder.setMultiSigPubKey(ByteString.copyFrom(e)));
Optional.ofNullable(rawTransactionInputs).ifPresent(e -> builder.addAllRawTransactionInputs( Optional.ofNullable(rawTransactionInputs).ifPresent(e -> builder.addAllRawTransactionInputs(ProtoUtil.collectionToProto(e, protobuf.RawTransactionInput.class)));
ProtoUtil.collectionToProto(e, protobuf.RawTransactionInput.class)));
Optional.ofNullable(changeOutputAddress).ifPresent(builder::setChangeOutputAddress); Optional.ofNullable(changeOutputAddress).ifPresent(builder::setChangeOutputAddress);
Optional.ofNullable(accountAgeWitnessNonce).ifPresent(e -> builder.setAccountAgeWitnessNonce(ByteString.copyFrom(e))); Optional.ofNullable(accountAgeWitnessNonce).ifPresent(e -> builder.setAccountAgeWitnessNonce(ByteString.copyFrom(e)));
Optional.ofNullable(accountAgeWitnessSignature).ifPresent(e -> builder.setAccountAgeWitnessSignature(ByteString.copyFrom(e))); Optional.ofNullable(accountAgeWitnessSignature).ifPresent(e -> builder.setAccountAgeWitnessSignature(ByteString.copyFrom(e)));

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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);
}
}
}

View file

@ -71,7 +71,7 @@ public class BuyerProcessesPaymentReceivedMessage extends TradeTask {
walletService.closeMultisigWallet(trade.getId()); walletService.closeMultisigWallet(trade.getId());
} else { } else {
log.info("Buyer verifying, signing, and publishing seller's payout tx"); 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); trade.setState(Trade.State.BUYER_PUBLISHED_PAYOUT_TX);
// TODO (woodser): send PayoutTxPublishedMessage to arbitrator and seller // 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()); 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(); SignedWitness signedWitness = message.getSignedWitness();
if (signedWitness != null) { if (signedWitness != null) {
// We received the signedWitness from the seller and publish the data to the network. // We received the signedWitness from the seller and publish the data to the network.

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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<Trade> 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());
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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<Trade> 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());
}
}

View file

@ -40,7 +40,7 @@ public class SellerPreparesPaymentReceivedMessage extends TradeTask {
// verify, sign, and publish payout tx if given. otherwise create payout tx // verify, sign, and publish payout tx if given. otherwise create payout tx
if (trade.getBuyer().getPayoutTxHex() != null) { if (trade.getBuyer().getPayoutTxHex() != null) {
log.info("Seller verifying, signing, and publishing payout tx"); log.info("Seller verifying, signing, and publishing payout tx");
trade.verifySignAndPublishPayoutTx(trade.getBuyer().getPayoutTxHex()); trade.verifyPayoutTx(trade.getBuyer().getPayoutTxHex(), true, true);
} else { } else {
log.info("Seller creating unsigned payout tx"); log.info("Seller creating unsigned payout tx");
MoneroTxWallet payoutTx = trade.createPayoutTx(); MoneroTxWallet payoutTx = trade.createPayoutTx();

View file

@ -23,7 +23,7 @@ import bisq.core.trade.messages.TradeMessage;
import bisq.network.p2p.NodeAddress; import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.SendMailboxMessageListener; import bisq.network.p2p.SendMailboxMessageListener;
import bisq.common.crypto.PubKeyRing;
import bisq.common.taskrunner.TaskRunner; import bisq.common.taskrunner.TaskRunner;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -34,7 +34,15 @@ public abstract class SendMailboxMessageTask extends TradeTask {
super(taskHandler, trade); 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(); protected abstract void setStateSent();
@ -51,34 +59,31 @@ public abstract class SendMailboxMessageTask extends TradeTask {
String id = processModel.getOfferId(); String id = processModel.getOfferId();
TradeMailboxMessage message = getTradeMailboxMessage(id); TradeMailboxMessage message = getTradeMailboxMessage(id);
setStateSent(); setStateSent();
NodeAddress peersNodeAddress = trade.getTradingPeerNodeAddress(); NodeAddress peersNodeAddress = getReceiverNodeAddress();
log.info("Send {} to peer {}. tradeId={}, uid={}", log.info("Send {} to peer {}. tradeId={}, uid={}",
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
processModel.getP2PService().getMailboxMessageService().sendEncryptedMailboxMessage( processModel.getP2PService().getMailboxMessageService().sendEncryptedMailboxMessage(
peersNodeAddress, peersNodeAddress,
trade.getTradingPeer().getPubKeyRing(), getReceiverPubKeyRing(),
message, message,
new SendMailboxMessageListener() { new SendMailboxMessageListener() {
@Override @Override
public void onArrived() { public void onArrived() {
log.info("{} arrived at peer {}. tradeId={}, uid={}", log.info("{} arrived at peer {}. tradeId={}, uid={}", message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
setStateArrived(); setStateArrived();
complete(); complete();
} }
@Override @Override
public void onStoredInMailbox() { public void onStoredInMailbox() {
log.info("{} stored in mailbox for peer {}. tradeId={}, uid={}", log.info("{} stored in mailbox for peer {}. tradeId={}, uid={}", message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
SendMailboxMessageTask.this.onStoredInMailbox(); SendMailboxMessageTask.this.onStoredInMailbox();
} }
@Override @Override
public void onFault(String errorMessage) { public void onFault(String errorMessage) {
log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}", log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}", message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid(), errorMessage);
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid(), errorMessage);
SendMailboxMessageTask.this.onFault(errorMessage, message); SendMailboxMessageTask.this.onFault(errorMessage, message);
} }
} }

View file

@ -79,10 +79,12 @@ message NetworkEnvelope {
PaymentSentMessage payment_sent_message = 1011; PaymentSentMessage payment_sent_message = 1011;
PaymentReceivedMessage payment_received_message = 1012; PaymentReceivedMessage payment_received_message = 1012;
PayoutTxPublishedMessage payout_tx_published_message = 1013; PayoutTxPublishedMessage payout_tx_published_message = 1013;
UpdateMultisigRequest update_multisig_request = 1014;
UpdateMultisigResponse update_multisig_response = 1015;
ArbitratorPayoutTxRequest arbitrator_payout_tx_request = 1016; ArbitratorPayoutTxRequest arbitrator_payout_tx_request = 1016;
ArbitratorPayoutTxResponse arbitrator_payout_tx_response = 1017; 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; string trade_id = 1;
NodeAddress sender_node_address = 2; NodeAddress sender_node_address = 2;
string uid = 3; string uid = 3;
SignedWitness signed_witness = 4; // Added in v1.4.0 SignedWitness signed_witness = 4;
string payout_tx_hex = 5; string signed_payout_tx_hex = 5;
} }
message ArbitratorPayoutTxRequest { message ArbitratorPayoutTxRequest {