diff --git a/core/src/main/java/bisq/core/trade/HavenoUtils.java b/core/src/main/java/bisq/core/trade/HavenoUtils.java index 03d92c9c..f9aebbdb 100644 --- a/core/src/main/java/bisq/core/trade/HavenoUtils.java +++ b/core/src/main/java/bisq/core/trade/HavenoUtils.java @@ -18,8 +18,10 @@ package bisq.core.trade; import bisq.common.config.Config; +import bisq.common.crypto.Hash; import bisq.common.crypto.PubKeyRing; import bisq.common.crypto.Sig; +import bisq.common.util.Utilities; import bisq.core.offer.Offer; import bisq.core.offer.OfferPayload; import bisq.core.support.dispute.arbitration.ArbitrationManager; @@ -30,6 +32,7 @@ import bisq.core.trade.messages.PaymentSentMessage; import bisq.core.util.JsonUtil; import bisq.core.util.ParsingUtils; import bisq.core.util.coin.CoinUtil; +import bisq.network.p2p.NodeAddress; import lombok.extern.slf4j.Slf4j; import java.math.BigDecimal; @@ -180,6 +183,19 @@ public class HavenoUtils { } } + /** + * Returns a unique deterministic id for sending a trade mailbox message. + * + * @param trade the trade + * @param tradeMessageClass the trade message class + * @param receiver the receiver address + * @return a unique deterministic id for sending a trade mailbox message + */ + public static String getDeterministicId(Trade trade, Class tradeMessageClass, NodeAddress receiver) { + String uniqueId = trade.getId() + "_" + tradeMessageClass.getSimpleName() + "_" + trade.getRole() + "_to_" + trade.getPeerRole(trade.getTradingPeer(receiver)); + return Utilities.bytesAsHexString(Hash.getSha256Ripemd160hash(uniqueId.getBytes(Charsets.UTF_8))); + } + /** * Check if the arbitrator signature is valid for an offer. * diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/Trade.java index 6d6a05e8..03d908e6 100644 --- a/core/src/main/java/bisq/core/trade/Trade.java +++ b/core/src/main/java/bisq/core/trade/Trade.java @@ -1167,6 +1167,20 @@ public abstract class Trade implements Tradable, Model { return null; } + public String getRole() { + if (isBuyer()) return "Buyer"; + if (isSeller()) return "Seller"; + if (isArbitrator()) return "Arbitrator"; + throw new IllegalArgumentException("Trade is not buyer, seller, or arbitrator"); + } + + public String getPeerRole(TradingPeer peer) { + if (peer == getBuyer()) return "Buyer"; + if (peer == getSeller()) return "Seller"; + if (peer == getArbitrator()) return "Arbitrator"; + throw new IllegalArgumentException("Peer is not buyer, seller, or arbitrator"); + } + public Date getTakeOfferDate() { return new Date(takeOfferDate); } diff --git a/core/src/main/java/bisq/core/trade/messages/PaymentReceivedMessage.java b/core/src/main/java/bisq/core/trade/messages/PaymentReceivedMessage.java index ae8e1631..f9857f57 100644 --- a/core/src/main/java/bisq/core/trade/messages/PaymentReceivedMessage.java +++ b/core/src/main/java/bisq/core/trade/messages/PaymentReceivedMessage.java @@ -59,6 +59,7 @@ public final class PaymentReceivedMessage extends TradeMailboxMessage { public PaymentReceivedMessage(String tradeId, NodeAddress senderNodeAddress, + String uid, String unsignedPayoutTxHex, String signedPayoutTxHex, String updatedMultisigHex, @@ -68,7 +69,7 @@ public final class PaymentReceivedMessage extends TradeMailboxMessage { PaymentSentMessage paymentSentMessage) { this(tradeId, senderNodeAddress, - UUID.randomUUID().toString(), + uid, Version.getP2PMessageVersion(), unsignedPayoutTxHex, signedPayoutTxHex, diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerSendPaymentSentMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerSendPaymentSentMessage.java index 60ddac9d..866ac6ce 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerSendPaymentSentMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerSendPaymentSentMessage.java @@ -18,6 +18,7 @@ package bisq.core.trade.protocol.tasks; import bisq.core.network.MessageState; +import bisq.core.trade.HavenoUtils; import bisq.core.trade.Trade; import bisq.core.trade.messages.PaymentSentMessage; import bisq.core.trade.messages.TradeMailboxMessage; @@ -78,7 +79,7 @@ public abstract class BuyerSendPaymentSentMessage extends SendMailboxMessageTask // peer does not respond with an ACK msg in a certain time interval. To avoid that we get dangling mailbox // messages where only the one which gets processed by the peer would be removed we use the same uid. All // other data stays the same when we re-send the message at any time later. - String deterministicId = tradeId + processModel.getMyNodeAddress().getFullAddress(); + String deterministicId = HavenoUtils.getDeterministicId(trade, PaymentSentMessage.class, getReceiverNodeAddress()); // create payment sent message PaymentSentMessage message = new PaymentSentMessage( diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/SellerSendPaymentReceivedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/SellerSendPaymentReceivedMessage.java index 43af1cb2..8b1a3bad 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/SellerSendPaymentReceivedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/SellerSendPaymentReceivedMessage.java @@ -19,6 +19,7 @@ package bisq.core.trade.protocol.tasks; import bisq.core.account.sign.SignedWitness; import bisq.core.account.witness.AccountAgeWitnessService; +import bisq.core.trade.HavenoUtils; import bisq.core.trade.Trade; import bisq.core.trade.messages.PaymentReceivedMessage; import bisq.core.trade.messages.TradeMailboxMessage; @@ -71,10 +72,15 @@ public abstract class SellerSendPaymentReceivedMessage extends SendMailboxMessag log.info("{} {} signed and published peers account age witness", trade.getClass().getSimpleName(), trade.getId()); } - // TODO: create with deterministic id like BuyerSendPaymentSentMessage + // We do not use a real unique ID here as we want to be able to re-send the exact same message in case the + // peer does not respond with an ACK msg in a certain time interval. To avoid that we get dangling mailbox + // messages where only the one which gets processed by the peer would be removed we use the same uid. All + // other data stays the same when we re-send the message at any time later. + String deterministicId = HavenoUtils.getDeterministicId(trade, PaymentReceivedMessage.class, getReceiverNodeAddress()); message = new PaymentReceivedMessage( tradeId, processModel.getMyNodeAddress(), + deterministicId, trade.isPayoutPublished() ? null : trade.getPayoutTxHex(), // unsigned trade.isPayoutPublished() ? trade.getPayoutTxHex() : null, // signed trade.getSelf().getUpdatedMultisigHex(), diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/SendDepositsConfirmedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/SendDepositsConfirmedMessage.java index fcee1313..38900f96 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/SendDepositsConfirmedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/SendDepositsConfirmedMessage.java @@ -18,6 +18,7 @@ package bisq.core.trade.protocol.tasks; import bisq.core.btc.wallet.XmrWalletService; +import bisq.core.trade.HavenoUtils; import bisq.core.trade.Trade; import bisq.core.trade.messages.DepositsConfirmedMessage; import bisq.core.trade.messages.TradeMailboxMessage; @@ -71,13 +72,13 @@ public abstract class SendDepositsConfirmedMessage extends SendMailboxMessageTas // peer does not respond with an ACK msg in a certain time interval. To avoid that we get dangling mailbox // messages where only the one which gets processed by the peer would be removed we use the same uid. All // other data stays the same when we re-send the message at any time later. - String deterministicId = tradeId + processModel.getMyNodeAddress().getFullAddress(); + String deterministicId = HavenoUtils.getDeterministicId(trade, DepositsConfirmedMessage.class, getReceiverNodeAddress()); message = new DepositsConfirmedMessage( trade.getOffer().getId(), processModel.getMyNodeAddress(), processModel.getPubKeyRing(), deterministicId, - getReceiverNodeAddress().equals(trade.getBuyer().getNodeAddress()) ? trade.getSeller().getPaymentAccountKey() : null, // buyer receives seller's payment account decryption key + trade.getBuyer() == trade.getTradingPeer(getReceiverNodeAddress()) ? trade.getSeller().getPaymentAccountKey() : null, // buyer receives seller's payment account decryption key trade.getSelf().getUpdatedMultisigHex()); } return message;