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
* @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();
}

View File

@ -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?

View File

@ -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) {

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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);

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
///////////////////////////////////////////////////////////////////////////////////////////
@ -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);
}
}
}

View File

@ -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;

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
///////////////////////////////////////////////////////////////////////////////////////////
@ -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) {

View File

@ -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)));

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());
} 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.

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
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();

View File

@ -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);
}
}

View File

@ -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 {