diff --git a/src/main/java/bisq/core/alert/PrivateNotificationManager.java b/src/main/java/bisq/core/alert/PrivateNotificationManager.java index 7c424358..a10a275f 100644 --- a/src/main/java/bisq/core/alert/PrivateNotificationManager.java +++ b/src/main/java/bisq/core/alert/PrivateNotificationManager.java @@ -87,10 +87,11 @@ public PrivateNotificationManager(P2PService p2PService, private void handleMessage(DecryptedMessageWithPubKey decryptedMessageWithPubKey, NodeAddress senderNodeAddress) { this.decryptedMessageWithPubKey = decryptedMessageWithPubKey; - NetworkEnvelope networkEnvelop = decryptedMessageWithPubKey.getNetworkEnvelope(); - if (networkEnvelop instanceof PrivateNotificationMessage) { - PrivateNotificationMessage privateNotificationMessage = (PrivateNotificationMessage) networkEnvelop; - log.trace("Received privateNotificationMessage: " + privateNotificationMessage); + NetworkEnvelope networkEnvelope = decryptedMessageWithPubKey.getNetworkEnvelope(); + if (networkEnvelope instanceof PrivateNotificationMessage) { + PrivateNotificationMessage privateNotificationMessage = (PrivateNotificationMessage) networkEnvelope; + log.info("Received PrivateNotificationMessage from {} with uid={}", + senderNodeAddress, privateNotificationMessage.getUid()); if (privateNotificationMessage.getSenderNodeAddress().equals(senderNodeAddress)) { final PrivateNotificationPayload privateNotification = privateNotificationMessage.getPrivateNotificationPayload(); if (verifySignature(privateNotification)) @@ -110,16 +111,20 @@ public ReadOnlyObjectProperty privateNotificationPro return privateNotificationMessageProperty; } - public boolean sendPrivateNotificationMessageIfKeyIsValid(PrivateNotificationPayload privateNotification, PubKeyRing pubKeyRing, NodeAddress nodeAddress, + public boolean sendPrivateNotificationMessageIfKeyIsValid(PrivateNotificationPayload privateNotification, PubKeyRing pubKeyRing, NodeAddress peersNodeAddress, String privKeyString, SendMailboxMessageListener sendMailboxMessageListener) { boolean isKeyValid = isKeyValid(privKeyString); if (isKeyValid) { signAndAddSignatureToPrivateNotificationMessage(privateNotification); - p2PService.sendEncryptedMailboxMessage(nodeAddress, + + PrivateNotificationMessage message = new PrivateNotificationMessage(privateNotification, + p2PService.getNetworkNode().getNodeAddress(), + UUID.randomUUID().toString()); + log.info("Send {} to peer {}. uid={}", + message.getClass().getSimpleName(), peersNodeAddress, message.getUid()); + p2PService.sendEncryptedMailboxMessage(peersNodeAddress, pubKeyRing, - new PrivateNotificationMessage(privateNotification, - p2PService.getNetworkNode().getNodeAddress(), - UUID.randomUUID().toString()), + message, sendMailboxMessageListener); } diff --git a/src/main/java/bisq/core/arbitration/Dispute.java b/src/main/java/bisq/core/arbitration/Dispute.java index 746782b1..0d9e474c 100644 --- a/src/main/java/bisq/core/arbitration/Dispute.java +++ b/src/main/java/bisq/core/arbitration/Dispute.java @@ -60,6 +60,7 @@ public final class Dispute implements NetworkPayload { private final int traderId; private final boolean disputeOpenerIsBuyer; private final boolean disputeOpenerIsMaker; + // PubKeyRing of trader who opened the dispute private final PubKeyRing traderPubKeyRing; private final long tradeDate; private final Contract contract; @@ -246,7 +247,7 @@ public static Dispute fromProto(PB.Dispute proto, CoreProtoResolver coreProtoRes // API /////////////////////////////////////////////////////////////////////////////////////////// - public void addDisputeMessage(DisputeCommunicationMessage disputeCommunicationMessage) { + public void addDisputeCommunicationMessage(DisputeCommunicationMessage disputeCommunicationMessage) { if (!disputeCommunicationMessages.contains(disputeCommunicationMessage)) { disputeCommunicationMessages.add(disputeCommunicationMessage); storage.queueUpForSave(); diff --git a/src/main/java/bisq/core/arbitration/DisputeList.java b/src/main/java/bisq/core/arbitration/DisputeList.java index b8abec7f..703cc0e8 100644 --- a/src/main/java/bisq/core/arbitration/DisputeList.java +++ b/src/main/java/bisq/core/arbitration/DisputeList.java @@ -89,7 +89,7 @@ public static DisputeList fromProto(PB.DisputeList proto, List list = proto.getDisputeList().stream() .map(disputeProto -> Dispute.fromProto(disputeProto, coreProtoResolver)) .collect(Collectors.toList()); - list.stream().forEach(e -> e.setStorage(storage)); + list.forEach(e -> e.setStorage(storage)); return new DisputeList(storage, list); } @@ -102,7 +102,7 @@ public boolean add(Dispute dispute) { if (!list.contains(dispute)) { boolean changed = list.add(dispute); if (changed) - storage.queueUpForSave(); + persist(); return changed; } else { return false; @@ -113,10 +113,14 @@ public boolean remove(Object dispute) { //noinspection SuspiciousMethodCalls boolean changed = list.remove(dispute); if (changed) - storage.queueUpForSave(); + persist(); return changed; } + public void persist() { + storage.queueUpForSave(); + } + public int size() { return list.size(); } diff --git a/src/main/java/bisq/core/arbitration/DisputeManager.java b/src/main/java/bisq/core/arbitration/DisputeManager.java index 14c4cddd..bb5c7c51 100644 --- a/src/main/java/bisq/core/arbitration/DisputeManager.java +++ b/src/main/java/bisq/core/arbitration/DisputeManager.java @@ -37,6 +37,8 @@ import bisq.core.trade.TradeManager; import bisq.core.trade.closed.ClosedTradableManager; +import bisq.network.p2p.AckMessage; +import bisq.network.p2p.AckMessageSourceType; import bisq.network.p2p.BootstrapListener; import bisq.network.p2p.DecryptedMessageWithPubKey; import bisq.network.p2p.NodeAddress; @@ -45,7 +47,6 @@ import bisq.common.Timer; import bisq.common.UserThread; -import bisq.common.app.Log; import bisq.common.crypto.KeyRing; import bisq.common.crypto.PubKeyRing; import bisq.common.handlers.FaultHandler; @@ -54,6 +55,7 @@ import bisq.common.proto.persistable.PersistedDataHost; import bisq.common.proto.persistable.PersistenceProtoResolver; import bisq.common.storage.Storage; +import bisq.common.util.Tuple2; import org.bitcoinj.core.AddressFormatException; import org.bitcoinj.core.Transaction; @@ -71,7 +73,6 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -85,6 +86,8 @@ import org.jetbrains.annotations.NotNull; +import javax.annotation.Nullable; + public class DisputeManager implements PersistedDataHost { private static final Logger log = LoggerFactory.getLogger(DisputeManager.class); @@ -157,7 +160,7 @@ public DisputeManager(P2PService p2PService, public void readPersisted() { disputes = new DisputeList(disputeStorage); disputes.readPersisted(); - disputes.stream().forEach(dispute -> dispute.setStorage(getDisputeStorage())); + disputes.stream().forEach(dispute -> dispute.setStorage(disputeStorage)); } public void onAllServicesInitialized() { @@ -185,7 +188,7 @@ public void onUpdatedDataReceived() { public void cleanupDisputes() { disputes.stream().forEach(dispute -> { - dispute.setStorage(getDisputeStorage()); + dispute.setStorage(disputeStorage); if (dispute.isClosed()) closedDisputes.put(dispute.getTradeId(), dispute); else @@ -194,11 +197,9 @@ public void cleanupDisputes() { // If we have duplicate disputes we close the second one (might happen if both traders opened a dispute and arbitrator // was offline, so could not forward msg to other peer, then the arbitrator might have 4 disputes open for 1 trade) - openDisputes.entrySet().stream().forEach(openDisputeEntry -> { - String key = openDisputeEntry.getKey(); + openDisputes.forEach((key, openDispute) -> { if (closedDisputes.containsKey(key)) { final Dispute closedDispute = closedDisputes.get(key); - final Dispute openDispute = openDisputeEntry.getValue(); // We need to check if is from the same peer, we don't want to close the peers dispute if (closedDispute.getTraderId() == openDispute.getTraderId()) { openDispute.setIsClosed(true); @@ -222,9 +223,11 @@ private boolean isReadyForTxBroadcast() { private void applyMessages() { decryptedDirectMessageWithPubKeys.forEach(decryptedMessageWithPubKey -> { NetworkEnvelope networkEnvelope = decryptedMessageWithPubKey.getNetworkEnvelope(); - log.debug("decryptedDirectMessageWithPubKeys.message " + networkEnvelope); - if (networkEnvelope instanceof DisputeMessage) + if (networkEnvelope instanceof DisputeMessage) { dispatchMessage((DisputeMessage) networkEnvelope); + } else if (networkEnvelope instanceof AckMessage) { + processAckMessage((AckMessage) networkEnvelope, null); + } }); decryptedDirectMessageWithPubKeys.clear(); @@ -234,12 +237,43 @@ private void applyMessages() { if (networkEnvelope instanceof DisputeMessage) { dispatchMessage((DisputeMessage) networkEnvelope); p2PService.removeEntryFromMailbox(decryptedMessageWithPubKey); + } else if (networkEnvelope instanceof AckMessage) { + processAckMessage((AckMessage) networkEnvelope, decryptedMessageWithPubKey); } }); decryptedMailboxMessageWithPubKeys.clear(); } + private void processAckMessage(AckMessage ackMessage, @Nullable DecryptedMessageWithPubKey decryptedMessageWithPubKey) { + if (ackMessage.getSourceType() == AckMessageSourceType.DISPUTE_MESSAGE) { + if (ackMessage.isSuccess()) { + log.info("Received AckMessage for {} with tradeId {} and uid {}", + ackMessage.getSourceMsgClassName(), ackMessage.getSourceId(), ackMessage.getSourceUid()); + } else { + log.warn("Received AckMessage with error state for {} with tradeId {} and errorMessage={}", + ackMessage.getSourceMsgClassName(), ackMessage.getSourceId(), ackMessage.getErrorMessage()); + } + + disputes.getList().stream() + .flatMap(dispute -> dispute.getDisputeCommunicationMessages().stream()) + .filter(msg -> msg.getUid().equals(ackMessage.getSourceUid())) + .forEach(msg -> { + if (ackMessage.isSuccess()) + msg.setAcknowledged(true); + else + msg.setAckError(ackMessage.getErrorMessage()); + }); + disputes.persist(); + + if (decryptedMessageWithPubKey != null) + p2PService.removeEntryFromMailbox(decryptedMessageWithPubKey); + } + } + private void dispatchMessage(DisputeMessage message) { + log.info("Received {} with tradeId {} and uid {}", + message.getClass().getSimpleName(), message.getTradeId(), message.getUid()); + if (message instanceof OpenNewDisputeMessage) onOpenNewDisputeMessage((OpenNewDisputeMessage) message); else if (message instanceof PeerOpenedDisputeMessage) @@ -267,42 +301,70 @@ public void sendOpenNewDisputeMessage(Dispute dispute, boolean reOpen, ResultHan keyRing.getPubKeyRing().hashCode(), false, Res.get("support.systemMsg", sysMsg), - null, - p2PService.getAddress(), - new Date().getTime(), - false, - false, - UUID.randomUUID().toString() + p2PService.getAddress() ); disputeCommunicationMessage.setSystemMessage(true); - dispute.addDisputeMessage(disputeCommunicationMessage); + dispute.addDisputeCommunicationMessage(disputeCommunicationMessage); if (!reOpen) { disputes.add(dispute); } - p2PService.sendEncryptedMailboxMessage(dispute.getContract().getArbitratorNodeAddress(), + NodeAddress peersNodeAddress = dispute.getContract().getArbitratorNodeAddress(); + OpenNewDisputeMessage openNewDisputeMessage = new OpenNewDisputeMessage(dispute, p2PService.getAddress(), + UUID.randomUUID().toString()); + log.info("Send {} to peer {}. tradeId={}, openNewDisputeMessage.uid={}, " + + "disputeCommunicationMessage.uid={}", + openNewDisputeMessage.getClass().getSimpleName(), peersNodeAddress, + openNewDisputeMessage.getTradeId(), openNewDisputeMessage.getUid(), + disputeCommunicationMessage.getUid()); + p2PService.sendEncryptedMailboxMessage(peersNodeAddress, dispute.getArbitratorPubKeyRing(), - new OpenNewDisputeMessage(dispute, p2PService.getAddress(), - UUID.randomUUID().toString()), + openNewDisputeMessage, new SendMailboxMessageListener() { @Override public void onArrived() { - log.info("Message arrived at peer. tradeId={}", disputeCommunicationMessage.getTradeId()); + log.info("{} arrived at peer {}. tradeId={}, openNewDisputeMessage.uid={}, " + + "disputeCommunicationMessage.uid={}", + openNewDisputeMessage.getClass().getSimpleName(), peersNodeAddress, + openNewDisputeMessage.getTradeId(), openNewDisputeMessage.getUid(), + disputeCommunicationMessage.getUid()); + + // We use the disputeCommunicationMessage wrapped inside the openNewDisputeMessage for + // the state, as that is displayed to the user and we only persist that msg disputeCommunicationMessage.setArrived(true); + disputes.persist(); resultHandler.handleResult(); } @Override public void onStoredInMailbox() { - log.info("Message stored in mailbox. tradeId={}", disputeCommunicationMessage.getTradeId()); + log.info("{} stored in mailbox for peer {}. tradeId={}, openNewDisputeMessage.uid={}, " + + "disputeCommunicationMessage.uid={}", + openNewDisputeMessage.getClass().getSimpleName(), peersNodeAddress, + openNewDisputeMessage.getTradeId(), openNewDisputeMessage.getUid(), + disputeCommunicationMessage.getUid()); + + // We use the disputeCommunicationMessage wrapped inside the openNewDisputeMessage for + // the state, as that is displayed to the user and we only persist that msg disputeCommunicationMessage.setStoredInMailbox(true); + disputes.persist(); resultHandler.handleResult(); } @Override public void onFault(String errorMessage) { - log.error("sendEncryptedMailboxMessage failed. disputeCommunicationMessage=" + disputeCommunicationMessage); - faultHandler.handleFault("Sending dispute message failed: " + errorMessage, new MessageDeliveryFailedException()); + log.error("{} failed: Peer {}. tradeId={}, openNewDisputeMessage.uid={}, " + + "disputeCommunicationMessage.uid={}, errorMessage={}", + openNewDisputeMessage.getClass().getSimpleName(), peersNodeAddress, + openNewDisputeMessage.getTradeId(), openNewDisputeMessage.getUid(), + disputeCommunicationMessage.getUid(), errorMessage); + + // We use the disputeCommunicationMessage wrapped inside the openNewDisputeMessage for + // the state, as that is displayed to the user and we only persist that msg + disputeCommunicationMessage.setSendMessageError(errorMessage); + disputes.persist(); + faultHandler.handleFault("Sending dispute message failed: " + + errorMessage, new MessageDeliveryFailedException()); } } ); @@ -320,9 +382,7 @@ public void onFault(String errorMessage) { } // arbitrator sends that to trading peer when he received openDispute request - private void sendPeerOpenedDisputeMessage(Dispute disputeFromOpener) { - Contract contractFromOpener = disputeFromOpener.getContract(); - PubKeyRing pubKeyRing = disputeFromOpener.isDisputeOpenerIsBuyer() ? contractFromOpener.getSellerPubKeyRing() : contractFromOpener.getBuyerPubKeyRing(); + private String sendPeerOpenedDisputeMessage(Dispute disputeFromOpener, Contract contractFromOpener, PubKeyRing pubKeyRing) { Dispute dispute = new Dispute( disputeStorage, disputeFromOpener.getTradeId(), @@ -353,113 +413,135 @@ private void sendPeerOpenedDisputeMessage(Dispute disputeFromOpener) { keyRing.getPubKeyRing().hashCode(), false, Res.get("support.systemMsg", sysMsg), - null, - p2PService.getAddress(), - new Date().getTime(), - false, - false, - UUID.randomUUID().toString() + p2PService.getAddress() ); disputeCommunicationMessage.setSystemMessage(true); - dispute.addDisputeMessage(disputeCommunicationMessage); + dispute.addDisputeCommunicationMessage(disputeCommunicationMessage); disputes.add(dispute); // we mirrored dispute already! Contract contract = dispute.getContract(); PubKeyRing peersPubKeyRing = dispute.isDisputeOpenerIsBuyer() ? contract.getBuyerPubKeyRing() : contract.getSellerPubKeyRing(); - NodeAddress peerNodeAddress = dispute.isDisputeOpenerIsBuyer() ? contract.getBuyerNodeAddress() : contract.getSellerNodeAddress(); - log.trace("sendPeerOpenedDisputeMessage to peerAddress " + peerNodeAddress); - p2PService.sendEncryptedMailboxMessage(peerNodeAddress, + NodeAddress peersNodeAddress = dispute.isDisputeOpenerIsBuyer() ? contract.getBuyerNodeAddress() : contract.getSellerNodeAddress(); + PeerOpenedDisputeMessage peerOpenedDisputeMessage = new PeerOpenedDisputeMessage(dispute, + p2PService.getAddress(), + UUID.randomUUID().toString()); + log.info("Send {} to peer {}. tradeId={}, peerOpenedDisputeMessage.uid={}, " + + "disputeCommunicationMessage.uid={}", + peerOpenedDisputeMessage.getClass().getSimpleName(), peersNodeAddress, + peerOpenedDisputeMessage.getTradeId(), peerOpenedDisputeMessage.getUid(), + disputeCommunicationMessage.getUid()); + p2PService.sendEncryptedMailboxMessage(peersNodeAddress, peersPubKeyRing, - new PeerOpenedDisputeMessage(dispute, - p2PService.getAddress(), - UUID.randomUUID().toString()), + peerOpenedDisputeMessage, new SendMailboxMessageListener() { @Override public void onArrived() { - log.info("Message arrived at peer. tradeId={}", disputeCommunicationMessage.getTradeId()); + log.info("{} arrived at peer {}. tradeId={}, peerOpenedDisputeMessage.uid={}, " + + "disputeCommunicationMessage.uid={}", + peerOpenedDisputeMessage.getClass().getSimpleName(), peersNodeAddress, + peerOpenedDisputeMessage.getTradeId(), peerOpenedDisputeMessage.getUid(), + disputeCommunicationMessage.getUid()); + + // We use the disputeCommunicationMessage wrapped inside the peerOpenedDisputeMessage for + // the state, as that is displayed to the user and we only persist that msg disputeCommunicationMessage.setArrived(true); + disputes.persist(); } @Override public void onStoredInMailbox() { - log.info("Message stored in mailbox. tradeId={}", disputeCommunicationMessage.getTradeId()); + log.info("{} stored in mailbox for peer {}. tradeId={}, peerOpenedDisputeMessage.uid={}, " + + "disputeCommunicationMessage.uid={}", + peerOpenedDisputeMessage.getClass().getSimpleName(), peersNodeAddress, + peerOpenedDisputeMessage.getTradeId(), peerOpenedDisputeMessage.getUid(), + disputeCommunicationMessage.getUid()); + + // We use the disputeCommunicationMessage wrapped inside the peerOpenedDisputeMessage for + // the state, as that is displayed to the user and we only persist that msg disputeCommunicationMessage.setStoredInMailbox(true); + disputes.persist(); } @Override public void onFault(String errorMessage) { - log.error("sendEncryptedMailboxMessage failed. disputeCommunicationMessage=" + disputeCommunicationMessage); + log.error("{} failed: Peer {}. tradeId={}, peerOpenedDisputeMessage.uid={}, " + + "disputeCommunicationMessage.uid={}, errorMessage={}", + peerOpenedDisputeMessage.getClass().getSimpleName(), peersNodeAddress, + peerOpenedDisputeMessage.getTradeId(), peerOpenedDisputeMessage.getUid(), + disputeCommunicationMessage.getUid(), errorMessage); + + // We use the disputeCommunicationMessage wrapped inside the peerOpenedDisputeMessage for + // the state, as that is displayed to the user and we only persist that msg + disputeCommunicationMessage.setSendMessageError(errorMessage); + disputes.persist(); } } ); + return null; } else { - log.warn("We got a dispute already open for that trade and trading peer.\n" + - "TradeId = " + dispute.getTradeId()); + String msg = "We got a dispute already open for that trade and trading peer.\n" + + "TradeId = " + dispute.getTradeId(); + log.warn(msg); + return msg; } } // traders send msg to the arbitrator or arbitrator to 1 trader (trader to trader is not allowed) public DisputeCommunicationMessage sendDisputeDirectMessage(Dispute dispute, String text, ArrayList attachments) { - DisputeCommunicationMessage disputeCommunicationMessage = new DisputeCommunicationMessage( + DisputeCommunicationMessage message = new DisputeCommunicationMessage( dispute.getTradeId(), dispute.getTraderPubKeyRing().hashCode(), isTrader(dispute), text, - null, - p2PService.getAddress(), - new Date().getTime(), - false, - false, - UUID.randomUUID().toString() + p2PService.getAddress() ); - disputeCommunicationMessage.addAllAttachments(attachments); - PubKeyRing receiverPubKeyRing = null; - NodeAddress peerNodeAddress = null; - if (isTrader(dispute)) { - dispute.addDisputeMessage(disputeCommunicationMessage); - receiverPubKeyRing = dispute.getArbitratorPubKeyRing(); - peerNodeAddress = dispute.getContract().getArbitratorNodeAddress(); - } else if (isArbitrator(dispute)) { - if (!disputeCommunicationMessage.isSystemMessage()) - dispute.addDisputeMessage(disputeCommunicationMessage); - receiverPubKeyRing = dispute.getTraderPubKeyRing(); - Contract contract = dispute.getContract(); - if (contract.getBuyerPubKeyRing().equals(receiverPubKeyRing)) - peerNodeAddress = contract.getBuyerNodeAddress(); - else - peerNodeAddress = contract.getSellerNodeAddress(); - } else { - log.error("That must not happen. Trader cannot communicate to other trader."); - } + message.addAllAttachments(attachments); + Tuple2 tuple = getNodeAddressPubKeyRingTuple(dispute); + NodeAddress peersNodeAddress = tuple.first; + PubKeyRing receiverPubKeyRing = tuple.second; + + if (isTrader(dispute) || + (isArbitrator(dispute) && !message.isSystemMessage())) + dispute.addDisputeCommunicationMessage(message); + if (receiverPubKeyRing != null) { - log.trace("sendDisputeDirectMessage to peerAddress " + peerNodeAddress); - p2PService.sendEncryptedMailboxMessage(peerNodeAddress, + log.info("Send {} to peer {}. tradeId={}, uid={}", + message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); + + p2PService.sendEncryptedMailboxMessage(peersNodeAddress, receiverPubKeyRing, - disputeCommunicationMessage, + message, new SendMailboxMessageListener() { @Override public void onArrived() { - log.info("Message arrived at peer. tradeId={}", disputeCommunicationMessage.getTradeId()); - disputeCommunicationMessage.setArrived(true); + log.info("{} arrived at peer {}. tradeId={}, uid={}", + message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); + message.setArrived(true); + disputes.persist(); } @Override public void onStoredInMailbox() { - log.info("Message stored in mailbox. tradeId={}", disputeCommunicationMessage.getTradeId()); - disputeCommunicationMessage.setStoredInMailbox(true); + log.info("{} stored in mailbox for peer {}. tradeId={}, uid={}", + message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); + message.setStoredInMailbox(true); + disputes.persist(); } @Override public void onFault(String errorMessage) { - log.error("sendEncryptedMailboxMessage failed. disputeCommunicationMessage=" + disputeCommunicationMessage); + log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}", + message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid(), errorMessage); + message.setSendMessageError(errorMessage); + disputes.persist(); } } ); } - return disputeCommunicationMessage; + return message; } // arbitrator send result to trader @@ -469,43 +551,67 @@ public void sendDisputeResultMessage(DisputeResult disputeResult, Dispute disput dispute.getTraderPubKeyRing().hashCode(), false, text, - null, - p2PService.getAddress(), - new Date().getTime(), - false, - false, - UUID.randomUUID().toString() + p2PService.getAddress() ); - dispute.addDisputeMessage(disputeCommunicationMessage); + dispute.addDisputeCommunicationMessage(disputeCommunicationMessage); disputeResult.setDisputeCommunicationMessage(disputeCommunicationMessage); - NodeAddress peerNodeAddress; + NodeAddress peersNodeAddress; Contract contract = dispute.getContract(); if (contract.getBuyerPubKeyRing().equals(dispute.getTraderPubKeyRing())) - peerNodeAddress = contract.getBuyerNodeAddress(); + peersNodeAddress = contract.getBuyerNodeAddress(); else - peerNodeAddress = contract.getSellerNodeAddress(); - p2PService.sendEncryptedMailboxMessage(peerNodeAddress, + peersNodeAddress = contract.getSellerNodeAddress(); + DisputeResultMessage disputeResultMessage = new DisputeResultMessage(disputeResult, p2PService.getAddress(), + UUID.randomUUID().toString()); + log.info("Send {} to peer {}. tradeId={}, disputeResultMessage.uid={}, disputeCommunicationMessage.uid={}", + disputeResultMessage.getClass().getSimpleName(), peersNodeAddress, disputeResultMessage.getTradeId(), + disputeResultMessage.getUid(), disputeCommunicationMessage.getUid()); + p2PService.sendEncryptedMailboxMessage(peersNodeAddress, dispute.getTraderPubKeyRing(), - new DisputeResultMessage(disputeResult, p2PService.getAddress(), - UUID.randomUUID().toString()), + disputeResultMessage, new SendMailboxMessageListener() { @Override public void onArrived() { - log.info("Message arrived at peer. tradeId={}", disputeCommunicationMessage.getTradeId()); + log.info("{} arrived at peer {}. tradeId={}, disputeResultMessage.uid={}, " + + "disputeCommunicationMessage.uid={}", + disputeResultMessage.getClass().getSimpleName(), peersNodeAddress, + disputeResultMessage.getTradeId(), disputeResultMessage.getUid(), + disputeCommunicationMessage.getUid()); + + // We use the disputeCommunicationMessage wrapped inside the disputeResultMessage for + // the state, as that is displayed to the user and we only persist that msg disputeCommunicationMessage.setArrived(true); + disputes.persist(); } @Override public void onStoredInMailbox() { - log.info("Message stored in mailbox. tradeId={}", disputeCommunicationMessage.getTradeId()); + log.info("{} stored in mailbox for peer {}. tradeId={}, disputeResultMessage.uid={}, " + + "disputeCommunicationMessage.uid={}", + disputeResultMessage.getClass().getSimpleName(), peersNodeAddress, + disputeResultMessage.getTradeId(), disputeResultMessage.getUid(), + disputeCommunicationMessage.getUid()); + + // We use the disputeCommunicationMessage wrapped inside the disputeResultMessage for + // the state, as that is displayed to the user and we only persist that msg disputeCommunicationMessage.setStoredInMailbox(true); + disputes.persist(); } @Override public void onFault(String errorMessage) { - log.error("sendEncryptedMailboxMessage failed. disputeCommunicationMessage=" + disputeCommunicationMessage); + log.error("{} failed: Peer {}. tradeId={}, disputeResultMessage.uid={}, " + + "disputeCommunicationMessage.uid={}, errorMessage={}", + disputeResultMessage.getClass().getSimpleName(), peersNodeAddress, + disputeResultMessage.getTradeId(), disputeResultMessage.getUid(), + disputeCommunicationMessage.getUid(), errorMessage); + + // We use the disputeCommunicationMessage wrapped inside the disputeResultMessage for + // the state, as that is displayed to the user and we only persist that msg + disputeCommunicationMessage.setSendMessageError(errorMessage); + disputes.persist(); } } ); @@ -514,29 +620,74 @@ public void onFault(String errorMessage) { // winner (or buyer in case of 50/50) sends tx to other peer private void sendPeerPublishedPayoutTxMessage(Transaction transaction, Dispute dispute, Contract contract) { PubKeyRing peersPubKeyRing = dispute.isDisputeOpenerIsBuyer() ? contract.getSellerPubKeyRing() : contract.getBuyerPubKeyRing(); - NodeAddress peerNodeAddress = dispute.isDisputeOpenerIsBuyer() ? contract.getSellerNodeAddress() : contract.getBuyerNodeAddress(); - log.trace("sendPeerPublishedPayoutTxMessage to peerAddress " + peerNodeAddress); + NodeAddress peersNodeAddress = dispute.isDisputeOpenerIsBuyer() ? contract.getSellerNodeAddress() : contract.getBuyerNodeAddress(); + log.trace("sendPeerPublishedPayoutTxMessage to peerAddress " + peersNodeAddress); final PeerPublishedDisputePayoutTxMessage message = new PeerPublishedDisputePayoutTxMessage(transaction.bitcoinSerialize(), dispute.getTradeId(), p2PService.getAddress(), UUID.randomUUID().toString()); - p2PService.sendEncryptedMailboxMessage(peerNodeAddress, + log.info("Send {} to peer {}. tradeId={}, uid={}", + message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); + p2PService.sendEncryptedMailboxMessage(peersNodeAddress, peersPubKeyRing, message, new SendMailboxMessageListener() { @Override public void onArrived() { - log.info("Message arrived at peer. tradeId={}", message.getTradeId()); + log.info("{} arrived at peer {}. tradeId={}, uid={}", + message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); + } + + @Override + public void onStoredInMailbox() { + log.info("{} stored in mailbox for peer {}. tradeId={}, uid={}", + message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); + } + + @Override + public void onFault(String errorMessage) { + log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}", + message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid(), errorMessage); + } + } + ); + } + + private void sendAckMessage(DisputeMessage disputeMessage, PubKeyRing peersPubKeyRing, + boolean result, @Nullable String errorMessage) { + String tradeId = disputeMessage.getTradeId(); + String uid = disputeMessage.getUid(); + AckMessage ackMessage = new AckMessage(p2PService.getNetworkNode().getNodeAddress(), + AckMessageSourceType.DISPUTE_MESSAGE, + disputeMessage.getClass().getSimpleName(), + uid, + tradeId, + result, + errorMessage); + final NodeAddress peersNodeAddress = disputeMessage.getSenderNodeAddress(); + log.info("Send AckMessage for {} to peer {}. tradeId={}, uid={}", + ackMessage.getSourceMsgClassName(), peersNodeAddress, tradeId, uid); + p2PService.sendEncryptedMailboxMessage( + peersNodeAddress, + peersPubKeyRing, + ackMessage, + new SendMailboxMessageListener() { + @Override + public void onArrived() { + log.info("AckMessage for {} arrived at peer {}. tradeId={}, uid={}", + ackMessage.getSourceMsgClassName(), peersNodeAddress, tradeId, uid); } @Override public void onStoredInMailbox() { - log.info("Message stored in mailbox. tradeId={}", message.getTradeId()); + log.info("AckMessage for {} stored in mailbox for peer {}. tradeId={}, uid={}", + ackMessage.getSourceMsgClassName(), peersNodeAddress, tradeId, uid); } @Override public void onFault(String errorMessage) { - log.error("sendEncryptedMailboxMessage failed. message=" + message); + log.error("AckMessage for {} failed. Peer {}. tradeId={}, uid={}, errorMessage={}", + ackMessage.getSourceMsgClassName(), peersNodeAddress, tradeId, uid, errorMessage); } } ); @@ -549,232 +700,280 @@ public void onFault(String errorMessage) { // arbitrator receives that from trader who opens dispute private void onOpenNewDisputeMessage(OpenNewDisputeMessage openNewDisputeMessage) { + String errorMessage; Dispute dispute = openNewDisputeMessage.getDispute(); + Contract contractFromOpener = dispute.getContract(); + PubKeyRing peersPubKeyRing = dispute.isDisputeOpenerIsBuyer() ? contractFromOpener.getSellerPubKeyRing() : contractFromOpener.getBuyerPubKeyRing(); if (isArbitrator(dispute)) { if (!disputes.contains(dispute)) { final Optional storedDisputeOptional = findDispute(dispute.getTradeId(), dispute.getTraderId()); if (!storedDisputeOptional.isPresent()) { - dispute.setStorage(getDisputeStorage()); + dispute.setStorage(disputeStorage); disputes.add(dispute); - sendPeerOpenedDisputeMessage(dispute); + errorMessage = sendPeerOpenedDisputeMessage(dispute, contractFromOpener, peersPubKeyRing); } else { - log.warn("We got a dispute already open for that trade and trading peer.\n" + - "TradeId = " + dispute.getTradeId()); + errorMessage = "We got a dispute already open for that trade and trading peer.\n" + + "TradeId = " + dispute.getTradeId(); + log.warn(errorMessage); } } else { - log.warn("We got a dispute msg what we have already stored. TradeId = " + dispute.getTradeId()); + errorMessage = "We got a dispute msg what we have already stored. TradeId = " + dispute.getTradeId(); + log.warn(errorMessage); } } else { - log.error("Trader received openNewDisputeMessage. That must never happen."); + errorMessage = "Trader received openNewDisputeMessage. That must never happen."; + log.error(errorMessage); + } + + // We use the DisputeCommunicationMessage not the openNewDisputeMessage for the ACK + ObservableList messages = openNewDisputeMessage.getDispute().getDisputeCommunicationMessages(); + if (!messages.isEmpty()) { + DisputeCommunicationMessage msg = messages.get(0); + PubKeyRing sendersPubKeyRing = dispute.isDisputeOpenerIsBuyer() ? contractFromOpener.getBuyerPubKeyRing() : contractFromOpener.getSellerPubKeyRing(); + sendAckMessage(msg, sendersPubKeyRing, errorMessage == null, errorMessage); } } // not dispute requester receives that from arbitrator private void onPeerOpenedDisputeMessage(PeerOpenedDisputeMessage peerOpenedDisputeMessage) { + String errorMessage; Dispute dispute = peerOpenedDisputeMessage.getDispute(); if (!isArbitrator(dispute)) { if (!disputes.contains(dispute)) { final Optional storedDisputeOptional = findDispute(dispute.getTradeId(), dispute.getTraderId()); if (!storedDisputeOptional.isPresent()) { - dispute.setStorage(getDisputeStorage()); + dispute.setStorage(disputeStorage); disputes.add(dispute); Optional tradeOptional = tradeManager.getTradeById(dispute.getTradeId()); - if (tradeOptional.isPresent()) - tradeOptional.get().setDisputeState(Trade.DisputeState.DISPUTE_STARTED_BY_PEER); + tradeOptional.ifPresent(trade -> trade.setDisputeState(Trade.DisputeState.DISPUTE_STARTED_BY_PEER)); + errorMessage = null; } else { - log.warn("We got a dispute already open for that trade and trading peer.\n" + - "TradeId = " + dispute.getTradeId()); + errorMessage = "We got a dispute already open for that trade and trading peer.\n" + + "TradeId = " + dispute.getTradeId(); + log.warn(errorMessage); } } else { - log.warn("We got a dispute msg what we have already stored. TradeId = " + dispute.getTradeId()); + errorMessage = "We got a dispute msg what we have already stored. TradeId = " + dispute.getTradeId(); + log.warn(errorMessage); } } else { - log.error("Arbitrator received peerOpenedDisputeMessage. That must never happen."); + errorMessage = "Arbitrator received peerOpenedDisputeMessage. That must never happen."; + log.error(errorMessage); } + + // We use the DisputeCommunicationMessage not the peerOpenedDisputeMessage for the ACK + ObservableList messages = peerOpenedDisputeMessage.getDispute().getDisputeCommunicationMessages(); + if (!messages.isEmpty()) { + DisputeCommunicationMessage msg = messages.get(0); + sendAckMessage(msg, dispute.getArbitratorPubKeyRing(), errorMessage == null, errorMessage); + } + + sendAckMessage(peerOpenedDisputeMessage, dispute.getArbitratorPubKeyRing(), errorMessage == null, errorMessage); } - // a trader can receive a msg from the arbitrator or the arbitrator form a trader. Trader to trader is not allowed. + // A trader can receive a msg from the arbitrator or the arbitrator from a trader. Trader to trader is not allowed. private void onDisputeDirectMessage(DisputeCommunicationMessage disputeCommunicationMessage) { - Log.traceCall("disputeCommunicationMessage " + disputeCommunicationMessage); final String tradeId = disputeCommunicationMessage.getTradeId(); - Optional disputeOptional = findDispute(tradeId, disputeCommunicationMessage.getTraderId()); final String uid = disputeCommunicationMessage.getUid(); - if (disputeOptional.isPresent()) { - cleanupRetryMap(uid); - - Dispute dispute = disputeOptional.get(); - if (!dispute.getDisputeCommunicationMessages().contains(disputeCommunicationMessage)) - dispute.addDisputeMessage(disputeCommunicationMessage); - else - log.warn("We got a disputeCommunicationMessage what we have already stored. TradeId = " + tradeId); - } else { + Optional disputeOptional = findDispute(tradeId, disputeCommunicationMessage.getTraderId()); + if (!disputeOptional.isPresent()) { log.debug("We got a disputeCommunicationMessage but we don't have a matching dispute. TradeId = " + tradeId); if (!delayMsgMap.containsKey(uid)) { Timer timer = UserThread.runAfter(() -> onDisputeDirectMessage(disputeCommunicationMessage), 1); delayMsgMap.put(uid, timer); } else { - log.warn("We got a disputeCommunicationMessage after we already repeated to apply the message after a delay. That should never happen. TradeId = " + tradeId); + String msg = "We got a disputeCommunicationMessage after we already repeated to apply the message after a delay. That should never happen. TradeId = " + tradeId; + log.warn(msg); } + return; } + + cleanupRetryMap(uid); + Dispute dispute = disputeOptional.get(); + Tuple2 tuple = getNodeAddressPubKeyRingTuple(dispute); + PubKeyRing receiverPubKeyRing = tuple.second; + + if (!dispute.getDisputeCommunicationMessages().contains(disputeCommunicationMessage)) + dispute.addDisputeCommunicationMessage(disputeCommunicationMessage); + else + log.warn("We got a disputeCommunicationMessage what we have already stored. TradeId = " + tradeId); + + // We never get a errorMessage in that method (only if we cannot resolve the receiverPubKeyRing but then we + // cannot send it anyway) + if (receiverPubKeyRing != null) + sendAckMessage(disputeCommunicationMessage, receiverPubKeyRing, true, null); } // We get that message at both peers. The dispute object is in context of the trader private void onDisputeResultMessage(DisputeResultMessage disputeResultMessage) { + String errorMessage = null; + boolean success = false; + PubKeyRing arbitratorsPubKeyRing = null; DisputeResult disputeResult = disputeResultMessage.getDisputeResult(); - if (!isArbitrator(disputeResult)) { - final String tradeId = disputeResult.getTradeId(); - Optional disputeOptional = findDispute(tradeId, disputeResult.getTraderId()); - final String uid = disputeResultMessage.getUid(); - if (disputeOptional.isPresent()) { - cleanupRetryMap(uid); - - Dispute dispute = disputeOptional.get(); - - DisputeCommunicationMessage disputeCommunicationMessage = disputeResult.getDisputeCommunicationMessage(); - if (!dispute.getDisputeCommunicationMessages().contains(disputeCommunicationMessage)) - dispute.addDisputeMessage(disputeCommunicationMessage); - else - log.warn("We got a dispute mail msg what we have already stored. TradeId = " + disputeCommunicationMessage.getTradeId()); - - dispute.setIsClosed(true); - - if (dispute.disputeResultProperty().get() != null) - log.warn("We got already a dispute result. That should only happen if a dispute needs to be closed " + - "again because the first close did not succeed. TradeId = " + tradeId); - - dispute.setDisputeResult(disputeResult); - - // We need to avoid publishing the tx from both traders as it would create problems with zero confirmation withdrawals - // There would be different transactions if both sign and publish (signers: once buyer+arb, once seller+arb) - // The tx publisher is the winner or in case both get 50% the buyer, as the buyer has more inventive to publish the tx as he receives - // more BTC as he has deposited - final Contract contract = dispute.getContract(); - - boolean isBuyer = keyRing.getPubKeyRing().equals(contract.getBuyerPubKeyRing()); - DisputeResult.Winner publisher = disputeResult.getWinner(); - - // Sometimes the user who receives the trade amount is never online, so we might want to - // let the loser publish the tx. When the winner comes online he gets his funds as it was published by the other peer. - // Default isLoserPublisher is set to false - if (disputeResult.isLoserPublisher()) { - // we invert the logic - if (publisher == DisputeResult.Winner.BUYER) - publisher = DisputeResult.Winner.SELLER; - else if (publisher == DisputeResult.Winner.SELLER) - publisher = DisputeResult.Winner.BUYER; - } - if ((isBuyer && publisher == DisputeResult.Winner.BUYER) - || (!isBuyer && publisher == DisputeResult.Winner.SELLER)) { + if (isArbitrator(disputeResult)) { + log.error("Arbitrator received disputeResultMessage. That must never happen."); + return; + } - final Optional tradeOptional = tradeManager.getTradeById(tradeId); - Transaction payoutTx = null; - if (tradeOptional.isPresent()) { - payoutTx = tradeOptional.get().getPayoutTx(); - } else { - final Optional tradableOptional = closedTradableManager.getTradableById(tradeId); - if (tradableOptional.isPresent() && tradableOptional.get() instanceof Trade) { - payoutTx = ((Trade) tradableOptional.get()).getPayoutTx(); - } + final String tradeId = disputeResult.getTradeId(); + Optional disputeOptional = findDispute(tradeId, disputeResult.getTraderId()); + final String uid = disputeResultMessage.getUid(); + if (!disputeOptional.isPresent()) { + log.debug("We got a dispute result msg but we don't have a matching dispute. " + + "That might happen when we get the disputeResultMessage before the dispute was created. " + + "We try again after 2 sec. to apply the disputeResultMessage. TradeId = " + tradeId); + if (!delayMsgMap.containsKey(uid)) { + // We delay2 sec. to be sure the comm. msg gets added first + Timer timer = UserThread.runAfter(() -> onDisputeResultMessage(disputeResultMessage), 2); + delayMsgMap.put(uid, timer); + } else { + log.warn("We got a dispute result msg after we already repeated to apply the message after a delay. " + + "That should never happen. TradeId = " + tradeId); + } + return; + } + + try { + cleanupRetryMap(uid); + Dispute dispute = disputeOptional.get(); + arbitratorsPubKeyRing = dispute.getArbitratorPubKeyRing(); + DisputeCommunicationMessage disputeCommunicationMessage = disputeResult.getDisputeCommunicationMessage(); + if (!dispute.getDisputeCommunicationMessages().contains(disputeCommunicationMessage)) + dispute.addDisputeCommunicationMessage(disputeCommunicationMessage); + else if (disputeCommunicationMessage != null) + log.warn("We got a dispute mail msg what we have already stored. TradeId = " + disputeCommunicationMessage.getTradeId()); + + dispute.setIsClosed(true); + + if (dispute.disputeResultProperty().get() != null) + log.warn("We got already a dispute result. That should only happen if a dispute needs to be closed " + + "again because the first close did not succeed. TradeId = " + tradeId); + + dispute.setDisputeResult(disputeResult); + + // We need to avoid publishing the tx from both traders as it would create problems with zero confirmation withdrawals + // There would be different transactions if both sign and publish (signers: once buyer+arb, once seller+arb) + // The tx publisher is the winner or in case both get 50% the buyer, as the buyer has more inventive to publish the tx as he receives + // more BTC as he has deposited + final Contract contract = dispute.getContract(); + + boolean isBuyer = keyRing.getPubKeyRing().equals(contract.getBuyerPubKeyRing()); + DisputeResult.Winner publisher = disputeResult.getWinner(); + + // Sometimes the user who receives the trade amount is never online, so we might want to + // let the loser publish the tx. When the winner comes online he gets his funds as it was published by the other peer. + // Default isLoserPublisher is set to false + if (disputeResult.isLoserPublisher()) { + // we invert the logic + if (publisher == DisputeResult.Winner.BUYER) + publisher = DisputeResult.Winner.SELLER; + else if (publisher == DisputeResult.Winner.SELLER) + publisher = DisputeResult.Winner.BUYER; + } + + if ((isBuyer && publisher == DisputeResult.Winner.BUYER) + || (!isBuyer && publisher == DisputeResult.Winner.SELLER)) { + + final Optional tradeOptional = tradeManager.getTradeById(tradeId); + Transaction payoutTx = null; + if (tradeOptional.isPresent()) { + payoutTx = tradeOptional.get().getPayoutTx(); + } else { + final Optional tradableOptional = closedTradableManager.getTradableById(tradeId); + if (tradableOptional.isPresent() && tradableOptional.get() instanceof Trade) { + payoutTx = ((Trade) tradableOptional.get()).getPayoutTx(); } + } - if (payoutTx == null) { - if (dispute.getDepositTxSerialized() != null) { - try { - log.debug("do payout Transaction "); - byte[] multiSigPubKey = isBuyer ? contract.getBuyerMultiSigPubKey() : contract.getSellerMultiSigPubKey(); - DeterministicKey multiSigKeyPair = walletService.getMultiSigKeyPair(dispute.getTradeId(), multiSigPubKey); - Transaction signedDisputedPayoutTx = tradeWalletService.traderSignAndFinalizeDisputedPayoutTx( - dispute.getDepositTxSerialized(), - disputeResult.getArbitratorSignature(), - disputeResult.getBuyerPayoutAmount(), - disputeResult.getSellerPayoutAmount(), - contract.getBuyerPayoutAddressString(), - contract.getSellerPayoutAddressString(), - multiSigKeyPair, - contract.getBuyerMultiSigPubKey(), - contract.getSellerMultiSigPubKey(), - disputeResult.getArbitratorPubKey() - ); - Transaction committedDisputedPayoutTx = tradeWalletService.addTxToWallet(signedDisputedPayoutTx); - log.debug("broadcast committedDisputedPayoutTx"); - tradeWalletService.broadcastTx(committedDisputedPayoutTx, new FutureCallback() { - @Override - public void onSuccess(Transaction transaction) { - log.debug("BroadcastTx succeeded. Transaction:" + transaction); - - // after successful publish we send peer the tx - - dispute.setDisputePayoutTxId(transaction.getHashAsString()); - sendPeerPublishedPayoutTxMessage(transaction, dispute, contract); - - // set state after payout as we call swapTradeEntryToAvailableEntry - if (tradeManager.getTradeById(dispute.getTradeId()).isPresent()) - tradeManager.closeDisputedTrade(dispute.getTradeId()); - else { - Optional openOfferOptional = openOfferManager.getOpenOfferById(dispute.getTradeId()); - if (openOfferOptional.isPresent()) - openOfferManager.closeOpenOffer(openOfferOptional.get().getOffer()); - } - } - - @Override - public void onFailure(@NotNull Throwable t) { - log.error(t.getMessage()); - } - }, 15); - } catch (AddressFormatException | WalletException | TransactionVerificationException e) { - e.printStackTrace(); - log.error("Error at traderSignAndFinalizeDisputedPayoutTx " + e.getMessage()); - throw new RuntimeException("Error at traderSignAndFinalizeDisputedPayoutTx " + e.toString()); + if (payoutTx == null) { + if (dispute.getDepositTxSerialized() != null) { + byte[] multiSigPubKey = isBuyer ? contract.getBuyerMultiSigPubKey() : contract.getSellerMultiSigPubKey(); + DeterministicKey multiSigKeyPair = walletService.getMultiSigKeyPair(dispute.getTradeId(), multiSigPubKey); + Transaction signedDisputedPayoutTx = tradeWalletService.traderSignAndFinalizeDisputedPayoutTx( + dispute.getDepositTxSerialized(), + disputeResult.getArbitratorSignature(), + disputeResult.getBuyerPayoutAmount(), + disputeResult.getSellerPayoutAmount(), + contract.getBuyerPayoutAddressString(), + contract.getSellerPayoutAddressString(), + multiSigKeyPair, + contract.getBuyerMultiSigPubKey(), + contract.getSellerMultiSigPubKey(), + disputeResult.getArbitratorPubKey() + ); + Transaction committedDisputedPayoutTx = tradeWalletService.addTxToWallet(signedDisputedPayoutTx); + tradeWalletService.broadcastTx(committedDisputedPayoutTx, new FutureCallback() { + @Override + public void onSuccess(Transaction transaction) { + // after successful publish we send peer the tx + + dispute.setDisputePayoutTxId(transaction.getHashAsString()); + sendPeerPublishedPayoutTxMessage(transaction, dispute, contract); + + // set state after payout as we call swapTradeEntryToAvailableEntry + if (tradeManager.getTradeById(dispute.getTradeId()).isPresent()) + tradeManager.closeDisputedTrade(dispute.getTradeId()); + else { + Optional openOfferOptional = openOfferManager.getOpenOfferById(dispute.getTradeId()); + openOfferOptional.ifPresent(openOffer -> openOfferManager.closeOpenOffer(openOffer.getOffer())); + } } - } else { - log.warn("DepositTx is null. TradeId = " + tradeId); - } + + @Override + public void onFailure(@NotNull Throwable t) { + log.error(t.getMessage()); + } + }, 15); + + success = true; } else { - log.warn("We got already a payout tx. That might be the case if the other peer did not get the " + - "payout tx and opened a dispute. TradeId = " + tradeId); - dispute.setDisputePayoutTxId(payoutTx.getHashAsString()); - sendPeerPublishedPayoutTxMessage(payoutTx, dispute, contract); + errorMessage = "DepositTx is null. TradeId = " + tradeId; + log.warn(errorMessage); + success = false; } } else { - log.trace("We don't publish the tx as we are not the winning party."); - // Clean up tangling trades - if (dispute.disputeResultProperty().get() != null && - dispute.isClosed() && - tradeManager.getTradeById(dispute.getTradeId()).isPresent()) - tradeManager.closeDisputedTrade(dispute.getTradeId()); + log.warn("We got already a payout tx. That might be the case if the other peer did not get the " + + "payout tx and opened a dispute. TradeId = " + tradeId); + dispute.setDisputePayoutTxId(payoutTx.getHashAsString()); + sendPeerPublishedPayoutTxMessage(payoutTx, dispute, contract); + + success = true; } + } else { - log.debug("We got a dispute result msg but we don't have a matching dispute. " + - "That might happen when we get the disputeResultMessage before the dispute was created. " + - "We try again after 2 sec. to apply the disputeResultMessage. TradeId = " + tradeId); - if (!delayMsgMap.containsKey(uid)) { - // We delay2 sec. to be sure the comm. msg gets added first - Timer timer = UserThread.runAfter(() -> onDisputeResultMessage(disputeResultMessage), 2); - delayMsgMap.put(uid, timer); - } else { - log.warn("We got a dispute result msg after we already repeated to apply the message after a delay. " + - "That should never happen. TradeId = " + tradeId); + log.trace("We don't publish the tx as we are not the winning party."); + // Clean up tangling trades + if (dispute.disputeResultProperty().get() != null && + dispute.isClosed() && + tradeManager.getTradeById(dispute.getTradeId()).isPresent()) { + tradeManager.closeDisputedTrade(dispute.getTradeId()); } + + success = true; + } + } catch (AddressFormatException | WalletException | TransactionVerificationException e) { + e.printStackTrace(); + errorMessage = "Error at traderSignAndFinalizeDisputedPayoutTx " + e.toString(); + log.error(errorMessage); + success = false; + throw new RuntimeException(errorMessage); + } finally { + if (arbitratorsPubKeyRing != null) { + // We use the disputeCommunicationMessage as we only persist those not the disputeResultMessage. + // If we would use the disputeResultMessage we could not lookup for the msg when we receive the AckMessage. + DisputeCommunicationMessage disputeCommunicationMessage = disputeResultMessage.getDisputeResult().getDisputeCommunicationMessage(); + sendAckMessage(disputeCommunicationMessage, arbitratorsPubKeyRing, success, errorMessage); } - } else { - log.error("Arbitrator received disputeResultMessage. That must never happen."); } } - // losing trader or in case of 50/50 the seller gets the tx sent from the winner or buyer + // Losing trader or in case of 50/50 the seller gets the tx sent from the winner or buyer private void onDisputedPayoutTxMessage(PeerPublishedDisputePayoutTxMessage peerPublishedDisputePayoutTxMessage) { final String uid = peerPublishedDisputePayoutTxMessage.getUid(); final String tradeId = peerPublishedDisputePayoutTxMessage.getTradeId(); Optional disputeOptional = findOwnDispute(tradeId); - if (disputeOptional.isPresent()) { - cleanupRetryMap(uid); - - Transaction walletTx = tradeWalletService.addTxToWallet(peerPublishedDisputePayoutTxMessage.getTransaction()); - disputeOptional.get().setDisputePayoutTxId(walletTx.getHashAsString()); - BtcWalletService.printTx("Disputed payoutTx received from peer", walletTx); - } else { + if (!disputeOptional.isPresent()) { log.debug("We got a peerPublishedPayoutTxMessage but we don't have a matching dispute. TradeId = " + tradeId); if (!delayMsgMap.containsKey(uid)) { // We delay 3 sec. to be sure the close msg gets added first @@ -784,7 +983,22 @@ private void onDisputedPayoutTxMessage(PeerPublishedDisputePayoutTxMessage peerP log.warn("We got a peerPublishedPayoutTxMessage after we already repeated to apply the message after a delay. " + "That should never happen. TradeId = " + tradeId); } + return; } + + Dispute dispute = disputeOptional.get(); + final Contract contract = dispute.getContract(); + PubKeyRing ownPubKeyRing = keyRing.getPubKeyRing(); + boolean isBuyer = ownPubKeyRing.equals(contract.getBuyerPubKeyRing()); + PubKeyRing peersPubKeyRing = isBuyer ? contract.getSellerPubKeyRing() : contract.getBuyerPubKeyRing(); + + cleanupRetryMap(uid); + Transaction walletTx = tradeWalletService.addTxToWallet(peerPublishedDisputePayoutTxMessage.getTransaction()); + dispute.setDisputePayoutTxId(walletTx.getHashAsString()); + BtcWalletService.printTx("Disputed payoutTx received from peer", walletTx); + + // We can only send the ack msg if we have the peersPubKeyRing which requires the dispute + sendAckMessage(peerPublishedDisputePayoutTxMessage, peersPubKeyRing, true, null); } @@ -835,6 +1049,26 @@ public String getNrOfDisputes(boolean isBuyer, Contract contract) { // Utils /////////////////////////////////////////////////////////////////////////////////////////// + + private Tuple2 getNodeAddressPubKeyRingTuple(Dispute dispute) { + PubKeyRing receiverPubKeyRing = null; + NodeAddress peerNodeAddress = null; + if (isTrader(dispute)) { + receiverPubKeyRing = dispute.getArbitratorPubKeyRing(); + peerNodeAddress = dispute.getContract().getArbitratorNodeAddress(); + } else if (isArbitrator(dispute)) { + receiverPubKeyRing = dispute.getTraderPubKeyRing(); + Contract contract = dispute.getContract(); + if (contract.getBuyerPubKeyRing().equals(receiverPubKeyRing)) + peerNodeAddress = contract.getBuyerNodeAddress(); + else + peerNodeAddress = contract.getSellerNodeAddress(); + } else { + log.error("That must not happen. Trader cannot communicate to other trader."); + } + return new Tuple2<>(peerNodeAddress, receiverPubKeyRing); + } + private Optional findDispute(String tradeId, int traderId) { return disputes.stream().filter(e -> e.getTradeId().equals(tradeId) && e.getTraderId() == traderId).findAny(); } diff --git a/src/main/java/bisq/core/arbitration/DisputeResult.java b/src/main/java/bisq/core/arbitration/DisputeResult.java index 57936240..ec5ec02c 100644 --- a/src/main/java/bisq/core/arbitration/DisputeResult.java +++ b/src/main/java/bisq/core/arbitration/DisputeResult.java @@ -21,6 +21,7 @@ import bisq.common.proto.ProtoUtil; import bisq.common.proto.network.NetworkPayload; +import bisq.common.util.Utilities; import io.bisq.generated.protobuffer.PB; @@ -233,4 +234,25 @@ public void setCloseDate(Date closeDate) { public Date getCloseDate() { return new Date(closeDate); } + + @Override + public String toString() { + return "DisputeResult{" + + "\n tradeId='" + tradeId + '\'' + + ",\n traderId=" + traderId + + ",\n winner=" + winner + + ",\n reasonOrdinal=" + reasonOrdinal + + ",\n tamperProofEvidenceProperty=" + tamperProofEvidenceProperty + + ",\n idVerificationProperty=" + idVerificationProperty + + ",\n screenCastProperty=" + screenCastProperty + + ",\n summaryNotesProperty=" + summaryNotesProperty + + ",\n disputeCommunicationMessage=" + disputeCommunicationMessage + + ",\n arbitratorSignature=" + Utilities.bytesAsHexString(arbitratorSignature) + + ",\n buyerPayoutAmount=" + buyerPayoutAmount + + ",\n sellerPayoutAmount=" + sellerPayoutAmount + + ",\n arbitratorPubKey=" + Utilities.bytesAsHexString(arbitratorPubKey) + + ",\n closeDate=" + closeDate + + ",\n isLoserPublisher=" + isLoserPublisher + + "\n}"; + } } diff --git a/src/main/java/bisq/core/arbitration/messages/DisputeCommunicationMessage.java b/src/main/java/bisq/core/arbitration/messages/DisputeCommunicationMessage.java index 34484d71..64f69606 100644 --- a/src/main/java/bisq/core/arbitration/messages/DisputeCommunicationMessage.java +++ b/src/main/java/bisq/core/arbitration/messages/DisputeCommunicationMessage.java @@ -27,24 +27,36 @@ import javafx.beans.property.BooleanProperty; import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.ReadOnlyStringProperty; import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; import java.util.ArrayList; +import java.util.Date; import java.util.List; import java.util.Optional; +import java.util.UUID; import java.util.stream.Collectors; +import java.lang.ref.WeakReference; + import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; -import lombok.ToString; +import lombok.extern.slf4j.Slf4j; import javax.annotation.Nullable; -@EqualsAndHashCode(callSuper = true) -@ToString +@EqualsAndHashCode(callSuper = true, exclude = {"listener"}) @Getter +@Slf4j public final class DisputeCommunicationMessage extends DisputeMessage { + + public interface Listener { + void onMessageStateChanged(); + } + private final String tradeId; private final int traderId; private final boolean senderIsTrader; @@ -57,28 +69,31 @@ public final class DisputeCommunicationMessage extends DisputeMessage { private final BooleanProperty arrivedProperty; private final BooleanProperty storedInMailboxProperty; + private final BooleanProperty acknowledgedProperty; + private final StringProperty sendMessageErrorProperty; + private final StringProperty ackErrorProperty; + + transient private WeakReference listener; public DisputeCommunicationMessage(String tradeId, int traderId, boolean senderIsTrader, String message, - @Nullable List attachments, - NodeAddress senderNodeAddress, - long date, - boolean arrived, - boolean storedInMailbox, - String uid) { + NodeAddress senderNodeAddress) { this(tradeId, traderId, senderIsTrader, message, - attachments, + null, senderNodeAddress, - date, - arrived, - storedInMailbox, - uid, - Version.getP2PMessageVersion()); + new Date().getTime(), + false, + false, + UUID.randomUUID().toString(), + Version.getP2PMessageVersion(), + false, + null, + null); } @@ -96,7 +111,10 @@ private DisputeCommunicationMessage(String tradeId, boolean arrived, boolean storedInMailbox, String uid, - int messageVersion) { + int messageVersion, + boolean acknowledged, + @Nullable String sendMessageError, + @Nullable String ackError) { super(messageVersion, uid); this.tradeId = tradeId; this.traderId = traderId; @@ -107,24 +125,31 @@ private DisputeCommunicationMessage(String tradeId, this.date = date; arrivedProperty = new SimpleBooleanProperty(arrived); storedInMailboxProperty = new SimpleBooleanProperty(storedInMailbox); + acknowledgedProperty = new SimpleBooleanProperty(acknowledged); + sendMessageErrorProperty = new SimpleStringProperty(sendMessageError); + ackErrorProperty = new SimpleStringProperty(ackError); + notifyChangeListener(); } @Override public PB.NetworkEnvelope toProtoNetworkEnvelope() { + PB.DisputeCommunicationMessage.Builder builder = PB.DisputeCommunicationMessage.newBuilder() + .setTradeId(tradeId) + .setTraderId(traderId) + .setSenderIsTrader(senderIsTrader) + .setMessage(message) + .addAllAttachments(attachments.stream().map(Attachment::toProtoMessage).collect(Collectors.toList())) + .setSenderNodeAddress(senderNodeAddress.toProtoMessage()) + .setDate(date) + .setArrived(arrivedProperty.get()) + .setStoredInMailbox(storedInMailboxProperty.get()) + .setIsSystemMessage(isSystemMessage) + .setUid(uid) + .setAcknowledged(acknowledgedProperty.get()); + Optional.ofNullable(sendMessageErrorProperty.get()).ifPresent(builder::setSendMessageError); + Optional.ofNullable(ackErrorProperty.get()).ifPresent(builder::setAckError); return getNetworkEnvelopeBuilder() - .setDisputeCommunicationMessage(PB.DisputeCommunicationMessage.newBuilder() - .setTradeId(tradeId) - .setTraderId(traderId) - .setSenderIsTrader(senderIsTrader) - .setMessage(message) - .addAllAttachments(attachments.stream().map(Attachment::toProtoMessage).collect(Collectors.toList())) - .setSenderNodeAddress(senderNodeAddress.toProtoMessage()) - .setDate(date) - .setArrived(arrivedProperty.get()) - .setStoredInMailbox(storedInMailboxProperty.get()) - .setIsSystemMessage(isSystemMessage) - .setUid(uid) - ) + .setDisputeCommunicationMessage(builder) .build(); } @@ -140,7 +165,10 @@ public static DisputeCommunicationMessage fromProto(PB.DisputeCommunicationMessa proto.getArrived(), proto.getStoredInMailbox(), proto.getUid(), - messageVersion); + messageVersion, + proto.getAcknowledged(), + proto.getSendMessageError().isEmpty() ? null : proto.getSendMessageError(), + proto.getAckError().isEmpty() ? null : proto.getAckError()); disputeCommunicationMessage.setSystemMessage(proto.getIsSystemMessage()); return disputeCommunicationMessage; } @@ -148,22 +176,9 @@ public static DisputeCommunicationMessage fromProto(PB.DisputeCommunicationMessa public static DisputeCommunicationMessage fromPayloadProto(PB.DisputeCommunicationMessage proto) { // We have the case that an envelope got wrapped into a payload. // We don't check the message version here as it was checked in the carrier envelope already (in connection class) - // Payloads dont have a message version and are also used for persistence + // Payloads don't have a message version and are also used for persistence // We set the value to -1 to indicate it is set but irrelevant - final DisputeCommunicationMessage disputeCommunicationMessage = new DisputeCommunicationMessage( - proto.getTradeId(), - proto.getTraderId(), - proto.getSenderIsTrader(), - proto.getMessage(), - new ArrayList<>(proto.getAttachmentsList().stream().map(Attachment::fromProto).collect(Collectors.toList())), - NodeAddress.fromProto(proto.getSenderNodeAddress()), - proto.getDate(), - proto.getArrived(), - proto.getStoredInMailbox(), - proto.getUid(), - -1); - disputeCommunicationMessage.setSystemMessage(proto.getIsSystemMessage()); - return disputeCommunicationMessage; + return fromProto(proto, -1); } @@ -177,19 +192,82 @@ public void addAllAttachments(List attachments) { public void setArrived(@SuppressWarnings("SameParameterValue") boolean arrived) { this.arrivedProperty.set(arrived); - } - - public void setStoredInMailbox(@SuppressWarnings("SameParameterValue") boolean storedInMailbox) { - this.storedInMailboxProperty.set(storedInMailbox); + notifyChangeListener(); } public ReadOnlyBooleanProperty arrivedProperty() { return arrivedProperty; } + + public void setStoredInMailbox(@SuppressWarnings("SameParameterValue") boolean storedInMailbox) { + this.storedInMailboxProperty.set(storedInMailbox); + notifyChangeListener(); + } + public ReadOnlyBooleanProperty storedInMailboxProperty() { return storedInMailboxProperty; } + public void setAcknowledged(boolean acknowledged) { + this.acknowledgedProperty.set(acknowledged); + notifyChangeListener(); + } + + public ReadOnlyBooleanProperty acknowledgedProperty() { + return acknowledgedProperty; + } + + public void setSendMessageError(String sendMessageError) { + this.sendMessageErrorProperty.set(sendMessageError); + notifyChangeListener(); + } + + public ReadOnlyStringProperty sendMessageErrorProperty() { + return sendMessageErrorProperty; + } + + public void setAckError(String ackError) { + this.ackErrorProperty.set(ackError); + notifyChangeListener(); + } + public ReadOnlyStringProperty ackErrorProperty() { + return ackErrorProperty; + } + + @Override + public String getTradeId() { + return tradeId; + } + + public void addWeakMessageStateListener(Listener listener) { + this.listener = new WeakReference<>(listener); + } + + private void notifyChangeListener() { + if (listener != null && listener.get() != null) + listener.get().onMessageStateChanged(); + } + + @Override + public String toString() { + return "DisputeCommunicationMessage{" + + "\n tradeId='" + tradeId + '\'' + + ",\n traderId=" + traderId + + ",\n senderIsTrader=" + senderIsTrader + + ",\n message='" + message + '\'' + + ",\n attachments=" + attachments + + ",\n senderNodeAddress=" + senderNodeAddress + + ",\n date=" + date + + ",\n isSystemMessage=" + isSystemMessage + + ",\n arrivedProperty=" + arrivedProperty + + ",\n storedInMailboxProperty=" + storedInMailboxProperty + + ",\n DisputeCommunicationMessage.uid='" + uid + '\'' + + ",\n messageVersion=" + messageVersion + + ",\n acknowledgedProperty=" + acknowledgedProperty + + ",\n sendMessageErrorProperty=" + sendMessageErrorProperty + + ",\n ackErrorProperty=" + ackErrorProperty + + "\n} " + super.toString(); + } } diff --git a/src/main/java/bisq/core/arbitration/messages/DisputeMessage.java b/src/main/java/bisq/core/arbitration/messages/DisputeMessage.java index b2d6734d..1387e9c6 100644 --- a/src/main/java/bisq/core/arbitration/messages/DisputeMessage.java +++ b/src/main/java/bisq/core/arbitration/messages/DisputeMessage.java @@ -18,21 +18,31 @@ package bisq.core.arbitration.messages; import bisq.network.p2p.MailboxMessage; +import bisq.network.p2p.UidMessage; import bisq.common.proto.network.NetworkEnvelope; import lombok.EqualsAndHashCode; import lombok.Getter; -import lombok.ToString; -@ToString @EqualsAndHashCode(callSuper = true) @Getter -public abstract class DisputeMessage extends NetworkEnvelope implements MailboxMessage { +public abstract class DisputeMessage extends NetworkEnvelope implements MailboxMessage, UidMessage { protected final String uid; public DisputeMessage(int messageVersion, String uid) { super(messageVersion); this.uid = uid; } + + public abstract String getTradeId(); + + + @Override + public String toString() { + return "DisputeMessage{" + + "\n uid='" + uid + '\'' + + ",\n messageVersion=" + messageVersion + + "\n} " + super.toString(); + } } diff --git a/src/main/java/bisq/core/arbitration/messages/DisputeResultMessage.java b/src/main/java/bisq/core/arbitration/messages/DisputeResultMessage.java index b916f141..e21a50e6 100644 --- a/src/main/java/bisq/core/arbitration/messages/DisputeResultMessage.java +++ b/src/main/java/bisq/core/arbitration/messages/DisputeResultMessage.java @@ -76,4 +76,19 @@ public static DisputeResultMessage fromProto(PB.DisputeResultMessage proto, int proto.getUid(), messageVersion); } + + @Override + public String getTradeId() { + return disputeResult.getTradeId(); + } + + @Override + public String toString() { + return "DisputeResultMessage{" + + "\n disputeResult=" + disputeResult + + ",\n senderNodeAddress=" + senderNodeAddress + + ",\n DisputeResultMessage.uid='" + uid + '\'' + + ",\n messageVersion=" + messageVersion + + "\n} " + super.toString(); + } } diff --git a/src/main/java/bisq/core/arbitration/messages/OpenNewDisputeMessage.java b/src/main/java/bisq/core/arbitration/messages/OpenNewDisputeMessage.java index 8b3c703e..00a10107 100644 --- a/src/main/java/bisq/core/arbitration/messages/OpenNewDisputeMessage.java +++ b/src/main/java/bisq/core/arbitration/messages/OpenNewDisputeMessage.java @@ -76,4 +76,19 @@ public static OpenNewDisputeMessage fromProto(PB.OpenNewDisputeMessage proto, proto.getUid(), messageVersion); } + + @Override + public String getTradeId() { + return dispute.getTradeId(); + } + + @Override + public String toString() { + return "OpenNewDisputeMessage{" + + "\n dispute=" + dispute + + ",\n senderNodeAddress=" + senderNodeAddress + + ",\n OpenNewDisputeMessage.uid='" + uid + '\'' + + ",\n messageVersion=" + messageVersion + + "\n} " + super.toString(); + } } diff --git a/src/main/java/bisq/core/arbitration/messages/PeerOpenedDisputeMessage.java b/src/main/java/bisq/core/arbitration/messages/PeerOpenedDisputeMessage.java index d219afed..8e215143 100644 --- a/src/main/java/bisq/core/arbitration/messages/PeerOpenedDisputeMessage.java +++ b/src/main/java/bisq/core/arbitration/messages/PeerOpenedDisputeMessage.java @@ -74,4 +74,19 @@ public static PeerOpenedDisputeMessage fromProto(PB.PeerOpenedDisputeMessage pro proto.getUid(), messageVersion); } + + @Override + public String getTradeId() { + return dispute.getTradeId(); + } + + @Override + public String toString() { + return "PeerOpenedDisputeMessage{" + + "\n dispute=" + dispute + + ",\n senderNodeAddress=" + senderNodeAddress + + ",\n PeerOpenedDisputeMessage.uid='" + uid + '\'' + + ",\n messageVersion=" + messageVersion + + "\n} " + super.toString(); + } } diff --git a/src/main/java/bisq/core/arbitration/messages/PeerPublishedDisputePayoutTxMessage.java b/src/main/java/bisq/core/arbitration/messages/PeerPublishedDisputePayoutTxMessage.java index 9d5a6f0f..4774a8af 100644 --- a/src/main/java/bisq/core/arbitration/messages/PeerPublishedDisputePayoutTxMessage.java +++ b/src/main/java/bisq/core/arbitration/messages/PeerPublishedDisputePayoutTxMessage.java @@ -20,6 +20,7 @@ import bisq.network.p2p.NodeAddress; import bisq.common.app.Version; +import bisq.common.util.Utilities; import io.bisq.generated.protobuffer.PB; @@ -80,4 +81,20 @@ public static PeerPublishedDisputePayoutTxMessage fromProto(PB.PeerPublishedDisp proto.getUid(), messageVersion); } + + @Override + public String getTradeId() { + return tradeId; + } + + @Override + public String toString() { + return "PeerPublishedDisputePayoutTxMessage{" + + "\n transaction=" + Utilities.bytesAsHexString(transaction) + + ",\n tradeId='" + tradeId + '\'' + + ",\n senderNodeAddress=" + senderNodeAddress + + ",\n PeerPublishedDisputePayoutTxMessage.uid='" + uid + '\'' + + ",\n messageVersion=" + messageVersion + + "\n} " + super.toString(); + } } diff --git a/src/main/java/bisq/core/dao/node/full/network/FullNodeNetworkService.java b/src/main/java/bisq/core/dao/node/full/network/FullNodeNetworkService.java index 31d64dd4..cf4148eb 100644 --- a/src/main/java/bisq/core/dao/node/full/network/FullNodeNetworkService.java +++ b/src/main/java/bisq/core/dao/node/full/network/FullNodeNetworkService.java @@ -126,10 +126,10 @@ public void onAwakeFromStandby() { /////////////////////////////////////////////////////////////////////////////////////////// @Override - public void onMessage(NetworkEnvelope networkEnvelop, Connection connection) { - if (networkEnvelop instanceof GetBsqBlocksRequest) { + public void onMessage(NetworkEnvelope networkEnvelope, Connection connection) { + if (networkEnvelope instanceof GetBsqBlocksRequest) { // We received a GetBsqBlocksRequest from a liteNode - Log.traceCall(networkEnvelop.toString() + "\n\tconnection=" + connection); + Log.traceCall(networkEnvelope.toString() + "\n\tconnection=" + connection); if (!stopped) { final String uid = connection.getUid(); if (!getBlocksRequestHandlers.containsKey(uid)) { @@ -155,7 +155,7 @@ public void onFault(String errorMessage, @Nullable Connection connection) { } }); getBlocksRequestHandlers.put(uid, requestHandler); - requestHandler.onGetBsqBlocksRequest((GetBsqBlocksRequest) networkEnvelop, connection); + requestHandler.onGetBsqBlocksRequest((GetBsqBlocksRequest) networkEnvelope, connection); } else { log.warn("We have already a GetDataRequestHandler for that connection started. " + "We start a cleanup timer if the handler has not closed by itself in between 2 minutes."); diff --git a/src/main/java/bisq/core/dao/node/lite/LiteNode.java b/src/main/java/bisq/core/dao/node/lite/LiteNode.java index 9ab293d5..c9922689 100644 --- a/src/main/java/bisq/core/dao/node/lite/LiteNode.java +++ b/src/main/java/bisq/core/dao/node/lite/LiteNode.java @@ -81,6 +81,7 @@ public LiteNode(ReadableBsqBlockChain readableBsqBlockChain, @Override public void onAllServicesInitialized(ErrorMessageHandler errorMessageHandler) { super.onInitialized(); + liteNodeNetworkService.init(); } diff --git a/src/main/java/bisq/core/dao/node/lite/network/LiteNodeNetworkService.java b/src/main/java/bisq/core/dao/node/lite/network/LiteNodeNetworkService.java index 93a8d00b..3298baef 100644 --- a/src/main/java/bisq/core/dao/node/lite/network/LiteNodeNetworkService.java +++ b/src/main/java/bisq/core/dao/node/lite/network/LiteNodeNetworkService.java @@ -217,9 +217,9 @@ public void onAwakeFromStandby() { /////////////////////////////////////////////////////////////////////////////////////////// @Override - public void onMessage(NetworkEnvelope networkEnvelop, Connection connection) { - if (networkEnvelop instanceof NewBsqBlockBroadcastMessage) { - listeners.forEach(listener -> listener.onNewBlockReceived((NewBsqBlockBroadcastMessage) networkEnvelop)); + public void onMessage(NetworkEnvelope networkEnvelope, Connection connection) { + if (networkEnvelope instanceof NewBsqBlockBroadcastMessage) { + listeners.forEach(listener -> listener.onNewBlockReceived((NewBsqBlockBroadcastMessage) networkEnvelope)); } } diff --git a/src/main/java/bisq/core/dao/node/lite/network/RequestBlocksHandler.java b/src/main/java/bisq/core/dao/node/lite/network/RequestBlocksHandler.java index 82f2bb22..c82e19a8 100644 --- a/src/main/java/bisq/core/dao/node/lite/network/RequestBlocksHandler.java +++ b/src/main/java/bisq/core/dao/node/lite/network/RequestBlocksHandler.java @@ -166,12 +166,12 @@ public void onFailure(@NotNull Throwable throwable) { /////////////////////////////////////////////////////////////////////////////////////////// @Override - public void onMessage(NetworkEnvelope networkEnvelop, Connection connection) { - if (networkEnvelop instanceof GetBsqBlocksResponse) { + public void onMessage(NetworkEnvelope networkEnvelope, Connection connection) { + if (networkEnvelope instanceof GetBsqBlocksResponse) { if (connection.getPeersNodeAddressOptional().isPresent() && connection.getPeersNodeAddressOptional().get().equals(nodeAddress)) { - Log.traceCall(networkEnvelop.toString() + "\n\tconnection=" + connection); + Log.traceCall(networkEnvelope.toString() + "\n\tconnection=" + connection); if (!stopped) { - GetBsqBlocksResponse getBsqBlocksResponse = (GetBsqBlocksResponse) networkEnvelop; + GetBsqBlocksResponse getBsqBlocksResponse = (GetBsqBlocksResponse) networkEnvelope; if (getBsqBlocksResponse.getRequestNonce() == nonce) { stopTimeoutTimer(); checkArgument(connection.getPeersNodeAddressOptional().isPresent(), diff --git a/src/main/java/bisq/core/network/MessageState.java b/src/main/java/bisq/core/network/MessageState.java new file mode 100644 index 00000000..25a36729 --- /dev/null +++ b/src/main/java/bisq/core/network/MessageState.java @@ -0,0 +1,27 @@ +/* + * This file is part of Bisq. + * + * Bisq 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. + * + * Bisq 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 Bisq. If not, see . + */ + +package bisq.core.network; + +public enum MessageState { + UNDEFINED, + SENT, + ARRIVED, + STORED_IN_MAILBOX, + ACKNOWLEDGED, + FAILED +} diff --git a/src/main/java/bisq/core/offer/OpenOfferManager.java b/src/main/java/bisq/core/offer/OpenOfferManager.java index 18c8da15..b13f55c1 100644 --- a/src/main/java/bisq/core/offer/OpenOfferManager.java +++ b/src/main/java/bisq/core/offer/OpenOfferManager.java @@ -33,6 +33,8 @@ import bisq.core.user.User; import bisq.core.util.Validator; +import bisq.network.p2p.AckMessage; +import bisq.network.p2p.AckMessageSourceType; import bisq.network.p2p.BootstrapListener; import bisq.network.p2p.DecryptedDirectMessageListener; import bisq.network.p2p.DecryptedMessageWithPubKey; @@ -45,6 +47,7 @@ import bisq.common.UserThread; import bisq.common.app.Log; import bisq.common.crypto.KeyRing; +import bisq.common.crypto.PubKeyRing; import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ResultHandler; import bisq.common.proto.network.NetworkEnvelope; @@ -218,19 +221,31 @@ public void removeOpenOffers(List openOffers, @Nullable Runnable comp UserThread.runAfter(completeHandler::run, size * 200 + 500, TimeUnit.MILLISECONDS); } + /////////////////////////////////////////////////////////////////////////////////////////// // DecryptedDirectMessageListener implementation /////////////////////////////////////////////////////////////////////////////////////////// - @Override public void onDirectMessage(DecryptedMessageWithPubKey decryptedMessageWithPubKey, NodeAddress peerNodeAddress) { // Handler for incoming offer availability requests // We get an encrypted message but don't do the signature check as we don't know the peer yet. // A basic sig check is in done also at decryption time - NetworkEnvelope networkEnvelop = decryptedMessageWithPubKey.getNetworkEnvelope(); - if (networkEnvelop instanceof OfferAvailabilityRequest) - handleOfferAvailabilityRequest((OfferAvailabilityRequest) networkEnvelop, peerNodeAddress); + NetworkEnvelope networkEnvelope = decryptedMessageWithPubKey.getNetworkEnvelope(); + if (networkEnvelope instanceof OfferAvailabilityRequest) { + handleOfferAvailabilityRequest((OfferAvailabilityRequest) networkEnvelope, peerNodeAddress); + } else if (networkEnvelope instanceof AckMessage) { + AckMessage ackMessage = (AckMessage) networkEnvelope; + if (ackMessage.getSourceType() == AckMessageSourceType.OFFER_MESSAGE) { + if (ackMessage.isSuccess()) { + log.info("Received AckMessage for {} with offerId {} and uid {}", + ackMessage.getSourceMsgClassName(), ackMessage.getSourceId(), ackMessage.getSourceUid()); + } else { + log.warn("Received AckMessage with error state for {} with offerId {} and errorMessage={}", + ackMessage.getSourceMsgClassName(), ackMessage.getSourceId(), ackMessage.getErrorMessage()); + } + } + } } @@ -499,85 +514,144 @@ public Optional getOpenOfferById(String offerId) { // OfferPayload Availability /////////////////////////////////////////////////////////////////////////////////////////// - private void handleOfferAvailabilityRequest(OfferAvailabilityRequest message, NodeAddress sender) { - log.trace("handleNewMessage: message = " + message.getClass().getSimpleName() + " from " + sender); - if (p2PService.isBootstrapped()) { - if (!stopped) { - try { - Validator.nonEmptyStringOf(message.offerId); - checkNotNull(message.getPubKeyRing()); - } catch (Throwable t) { - log.warn("Invalid message " + message.toString()); - return; - } + private void handleOfferAvailabilityRequest(OfferAvailabilityRequest request, NodeAddress peer) { + log.info("Received OfferAvailabilityRequest from {} with offerId {} and uid {}", + peer, request.getOfferId(), request.getUid()); + + boolean result = false; + String errorMessage = null; + + if (!p2PService.isBootstrapped()) { + errorMessage = "We got a handleOfferAvailabilityRequest but we have not bootstrapped yet."; + log.info(errorMessage); + sendAckMessage(request, peer, false, errorMessage); + return; + } + + if (stopped) { + errorMessage = "We have stopped already. We ignore that handleOfferAvailabilityRequest call."; + log.debug(errorMessage); + sendAckMessage(request, peer, false, errorMessage); + return; + } + + try { + Validator.nonEmptyStringOf(request.offerId); + checkNotNull(request.getPubKeyRing()); + } catch (Throwable t) { + errorMessage = "Message validation failed. Error=" + t.toString() + ", Message=" + request.toString(); + log.warn(errorMessage); + sendAckMessage(request, peer, false, errorMessage); + return; + } - Optional openOfferOptional = getOpenOfferById(message.offerId); - AvailabilityResult availabilityResult; - if (openOfferOptional.isPresent()) { - if (openOfferOptional.get().getState() == OpenOffer.State.AVAILABLE) { - final Offer offer = openOfferOptional.get().getOffer(); - if (!preferences.getIgnoreTradersList().stream().anyMatch(i -> i.equals(offer.getMakerNodeAddress().getHostNameWithoutPostFix()))) { - availabilityResult = AvailabilityResult.AVAILABLE; - - // TODO mediators not impl yet - List acceptedArbitrators = user.getAcceptedArbitratorAddresses(); - if (acceptedArbitrators != null && !acceptedArbitrators.isEmpty()) { - // Check also tradePrice to avoid failures after taker fee is paid caused by a too big difference - // in trade price between the peers. Also here poor connectivity might cause market price API connection - // losses and therefore an outdated market price. - try { - offer.checkTradePriceTolerance(message.getTakersTradePrice()); - } catch (TradePriceOutOfToleranceException e) { - log.warn("Trade price check failed because takers price is outside out tolerance."); - availabilityResult = AvailabilityResult.PRICE_OUT_OF_TOLERANCE; - } catch (MarketPriceNotAvailableException e) { - log.warn(e.getMessage()); - availabilityResult = AvailabilityResult.MARKET_PRICE_NOT_AVAILABLE; - } catch (Throwable e) { - log.warn("Trade price check failed. " + e.getMessage()); - availabilityResult = AvailabilityResult.UNKNOWN_FAILURE; - } - } else { - log.warn("acceptedArbitrators is null or empty: acceptedArbitrators=" + acceptedArbitrators); - availabilityResult = AvailabilityResult.NO_ARBITRATORS; + try { + Optional openOfferOptional = getOpenOfferById(request.offerId); + AvailabilityResult availabilityResult; + if (openOfferOptional.isPresent()) { + if (openOfferOptional.get().getState() == OpenOffer.State.AVAILABLE) { + final Offer offer = openOfferOptional.get().getOffer(); + if (preferences.getIgnoreTradersList().stream().noneMatch(hostName -> hostName.equals(offer.getMakerNodeAddress().getHostNameWithoutPostFix()))) { + availabilityResult = AvailabilityResult.AVAILABLE; + + List acceptedArbitrators = user.getAcceptedArbitratorAddresses(); + if (acceptedArbitrators != null && !acceptedArbitrators.isEmpty()) { + // Check also tradePrice to avoid failures after taker fee is paid caused by a too big difference + // in trade price between the peers. Also here poor connectivity might cause market price API connection + // losses and therefore an outdated market price. + try { + offer.checkTradePriceTolerance(request.getTakersTradePrice()); + } catch (TradePriceOutOfToleranceException e) { + log.warn("Trade price check failed because takers price is outside out tolerance."); + availabilityResult = AvailabilityResult.PRICE_OUT_OF_TOLERANCE; + } catch (MarketPriceNotAvailableException e) { + log.warn(e.getMessage()); + availabilityResult = AvailabilityResult.MARKET_PRICE_NOT_AVAILABLE; + } catch (Throwable e) { + log.warn("Trade price check failed. " + e.getMessage()); + availabilityResult = AvailabilityResult.UNKNOWN_FAILURE; } } else { - availabilityResult = AvailabilityResult.USER_IGNORED; + log.warn("acceptedArbitrators is null or empty: acceptedArbitrators=" + acceptedArbitrators); + availabilityResult = AvailabilityResult.NO_ARBITRATORS; } } else { - availabilityResult = AvailabilityResult.OFFER_TAKEN; + availabilityResult = AvailabilityResult.USER_IGNORED; } } else { - log.warn("handleOfferAvailabilityRequest: openOffer not found. That should never happen."); availabilityResult = AvailabilityResult.OFFER_TAKEN; } - try { - p2PService.sendEncryptedDirectMessage(sender, - message.getPubKeyRing(), - new OfferAvailabilityResponse(message.offerId, availabilityResult), - new SendDirectMessageListener() { - @Override - public void onArrived() { - log.trace("OfferAvailabilityResponse successfully arrived at peer"); - } - - @Override - public void onFault() { - log.debug("Sending OfferAvailabilityResponse failed."); - } - }); - } catch (Throwable t) { - t.printStackTrace(); - log.debug("Exception at handleRequestIsOfferAvailableMessage " + t.getMessage()); - } } else { - log.debug("We have stopped already. We ignore that handleOfferAvailabilityRequest call."); + log.warn("handleOfferAvailabilityRequest: openOffer not found. That should never happen."); + availabilityResult = AvailabilityResult.OFFER_TAKEN; } - } else { - log.info("We got a handleOfferAvailabilityRequest but we have not bootstrapped yet."); + + OfferAvailabilityResponse offerAvailabilityResponse = new OfferAvailabilityResponse(request.offerId, availabilityResult); + log.info("Send {} with offerId {} and uid {} to peer {}", + offerAvailabilityResponse.getClass().getSimpleName(), offerAvailabilityResponse.getOfferId(), + offerAvailabilityResponse.getUid(), peer); + p2PService.sendEncryptedDirectMessage(peer, + request.getPubKeyRing(), + offerAvailabilityResponse, + new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived at peer: offerId={}; uid={}", + offerAvailabilityResponse.getClass().getSimpleName(), offerAvailabilityResponse.getOfferId(), offerAvailabilityResponse.getUid()); + } + + @Override + public void onFault(String errorMessage) { + log.error("Sending {} failed: uid={}; peer={}; error={}", + offerAvailabilityResponse.getClass().getSimpleName(), offerAvailabilityResponse.getUid(), + peer, errorMessage); + } + }); + result = true; + } catch (Throwable t) { + errorMessage = "Exception at handleRequestIsOfferAvailableMessage " + t.getMessage(); + log.error(errorMessage); + t.printStackTrace(); + } finally { + sendAckMessage(request, peer, result, errorMessage); } } + private void sendAckMessage(OfferAvailabilityRequest message, NodeAddress sender, boolean result, String errorMessage) { + String offerId = message.getOfferId(); + String sourceUid = message.getUid(); + AckMessage ackMessage = new AckMessage(p2PService.getNetworkNode().getNodeAddress(), + AckMessageSourceType.OFFER_MESSAGE, + message.getClass().getSimpleName(), + sourceUid, + offerId, + result, + errorMessage); + + final NodeAddress takersNodeAddress = sender; + PubKeyRing takersPubKeyRing = message.getPubKeyRing(); + log.info("Send AckMessage for OfferAvailabilityRequest to peer {} with offerId {} and sourceUid {}", + takersNodeAddress, offerId, ackMessage.getSourceUid()); + p2PService.sendEncryptedDirectMessage( + takersNodeAddress, + takersPubKeyRing, + ackMessage, + new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("AckMessage for OfferAvailabilityRequest arrived at takersNodeAddress {}. offerId={}, sourceUid={}", + takersNodeAddress, offerId, ackMessage.getSourceUid()); + } + + @Override + public void onFault(String errorMessage) { + log.error("AckMessage for OfferAvailabilityRequest failed. AckMessage={}, takersNodeAddress={}, errorMessage={}", + ackMessage, takersNodeAddress, errorMessage); + } + } + ); + } + /////////////////////////////////////////////////////////////////////////////////////////// // RepublishOffers, refreshOffers diff --git a/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java b/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java index f9996905..304987db 100644 --- a/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java +++ b/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java @@ -26,17 +26,17 @@ import bisq.common.crypto.PubKeyRing; import bisq.common.taskrunner.Model; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.Getter; public class OfferAvailabilityModel implements Model { - private static final Logger log = LoggerFactory.getLogger(OfferAvailabilityModel.class); - - public final Offer offer; - public final PubKeyRing pubKeyRing; - public final P2PService p2PService; - - private NodeAddress peerNodeAddress; + @Getter + private final Offer offer; + @Getter + private final PubKeyRing pubKeyRing; // takers PubKey (my pubkey) + @Getter + private final P2PService p2PService; + + private NodeAddress peerNodeAddress; // maker private OfferAvailabilityResponse message; public OfferAvailabilityModel(Offer offer, diff --git a/src/main/java/bisq/core/offer/availability/OfferAvailabilityProtocol.java b/src/main/java/bisq/core/offer/availability/OfferAvailabilityProtocol.java index 06615115..6acc22e7 100644 --- a/src/main/java/bisq/core/offer/availability/OfferAvailabilityProtocol.java +++ b/src/main/java/bisq/core/offer/availability/OfferAvailabilityProtocol.java @@ -24,10 +24,15 @@ import bisq.core.offer.messages.OfferMessage; import bisq.core.util.Validator; +import bisq.network.p2p.AckMessage; +import bisq.network.p2p.AckMessageSourceType; import bisq.network.p2p.DecryptedDirectMessageListener; +import bisq.network.p2p.NodeAddress; +import bisq.network.p2p.SendDirectMessageListener; import bisq.common.Timer; import bisq.common.UserThread; +import bisq.common.crypto.PubKeyRing; import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ResultHandler; import bisq.common.proto.network.NetworkEnvelope; @@ -35,6 +40,8 @@ import lombok.extern.slf4j.Slf4j; +import javax.annotation.Nullable; + @Slf4j public class OfferAvailabilityProtocol { private static final long TIMEOUT = 90; @@ -58,14 +65,14 @@ public OfferAvailabilityProtocol(OfferAvailabilityModel model, ResultHandler res this.errorMessageHandler = errorMessageHandler; decryptedDirectMessageListener = (decryptedMessageWithPubKey, peersNodeAddress) -> { - NetworkEnvelope networkEnvelop = decryptedMessageWithPubKey.getNetworkEnvelope(); - if (networkEnvelop instanceof OfferMessage) { - OfferMessage offerMessage = (OfferMessage) networkEnvelop; + NetworkEnvelope networkEnvelope = decryptedMessageWithPubKey.getNetworkEnvelope(); + if (networkEnvelope instanceof OfferMessage) { + OfferMessage offerMessage = (OfferMessage) networkEnvelope; Validator.nonEmptyStringOf(offerMessage.offerId); - if (networkEnvelop instanceof OfferAvailabilityResponse - && model.offer.getId().equals(offerMessage.offerId)) { - log.trace("handle OfferAvailabilityResponse = " + networkEnvelop.getClass().getSimpleName() + " from " + peersNodeAddress); - handle((OfferAvailabilityResponse) networkEnvelop); + if (networkEnvelope instanceof OfferAvailabilityResponse + && model.getOffer().getId().equals(offerMessage.offerId)) { + log.trace("handle OfferAvailabilityResponse = " + networkEnvelope.getClass().getSimpleName() + " from " + peersNodeAddress); + handleOfferAvailabilityResponse((OfferAvailabilityResponse) networkEnvelope, peersNodeAddress); } } }; @@ -73,7 +80,7 @@ public OfferAvailabilityProtocol(OfferAvailabilityModel model, ResultHandler res private void cleanup() { stopTimeout(); - model.p2PService.removeDecryptedDirectMessageListener(decryptedDirectMessageListener); + model.getP2PService().removeDecryptedDirectMessageListener(decryptedDirectMessageListener); } @@ -83,18 +90,14 @@ private void cleanup() { public void sendOfferAvailabilityRequest() { // reset - model.offer.setState(Offer.State.UNKNOWN); + model.getOffer().setState(Offer.State.UNKNOWN); - model.p2PService.addDecryptedDirectMessageListener(decryptedDirectMessageListener); - model.setPeerNodeAddress(model.offer.getMakerNodeAddress()); + model.getP2PService().addDecryptedDirectMessageListener(decryptedDirectMessageListener); + model.setPeerNodeAddress(model.getOffer().getMakerNodeAddress()); taskRunner = new TaskRunner<>(model, - () -> log.debug("sequence at sendOfferAvailabilityRequest completed"), - (errorMessage) -> { - log.error(errorMessage); - stopTimeout(); - errorMessageHandler.handleErrorMessage(errorMessage); - } + () -> handleTaskRunnerSuccess("TaskRunner at sendOfferAvailabilityRequest completed", null), + errorMessage -> handleTaskRunnerFault(errorMessage, null) ); taskRunner.addTasks(SendOfferAvailabilityRequest.class); startTimeout(); @@ -111,23 +114,22 @@ public void cancel() { // Incoming message handling /////////////////////////////////////////////////////////////////////////////////////////// - private void handle(OfferAvailabilityResponse message) { + private void handleOfferAvailabilityResponse(OfferAvailabilityResponse message, NodeAddress peersNodeAddress) { + log.info("Received handleOfferAvailabilityResponse from {} with offerId {} and uid {}", + peersNodeAddress, message.getOfferId(), message.getUid()); + stopTimeout(); startTimeout(); model.setMessage(message); taskRunner = new TaskRunner<>(model, () -> { - log.debug("sequence at handle OfferAvailabilityResponse completed"); + handleTaskRunnerSuccess("TaskRunner at handle OfferAvailabilityResponse completed", message); + stopTimeout(); resultHandler.handleResult(); }, - (errorMessage) -> { - log.error(errorMessage); - stopTimeout(); - errorMessageHandler.handleErrorMessage(errorMessage); - } - ); + errorMessage -> handleTaskRunnerFault(errorMessage, message)); taskRunner.addTasks(ProcessOfferAvailabilityResponse.class); taskRunner.run(); } @@ -136,7 +138,7 @@ private void startTimeout() { if (timeoutTimer == null) { timeoutTimer = UserThread.runAfter(() -> { log.debug("Timeout reached at " + this); - model.offer.setState(Offer.State.MAKER_OFFLINE); + model.getOffer().setState(Offer.State.MAKER_OFFLINE); errorMessageHandler.handleErrorMessage("Timeout reached: Peer has not responded."); }, TIMEOUT); } else { @@ -150,4 +152,58 @@ private void stopTimeout() { timeoutTimer = null; } } + + private void handleTaskRunnerSuccess(String info, @Nullable OfferAvailabilityResponse message) { + log.debug("handleTaskRunnerSuccess " + info); + + if (message != null) + sendAckMessage(message, true, null); + } + + private void handleTaskRunnerFault(String errorMessage, @Nullable OfferAvailabilityResponse message) { + log.error(errorMessage); + + stopTimeout(); + errorMessageHandler.handleErrorMessage(errorMessage); + + if (message != null) + sendAckMessage(message, false, errorMessage); + } + + private void sendAckMessage(OfferAvailabilityResponse message, boolean result, @Nullable String errorMessage) { + String offerId = message.getOfferId(); + String sourceUid = message.getUid(); + final NodeAddress makersNodeAddress = model.getPeerNodeAddress(); + PubKeyRing makersPubKeyRing = model.getOffer().getPubKeyRing(); + log.info("Send AckMessage for OfferAvailabilityResponse to peer {} with offerId {} and sourceUid {}", + makersNodeAddress, offerId, sourceUid); + + AckMessage ackMessage = new AckMessage(model.getP2PService().getNetworkNode().getNodeAddress(), + AckMessageSourceType.OFFER_MESSAGE, + message.getClass().getSimpleName(), + sourceUid, + offerId, + result, + errorMessage); + model.getP2PService().sendEncryptedDirectMessage( + makersNodeAddress, + makersPubKeyRing, + ackMessage, + new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("AckMessage for OfferAvailabilityResponse arrived at makersNodeAddress {}. " + + "offerId={}, sourceUid={}", + makersNodeAddress, offerId, ackMessage.getSourceUid()); + } + + @Override + public void onFault(String errorMessage) { + log.error("AckMessage for OfferAvailabilityResponse failed. AckMessage={}, " + + "makersNodeAddress={}, errorMessage={}", + ackMessage, makersNodeAddress, errorMessage); + } + } + ); + } } diff --git a/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java b/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java index b29d55ca..e67b5529 100644 --- a/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java +++ b/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java @@ -25,12 +25,7 @@ import bisq.common.taskrunner.Task; import bisq.common.taskrunner.TaskRunner; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - public class ProcessOfferAvailabilityResponse extends Task { - private static final Logger log = LoggerFactory.getLogger(ProcessOfferAvailabilityResponse.class); - public ProcessOfferAvailabilityResponse(TaskRunner taskHandler, OfferAvailabilityModel model) { super(taskHandler, model); } @@ -41,18 +36,18 @@ protected void run() { runInterceptHook(); OfferAvailabilityResponse offerAvailabilityResponse = model.getMessage(); - if (model.offer.getState() != Offer.State.REMOVED) { + if (model.getOffer().getState() != Offer.State.REMOVED) { if (offerAvailabilityResponse.getAvailabilityResult() == AvailabilityResult.AVAILABLE) { - model.offer.setState(Offer.State.AVAILABLE); + model.getOffer().setState(Offer.State.AVAILABLE); } else { - model.offer.setState(Offer.State.NOT_AVAILABLE); + model.getOffer().setState(Offer.State.NOT_AVAILABLE); failed("Take offer attempt rejected because of: " + offerAvailabilityResponse.getAvailabilityResult()); } } complete(); } catch (Throwable t) { - model.offer.setErrorMessage("An error occurred.\n" + + model.getOffer().setErrorMessage("An error occurred.\n" + "Error message:\n" + t.getMessage()); diff --git a/src/main/java/bisq/core/offer/availability/tasks/SendOfferAvailabilityRequest.java b/src/main/java/bisq/core/offer/availability/tasks/SendOfferAvailabilityRequest.java index dae62d3a..73f1cb7c 100644 --- a/src/main/java/bisq/core/offer/availability/tasks/SendOfferAvailabilityRequest.java +++ b/src/main/java/bisq/core/offer/availability/tasks/SendOfferAvailabilityRequest.java @@ -26,12 +26,10 @@ import bisq.common.taskrunner.Task; import bisq.common.taskrunner.TaskRunner; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; +@Slf4j public class SendOfferAvailabilityRequest extends Task { - private static final Logger log = LoggerFactory.getLogger(SendOfferAvailabilityRequest.class); - public SendOfferAvailabilityRequest(TaskRunner taskHandler, OfferAvailabilityModel model) { super(taskHandler, model); } @@ -41,23 +39,33 @@ protected void run() { try { runInterceptHook(); - model.p2PService.sendEncryptedDirectMessage(model.getPeerNodeAddress(), - model.offer.getPubKeyRing(), - new OfferAvailabilityRequest(model.offer.getId(), model.pubKeyRing, model.getTakersTradePrice()), + OfferAvailabilityRequest message = new OfferAvailabilityRequest(model.getOffer().getId(), model.getPubKeyRing(), model.getTakersTradePrice()); + log.info("Send {} with offerId {} and uid {} to peer {}", + message.getClass().getSimpleName(), message.getOfferId(), + message.getUid(), model.getPeerNodeAddress()); + + model.getP2PService().sendEncryptedDirectMessage(model.getPeerNodeAddress(), + model.getOffer().getPubKeyRing(), + message, new SendDirectMessageListener() { @Override public void onArrived() { + log.info("{} arrived at peer: offerId={}; uid={}", + message.getClass().getSimpleName(), message.getOfferId(), message.getUid()); complete(); } @Override - public void onFault() { - model.offer.setState(Offer.State.MAKER_OFFLINE); + public void onFault(String errorMessage) { + log.error("Sending {} failed: uid={}; peer={}; error={}", + message.getClass().getSimpleName(), message.getUid(), + model.getPeerNodeAddress(), errorMessage); + model.getOffer().setState(Offer.State.MAKER_OFFLINE); } } ); } catch (Throwable t) { - model.offer.setErrorMessage("An error occurred.\n" + + model.getOffer().setErrorMessage("An error occurred.\n" + "Error message:\n" + t.getMessage()); diff --git a/src/main/java/bisq/core/offer/messages/OfferAvailabilityRequest.java b/src/main/java/bisq/core/offer/messages/OfferAvailabilityRequest.java index 1fe0c349..9f0c9728 100644 --- a/src/main/java/bisq/core/offer/messages/OfferAvailabilityRequest.java +++ b/src/main/java/bisq/core/offer/messages/OfferAvailabilityRequest.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.Optional; +import java.util.UUID; import lombok.EqualsAndHashCode; import lombok.Value; @@ -46,7 +47,12 @@ public final class OfferAvailabilityRequest extends OfferMessage implements Supp public OfferAvailabilityRequest(String offerId, PubKeyRing pubKeyRing, long takersTradePrice) { - this(offerId, pubKeyRing, takersTradePrice, Capabilities.getSupportedCapabilities(), Version.getP2PMessageVersion()); + this(offerId, + pubKeyRing, + takersTradePrice, + Capabilities.getSupportedCapabilities(), + Version.getP2PMessageVersion(), + UUID.randomUUID().toString()); } @@ -58,8 +64,9 @@ private OfferAvailabilityRequest(String offerId, PubKeyRing pubKeyRing, long takersTradePrice, @Nullable List supportedCapabilities, - int messageVersion) { - super(messageVersion, offerId); + int messageVersion, + @Nullable String uid) { + super(messageVersion, offerId, uid); this.pubKeyRing = pubKeyRing; this.takersTradePrice = takersTradePrice; this.supportedCapabilities = supportedCapabilities; @@ -73,6 +80,7 @@ public PB.NetworkEnvelope toProtoNetworkEnvelope() { .setTakersTradePrice(takersTradePrice); Optional.ofNullable(supportedCapabilities).ifPresent(e -> builder.addAllSupportedCapabilities(supportedCapabilities)); + Optional.ofNullable(uid).ifPresent(e -> builder.setUid(uid)); return getNetworkEnvelopeBuilder() .setOfferAvailabilityRequest(builder) @@ -84,6 +92,7 @@ public static OfferAvailabilityRequest fromProto(PB.OfferAvailabilityRequest pro PubKeyRing.fromProto(proto.getPubKeyRing()), proto.getTakersTradePrice(), proto.getSupportedCapabilitiesList().isEmpty() ? null : proto.getSupportedCapabilitiesList(), - messageVersion); + messageVersion, + proto.getUid().isEmpty() ? null : proto.getUid()); } } diff --git a/src/main/java/bisq/core/offer/messages/OfferAvailabilityResponse.java b/src/main/java/bisq/core/offer/messages/OfferAvailabilityResponse.java index 7b549dc9..dd66e4c6 100644 --- a/src/main/java/bisq/core/offer/messages/OfferAvailabilityResponse.java +++ b/src/main/java/bisq/core/offer/messages/OfferAvailabilityResponse.java @@ -30,6 +30,7 @@ import java.util.List; import java.util.Optional; +import java.util.UUID; import lombok.EqualsAndHashCode; import lombok.Value; @@ -46,7 +47,11 @@ public final class OfferAvailabilityResponse extends OfferMessage implements Sup private final List supportedCapabilities; public OfferAvailabilityResponse(String offerId, AvailabilityResult availabilityResult) { - this(offerId, availabilityResult, Capabilities.getSupportedCapabilities(), Version.getP2PMessageVersion()); + this(offerId, + availabilityResult, + Capabilities.getSupportedCapabilities(), + Version.getP2PMessageVersion(), + UUID.randomUUID().toString()); } @@ -57,8 +62,9 @@ public OfferAvailabilityResponse(String offerId, AvailabilityResult availability private OfferAvailabilityResponse(String offerId, AvailabilityResult availabilityResult, @Nullable List supportedCapabilities, - int messageVersion) { - super(messageVersion, offerId); + int messageVersion, + @Nullable String uid) { + super(messageVersion, offerId, uid); this.availabilityResult = availabilityResult; this.supportedCapabilities = supportedCapabilities; } @@ -70,6 +76,7 @@ public PB.NetworkEnvelope toProtoNetworkEnvelope() { .setAvailabilityResult(PB.AvailabilityResult.valueOf(availabilityResult.name())); Optional.ofNullable(supportedCapabilities).ifPresent(e -> builder.addAllSupportedCapabilities(supportedCapabilities)); + Optional.ofNullable(uid).ifPresent(e -> builder.setUid(uid)); return getNetworkEnvelopeBuilder() .setOfferAvailabilityResponse(builder) @@ -80,6 +87,7 @@ public static OfferAvailabilityResponse fromProto(PB.OfferAvailabilityResponse p return new OfferAvailabilityResponse(proto.getOfferId(), ProtoUtil.enumFromProto(AvailabilityResult.class, proto.getAvailabilityResult().name()), proto.getSupportedCapabilitiesList().isEmpty() ? null : proto.getSupportedCapabilitiesList(), - messageVersion); + messageVersion, + proto.getUid().isEmpty() ? null : proto.getUid()); } } diff --git a/src/main/java/bisq/core/offer/messages/OfferMessage.java b/src/main/java/bisq/core/offer/messages/OfferMessage.java index 65c6e602..a9b0f3d4 100644 --- a/src/main/java/bisq/core/offer/messages/OfferMessage.java +++ b/src/main/java/bisq/core/offer/messages/OfferMessage.java @@ -18,6 +18,7 @@ package bisq.core.offer.messages; import bisq.network.p2p.DirectMessage; +import bisq.network.p2p.UidMessage; import bisq.common.proto.network.NetworkEnvelope; @@ -25,14 +26,21 @@ import lombok.Getter; import lombok.ToString; +import javax.annotation.Nullable; + @EqualsAndHashCode(callSuper = true) @Getter @ToString -public abstract class OfferMessage extends NetworkEnvelope implements DirectMessage { +public abstract class OfferMessage extends NetworkEnvelope implements DirectMessage, UidMessage { public final String offerId; - protected OfferMessage(int messageVersion, String offerId) { + // Added at version 0.7.1. Can be null if we receive the msg from an peer with an older version + @Nullable + protected final String uid; + + protected OfferMessage(int messageVersion, String offerId, @Nullable String uid) { super(messageVersion); this.offerId = offerId; + this.uid = uid; } } diff --git a/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java b/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java index 259805e4..fd2806ae 100644 --- a/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java +++ b/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java @@ -43,6 +43,7 @@ import bisq.core.trade.messages.PublishDepositTxRequest; import bisq.core.trade.statistics.TradeStatistics; +import bisq.network.p2p.AckMessage; import bisq.network.p2p.CloseConnectionMessage; import bisq.network.p2p.PrefixedSealedAndSignedMessage; import bisq.network.p2p.peers.getdata.messages.GetDataResponse; @@ -155,6 +156,8 @@ public NetworkEnvelope fromProto(PB.NetworkEnvelope proto) throws ProtobufferExc case ADD_PERSISTABLE_NETWORK_PAYLOAD_MESSAGE: return AddPersistableNetworkPayloadMessage.fromProto(proto.getAddPersistableNetworkPayloadMessage(), this, messageVersion); + case ACK_MESSAGE: + return AckMessage.fromProto(proto.getAckMessage(), messageVersion); default: throw new ProtobufferException("Unknown proto message case (PB.NetworkEnvelope). messageCase=" + proto.getMessageCase() + "; proto raw data=" + proto.toString()); diff --git a/src/main/java/bisq/core/setup/CoreNetworkCapabilities.java b/src/main/java/bisq/core/setup/CoreNetworkCapabilities.java index a3f15261..65294e60 100644 --- a/src/main/java/bisq/core/setup/CoreNetworkCapabilities.java +++ b/src/main/java/bisq/core/setup/CoreNetworkCapabilities.java @@ -18,7 +18,6 @@ package bisq.core.setup; import bisq.core.app.BisqEnvironment; -import bisq.core.dao.DaoOptionKeys; import bisq.common.app.Capabilities; @@ -31,14 +30,9 @@ public static void setSupportedCapabilities(BisqEnvironment bisqEnvironment) { Capabilities.Capability.TRADE_STATISTICS.ordinal(), Capabilities.Capability.TRADE_STATISTICS_2.ordinal(), Capabilities.Capability.ACCOUNT_AGE_WITNESS.ordinal(), - Capabilities.Capability.PROPOSAL.ordinal(), - Capabilities.Capability.BLIND_VOTE.ordinal() + Capabilities.Capability.ACK_MSG.ordinal() )); - Boolean fullDaoNode = bisqEnvironment.getProperty(DaoOptionKeys.FULL_DAO_NODE, Boolean.class, false); - if (fullDaoNode) - supportedCapabilities.add(Capabilities.Capability.DAO_FULL_NODE.ordinal()); - Capabilities.setSupportedCapabilities(supportedCapabilities); } } diff --git a/src/main/java/bisq/core/setup/CoreSetup.java b/src/main/java/bisq/core/setup/CoreSetup.java index a18099a4..7106c4b9 100644 --- a/src/main/java/bisq/core/setup/CoreSetup.java +++ b/src/main/java/bisq/core/setup/CoreSetup.java @@ -61,7 +61,7 @@ public static void setup(BisqEnvironment bisqEnvironment) { private static void setupLog(BisqEnvironment bisqEnvironment) { String logPath = Paths.get(bisqEnvironment.getProperty(AppOptionKeys.APP_DATA_DIR_KEY), "bisq").toString(); Log.setup(logPath); - log.info("Log files under: " + logPath); + log.info("\n\n\nLog files under: " + logPath); Utilities.printSysInfo(); Log.setLevel(Level.toLevel(bisqEnvironment.getRequiredProperty(CommonOptionKeys.LOG_LEVEL_KEY))); } diff --git a/src/main/java/bisq/core/trade/Trade.java b/src/main/java/bisq/core/trade/Trade.java index 2c8c208a..7cf4a7fc 100644 --- a/src/main/java/bisq/core/trade/Trade.java +++ b/src/main/java/bisq/core/trade/Trade.java @@ -441,7 +441,6 @@ public Message toProtoMessage() { Optional.ofNullable(arbitratorPubKeyRing).ifPresent(e -> builder.setArbitratorPubKeyRing(arbitratorPubKeyRing.toProtoMessage())); Optional.ofNullable(mediatorPubKeyRing).ifPresent(e -> builder.setMediatorPubKeyRing(mediatorPubKeyRing.toProtoMessage())); Optional.ofNullable(counterCurrencyTxId).ifPresent(e -> builder.setCounterCurrencyTxId(counterCurrencyTxId)); - return builder.build(); } diff --git a/src/main/java/bisq/core/trade/TradeManager.java b/src/main/java/bisq/core/trade/TradeManager.java index c47ffff1..c67206e9 100644 --- a/src/main/java/bisq/core/trade/TradeManager.java +++ b/src/main/java/bisq/core/trade/TradeManager.java @@ -39,12 +39,11 @@ import bisq.core.user.User; import bisq.core.util.Validator; +import bisq.network.p2p.AckMessage; +import bisq.network.p2p.AckMessageSourceType; import bisq.network.p2p.BootstrapListener; -import bisq.network.p2p.DecryptedDirectMessageListener; -import bisq.network.p2p.DecryptedMessageWithPubKey; import bisq.network.p2p.NodeAddress; import bisq.network.p2p.P2PService; -import bisq.network.p2p.messaging.DecryptedMailboxListener; import bisq.common.UserThread; import bisq.common.app.Log; @@ -93,8 +92,6 @@ import javax.annotation.Nullable; -import static com.google.common.base.Preconditions.checkArgument; - public class TradeManager implements PersistedDataHost { private static final Logger log = LoggerFactory.getLogger(TradeManager.class); @@ -157,33 +154,35 @@ public TradeManager(User user, tradableListStorage = new Storage<>(storageDir, persistenceProtoResolver); - p2PService.addDecryptedDirectMessageListener(new DecryptedDirectMessageListener() { - @Override - public void onDirectMessage(DecryptedMessageWithPubKey decryptedMessageWithPubKey, NodeAddress peerNodeAddress) { - NetworkEnvelope networkEnvelop = decryptedMessageWithPubKey.getNetworkEnvelope(); + p2PService.addDecryptedDirectMessageListener((decryptedMessageWithPubKey, peerNodeAddress) -> { + NetworkEnvelope networkEnvelope = decryptedMessageWithPubKey.getNetworkEnvelope(); - // Handler for incoming initial network_messages from taker - if (networkEnvelop instanceof PayDepositRequest) { - log.trace("Received PayDepositRequest: " + networkEnvelop); - handleInitialTakeOfferRequest((PayDepositRequest) networkEnvelop, peerNodeAddress); - } + // Handler for incoming initial network_messages from taker + if (networkEnvelope instanceof PayDepositRequest) { + handlePayDepositRequest((PayDepositRequest) networkEnvelope, peerNodeAddress); } }); // Might get called at startup after HS is published. Can be before or after initPendingTrades. - p2PService.addDecryptedMailboxListener(new DecryptedMailboxListener() { - @Override - public void onMailboxMessageAdded(DecryptedMessageWithPubKey decryptedMessageWithPubKey, NodeAddress senderNodeAddress) { - log.debug("onMailboxMessageAdded decryptedMessageWithPubKey: " + decryptedMessageWithPubKey); - log.trace("onMailboxMessageAdded senderAddress: " + senderNodeAddress); - NetworkEnvelope networkEnvelop = decryptedMessageWithPubKey.getNetworkEnvelope(); - if (networkEnvelop instanceof TradeMessage) { - log.trace("Received TradeMessage: " + networkEnvelop); - String tradeId = ((TradeMessage) networkEnvelop).getTradeId(); - Optional tradeOptional = tradableList.stream().filter(e -> e.getId().equals(tradeId)).findAny(); - // The mailbox message will be removed inside the tasks after they are processed successfully - if (tradeOptional.isPresent()) - tradeOptional.get().addDecryptedMessageWithPubKey(decryptedMessageWithPubKey); + p2PService.addDecryptedMailboxListener((decryptedMessageWithPubKey, senderNodeAddress) -> { + NetworkEnvelope networkEnvelope = decryptedMessageWithPubKey.getNetworkEnvelope(); + if (networkEnvelope instanceof TradeMessage) { + TradeMessage tradeMessage = (TradeMessage) networkEnvelope; + String tradeId = tradeMessage.getTradeId(); + Optional tradeOptional = tradableList.stream().filter(e -> e.getId().equals(tradeId)).findAny(); + // The mailbox message will be removed inside the tasks after they are processed successfully + tradeOptional.ifPresent(trade -> trade.addDecryptedMessageWithPubKey(decryptedMessageWithPubKey)); + } else if (networkEnvelope instanceof AckMessage) { + AckMessage ackMessage = (AckMessage) networkEnvelope; + if (ackMessage.getSourceType() == AckMessageSourceType.TRADE_MESSAGE) { + if (ackMessage.isSuccess()) { + log.info("Received AckMessage for {} with tradeId {} and uid {}", + ackMessage.getSourceMsgClassName(), ackMessage.getSourceId(), ackMessage.getSourceUid()); + } else { + log.warn("Received AckMessage with error state for {} with tradeId {} and errorMessage={}", + ackMessage.getSourceMsgClassName(), ackMessage.getSourceId(), ackMessage.getErrorMessage()); + } + p2PService.removeEntryFromMailbox(decryptedMessageWithPubKey); } } }); @@ -194,7 +193,9 @@ public void readPersisted() { tradableList = new TradableList<>(tradableListStorage, "PendingTrades"); tradableList.forEach(trade -> { trade.setTransientFields(tradableListStorage, btcWalletService); - trade.getOffer().setPriceFeedService(priceFeedService); + Offer offer = trade.getOffer(); + if (offer != null) + offer.setPriceFeedService(priceFeedService); }); } @@ -277,22 +278,21 @@ public void cleanUpAddressEntries() { }); } - private void handleInitialTakeOfferRequest(TradeMessage message, NodeAddress peerNodeAddress) { - log.trace("handleNewMessage: message = " + message.getClass().getSimpleName() + " from " + peerNodeAddress); + private void handlePayDepositRequest(PayDepositRequest payDepositRequest, NodeAddress peer) { + log.info("Received PayDepositRequest from {} with tradeId {} and uid {}", + peer, payDepositRequest.getTradeId(), payDepositRequest.getUid()); + try { - Validator.nonEmptyStringOf(message.getTradeId()); + Validator.nonEmptyStringOf(payDepositRequest.getTradeId()); } catch (Throwable t) { - log.warn("Invalid requestDepositTxInputsMessage " + message.toString()); + log.warn("Invalid requestDepositTxInputsMessage " + payDepositRequest.toString()); return; } - Optional openOfferOptional = openOfferManager.getOpenOfferById(message.getTradeId()); + Optional openOfferOptional = openOfferManager.getOpenOfferById(payDepositRequest.getTradeId()); if (openOfferOptional.isPresent() && openOfferOptional.get().getState() == OpenOffer.State.AVAILABLE) { Offer offer = openOfferOptional.get().getOffer(); openOfferManager.reserveOpenOffer(openOfferOptional.get()); - - checkArgument(message instanceof PayDepositRequest, "message must be PayDepositRequest"); - PayDepositRequest payDepositRequest = (PayDepositRequest) message; Trade trade; if (offer.isBuyOffer()) trade = new BuyerAsMakerTrade(offer, @@ -311,7 +311,7 @@ private void handleInitialTakeOfferRequest(TradeMessage message, NodeAddress pee initTrade(trade, trade.getProcessModel().isUseSavingsWallet(), trade.getProcessModel().getFundsNeededForTradeAsLong()); tradableList.add(trade); - ((MakerTrade) trade).handleTakeOfferRequest(message, peerNodeAddress, errorMessage -> { + ((MakerTrade) trade).handleTakeOfferRequest(payDepositRequest, peer, errorMessage -> { if (takeOfferRequestErrorMessageHandler != null) takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); }); diff --git a/src/main/java/bisq/core/trade/messages/CounterCurrencyTransferStartedMessage.java b/src/main/java/bisq/core/trade/messages/CounterCurrencyTransferStartedMessage.java index 06648991..28b1342f 100644 --- a/src/main/java/bisq/core/trade/messages/CounterCurrencyTransferStartedMessage.java +++ b/src/main/java/bisq/core/trade/messages/CounterCurrencyTransferStartedMessage.java @@ -39,7 +39,6 @@ public final class CounterCurrencyTransferStartedMessage extends TradeMessage implements MailboxMessage { private final String buyerPayoutAddress; private final NodeAddress senderNodeAddress; - private final String uid; private final byte[] buyerSignature; @Nullable private final String counterCurrencyTxId; @@ -71,12 +70,11 @@ private CounterCurrencyTransferStartedMessage(String tradeId, @Nullable String counterCurrencyTxId, String uid, int messageVersion) { - super(messageVersion, tradeId); + super(messageVersion, tradeId, uid); this.buyerPayoutAddress = buyerPayoutAddress; this.senderNodeAddress = senderNodeAddress; this.buyerSignature = buyerSignature; this.counterCurrencyTxId = counterCurrencyTxId; - this.uid = uid; } @Override diff --git a/src/main/java/bisq/core/trade/messages/DepositTxPublishedMessage.java b/src/main/java/bisq/core/trade/messages/DepositTxPublishedMessage.java index 8ef17093..366483ec 100644 --- a/src/main/java/bisq/core/trade/messages/DepositTxPublishedMessage.java +++ b/src/main/java/bisq/core/trade/messages/DepositTxPublishedMessage.java @@ -35,7 +35,6 @@ public final class DepositTxPublishedMessage extends TradeMessage implements MailboxMessage { private final byte[] depositTx; private final NodeAddress senderNodeAddress; - private final String uid; public DepositTxPublishedMessage(String tradeId, byte[] depositTx, @@ -57,10 +56,9 @@ private DepositTxPublishedMessage(String tradeId, NodeAddress senderNodeAddress, String uid, int messageVersion) { - super(messageVersion, tradeId); + super(messageVersion, tradeId, uid); this.depositTx = depositTx; this.senderNodeAddress = senderNodeAddress; - this.uid = uid; } diff --git a/src/main/java/bisq/core/trade/messages/PayDepositRequest.java b/src/main/java/bisq/core/trade/messages/PayDepositRequest.java index 902b922e..56fe5c0b 100644 --- a/src/main/java/bisq/core/trade/messages/PayDepositRequest.java +++ b/src/main/java/bisq/core/trade/messages/PayDepositRequest.java @@ -64,7 +64,6 @@ public final class PayDepositRequest extends TradeMessage { private final List acceptedMediatorNodeAddresses; private final NodeAddress arbitratorNodeAddress; private final NodeAddress mediatorNodeAddress; - private final String uid; // added in v 0.6. can be null if we trade with an older peer @Nullable @@ -95,7 +94,7 @@ public PayDepositRequest(String tradeId, int messageVersion, @Nullable byte[] accountAgeWitnessSignatureOfOfferId, long currentDate) { - super(messageVersion, tradeId); + super(messageVersion, tradeId, uid); this.senderNodeAddress = senderNodeAddress; this.tradeAmount = tradeAmount; this.tradePrice = tradePrice; @@ -115,7 +114,6 @@ public PayDepositRequest(String tradeId, this.acceptedMediatorNodeAddresses = acceptedMediatorNodeAddresses; this.arbitratorNodeAddress = arbitratorNodeAddress; this.mediatorNodeAddress = mediatorNodeAddress; - this.uid = uid; this.accountAgeWitnessSignatureOfOfferId = accountAgeWitnessSignatureOfOfferId; this.currentDate = currentDate; } diff --git a/src/main/java/bisq/core/trade/messages/PayoutTxPublishedMessage.java b/src/main/java/bisq/core/trade/messages/PayoutTxPublishedMessage.java index 5653bd1b..58447860 100644 --- a/src/main/java/bisq/core/trade/messages/PayoutTxPublishedMessage.java +++ b/src/main/java/bisq/core/trade/messages/PayoutTxPublishedMessage.java @@ -36,7 +36,6 @@ public final class PayoutTxPublishedMessage extends TradeMessage implements MailboxMessage { private final byte[] payoutTx; private final NodeAddress senderNodeAddress; - private final String uid; public PayoutTxPublishedMessage(String tradeId, byte[] payoutTx, @@ -59,10 +58,9 @@ private PayoutTxPublishedMessage(String tradeId, NodeAddress senderNodeAddress, String uid, int messageVersion) { - super(messageVersion, tradeId); + super(messageVersion, tradeId, uid); this.payoutTx = payoutTx; this.senderNodeAddress = senderNodeAddress; - this.uid = uid; } @Override diff --git a/src/main/java/bisq/core/trade/messages/PublishDepositTxRequest.java b/src/main/java/bisq/core/trade/messages/PublishDepositTxRequest.java index 110820fc..6a8e3e21 100644 --- a/src/main/java/bisq/core/trade/messages/PublishDepositTxRequest.java +++ b/src/main/java/bisq/core/trade/messages/PublishDepositTxRequest.java @@ -57,7 +57,6 @@ public final class PublishDepositTxRequest extends TradeMessage implements Mailb private final byte[] preparedDepositTx; private final List makerInputs; private final NodeAddress senderNodeAddress; - private final String uid; // added in v 0.6. can be null if we trade with an older peer @Nullable @@ -112,7 +111,7 @@ private PublishDepositTxRequest(String tradeId, int messageVersion, @Nullable byte[] accountAgeWitnessSignatureOfPreparedDepositTx, long currentDate) { - super(messageVersion, tradeId); + super(messageVersion, tradeId, uid); this.makerPaymentAccountPayload = makerPaymentAccountPayload; this.makerAccountId = makerAccountId; this.makerMultiSigPubKey = makerMultiSigPubKey; @@ -122,7 +121,6 @@ private PublishDepositTxRequest(String tradeId, this.preparedDepositTx = preparedDepositTx; this.makerInputs = makerInputs; this.senderNodeAddress = senderNodeAddress; - this.uid = uid; this.accountAgeWitnessSignatureOfPreparedDepositTx = accountAgeWitnessSignatureOfPreparedDepositTx; this.currentDate = currentDate; } diff --git a/src/main/java/bisq/core/trade/messages/TradeMessage.java b/src/main/java/bisq/core/trade/messages/TradeMessage.java index 4ca2237f..5b386e2e 100644 --- a/src/main/java/bisq/core/trade/messages/TradeMessage.java +++ b/src/main/java/bisq/core/trade/messages/TradeMessage.java @@ -18,6 +18,7 @@ package bisq.core.trade.messages; import bisq.network.p2p.DirectMessage; +import bisq.network.p2p.UidMessage; import bisq.common.proto.network.NetworkEnvelope; @@ -28,11 +29,13 @@ @EqualsAndHashCode(callSuper = true) @Getter @ToString -public abstract class TradeMessage extends NetworkEnvelope implements DirectMessage { +public abstract class TradeMessage extends NetworkEnvelope implements DirectMessage, UidMessage { protected final String tradeId; + protected final String uid; - protected TradeMessage(int messageVersion, String tradeId) { + protected TradeMessage(int messageVersion, String tradeId, String uid) { super(messageVersion); this.tradeId = tradeId; + this.uid = uid; } } diff --git a/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java b/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java index 788375c5..4784a7e8 100644 --- a/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java +++ b/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java @@ -91,18 +91,23 @@ public BuyerAsMakerProtocol(BuyerAsMakerTrade trade) { /////////////////////////////////////////////////////////////////////////////////////////// @Override - public void doApplyMailboxMessage(NetworkEnvelope networkEnvelop, Trade trade) { + public void doApplyMailboxMessage(NetworkEnvelope networkEnvelope, Trade trade) { this.trade = trade; - if (networkEnvelop instanceof MailboxMessage) { - MailboxMessage mailboxMessage = (MailboxMessage) networkEnvelop; + if (networkEnvelope instanceof MailboxMessage) { + MailboxMessage mailboxMessage = (MailboxMessage) networkEnvelope; NodeAddress peerNodeAddress = mailboxMessage.getSenderNodeAddress(); - if (networkEnvelop instanceof DepositTxPublishedMessage) - handle((DepositTxPublishedMessage) networkEnvelop, peerNodeAddress); - else if (networkEnvelop instanceof PayoutTxPublishedMessage) - handle((PayoutTxPublishedMessage) networkEnvelop, peerNodeAddress); - else - log.error("We received an unhandled MailboxMessage" + networkEnvelop.toString()); + if (networkEnvelope instanceof TradeMessage) { + TradeMessage tradeMessage = (TradeMessage) networkEnvelope; + log.info("Received {} as MailboxMessage from {} with tradeId {} and uid {}", + tradeMessage.getClass().getSimpleName(), peerNodeAddress, tradeMessage.getTradeId(), tradeMessage.getUid()); + if (tradeMessage instanceof DepositTxPublishedMessage) + handle((DepositTxPublishedMessage) tradeMessage, peerNodeAddress); + else if (tradeMessage instanceof PayoutTxPublishedMessage) + handle((PayoutTxPublishedMessage) tradeMessage, peerNodeAddress); + else + log.error("We received an unhandled tradeMessage" + tradeMessage.toString()); + } } } @@ -112,14 +117,14 @@ else if (networkEnvelop instanceof PayoutTxPublishedMessage) /////////////////////////////////////////////////////////////////////////////////////////// @Override - public void handleTakeOfferRequest(TradeMessage message, NodeAddress peerNodeAddress, ErrorMessageHandler errorMessageHandler) { - Validator.checkTradeId(processModel.getOfferId(), message); - checkArgument(message instanceof PayDepositRequest); - processModel.setTradeMessage(message); + public void handleTakeOfferRequest(TradeMessage tradeMessage, NodeAddress peerNodeAddress, ErrorMessageHandler errorMessageHandler) { + Validator.checkTradeId(processModel.getOfferId(), tradeMessage); + checkArgument(tradeMessage instanceof PayDepositRequest); + processModel.setTradeMessage(tradeMessage); processModel.setTempTradingPeerNodeAddress(peerNodeAddress); TradeTaskRunner taskRunner = new TradeTaskRunner(buyerAsMakerTrade, - () -> handleTaskRunnerSuccess("handleTakeOfferRequest"), + () -> handleTaskRunnerSuccess(tradeMessage, "handleTakeOfferRequest"), errorMessage -> { errorMessageHandler.handleErrorMessage(errorMessage); handleTaskRunnerFault(errorMessage); @@ -153,9 +158,9 @@ private void handle(DepositTxPublishedMessage tradeMessage, NodeAddress peerNode TradeTaskRunner taskRunner = new TradeTaskRunner(buyerAsMakerTrade, () -> { - handleTaskRunnerSuccess("handle DepositTxPublishedMessage"); + handleTaskRunnerSuccess(tradeMessage, "handle DepositTxPublishedMessage"); }, - this::handleTaskRunnerFault); + errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage)); taskRunner.addTasks( MakerProcessDepositTxPublishedMessage.class, MakerVerifyTakerAccount.class, @@ -204,13 +209,12 @@ public void onFiatPaymentStarted(ResultHandler resultHandler, ErrorMessageHandle /////////////////////////////////////////////////////////////////////////////////////////// private void handle(PayoutTxPublishedMessage tradeMessage, NodeAddress peerNodeAddress) { - log.debug("handle PayoutTxPublishedMessage called"); processModel.setTradeMessage(tradeMessage); processModel.setTempTradingPeerNodeAddress(peerNodeAddress); TradeTaskRunner taskRunner = new TradeTaskRunner(buyerAsMakerTrade, - () -> handleTaskRunnerSuccess("handle PayoutTxPublishedMessage"), - this::handleTaskRunnerFault); + () -> handleTaskRunnerSuccess(tradeMessage, "handle PayoutTxPublishedMessage"), + errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage)); taskRunner.addTasks( BuyerProcessPayoutTxPublishedMessage.class @@ -224,11 +228,14 @@ private void handle(PayoutTxPublishedMessage tradeMessage, NodeAddress peerNodeA /////////////////////////////////////////////////////////////////////////////////////////// @Override - protected void doHandleDecryptedMessage(TradeMessage tradeMessage, NodeAddress peerNodeAddress) { + protected void doHandleDecryptedMessage(TradeMessage tradeMessage, NodeAddress sender) { + log.info("Received {} from {} with tradeId {} and uid {}", + tradeMessage.getClass().getSimpleName(), sender, tradeMessage.getTradeId(), tradeMessage.getUid()); + if (tradeMessage instanceof DepositTxPublishedMessage) { - handle((DepositTxPublishedMessage) tradeMessage, peerNodeAddress); + handle((DepositTxPublishedMessage) tradeMessage, sender); } else if (tradeMessage instanceof PayoutTxPublishedMessage) { - handle((PayoutTxPublishedMessage) tradeMessage, peerNodeAddress); + handle((PayoutTxPublishedMessage) tradeMessage, sender); } else //noinspection StatementWithEmptyBody if (tradeMessage instanceof PayDepositRequest) { // do nothing as we get called the handleTakeOfferRequest method from outside diff --git a/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java b/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java index 727eac1f..168944a2 100644 --- a/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java +++ b/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java @@ -83,15 +83,23 @@ public BuyerAsTakerProtocol(BuyerAsTakerTrade trade) { /////////////////////////////////////////////////////////////////////////////////////////// @Override - public void doApplyMailboxMessage(NetworkEnvelope networkEnvelop, Trade trade) { + public void doApplyMailboxMessage(NetworkEnvelope networkEnvelope, Trade trade) { this.trade = trade; - final NodeAddress senderNodeAddress = ((MailboxMessage) networkEnvelop).getSenderNodeAddress(); - if (networkEnvelop instanceof PublishDepositTxRequest) - handle((PublishDepositTxRequest) networkEnvelop, senderNodeAddress); - else if (networkEnvelop instanceof PayoutTxPublishedMessage) { - handle((PayoutTxPublishedMessage) networkEnvelop, senderNodeAddress); - } else - log.error("We received an unhandled MailboxMessage" + networkEnvelop.toString()); + + if (networkEnvelope instanceof MailboxMessage) { + final NodeAddress peerNodeAddress = ((MailboxMessage) networkEnvelope).getSenderNodeAddress(); + if (networkEnvelope instanceof TradeMessage) { + TradeMessage tradeMessage = (TradeMessage) networkEnvelope; + log.info("Received {} as MailboxMessage from {} with tradeId {} and uid {}", + tradeMessage.getClass().getSimpleName(), peerNodeAddress, tradeMessage.getTradeId(), tradeMessage.getUid()); + if (tradeMessage instanceof PublishDepositTxRequest) + handle((PublishDepositTxRequest) tradeMessage, peerNodeAddress); + else if (tradeMessage instanceof PayoutTxPublishedMessage) { + handle((PayoutTxPublishedMessage) tradeMessage, peerNodeAddress); + } else + log.error("We received an unhandled tradeMessage" + tradeMessage.toString()); + } + } } @@ -133,9 +141,9 @@ private void handle(PublishDepositTxRequest tradeMessage, NodeAddress sender) { TradeTaskRunner taskRunner = new TradeTaskRunner(buyerAsTakerTrade, () -> { stopTimeout(); - handleTaskRunnerSuccess("PublishDepositTxRequest"); + handleTaskRunnerSuccess(tradeMessage, "PublishDepositTxRequest"); }, - this::handleTaskRunnerFault); + errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage)); taskRunner.addTasks( TakerProcessPublishDepositTxRequest.class, CheckIfPeerIsBanned.class, @@ -195,8 +203,8 @@ private void handle(PayoutTxPublishedMessage tradeMessage, NodeAddress peerNodeA processModel.setTempTradingPeerNodeAddress(peerNodeAddress); TradeTaskRunner taskRunner = new TradeTaskRunner(buyerAsTakerTrade, - () -> handleTaskRunnerSuccess("handle PayoutTxPublishedMessage"), - this::handleTaskRunnerFault); + () -> handleTaskRunnerSuccess(tradeMessage, "handle PayoutTxPublishedMessage"), + errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage)); taskRunner.addTasks( BuyerProcessPayoutTxPublishedMessage.class @@ -210,6 +218,9 @@ private void handle(PayoutTxPublishedMessage tradeMessage, NodeAddress peerNodeA @Override protected void doHandleDecryptedMessage(TradeMessage tradeMessage, NodeAddress sender) { + log.info("Received {} from {} with tradeId {} and uid {}", + tradeMessage.getClass().getSimpleName(), sender, tradeMessage.getTradeId(), tradeMessage.getUid()); + if (tradeMessage instanceof PublishDepositTxRequest) { handle((PublishDepositTxRequest) tradeMessage, sender); } else if (tradeMessage instanceof PayoutTxPublishedMessage) { diff --git a/src/main/java/bisq/core/trade/protocol/ProcessModel.java b/src/main/java/bisq/core/trade/protocol/ProcessModel.java index 62d6c762..a415da77 100644 --- a/src/main/java/bisq/core/trade/protocol/ProcessModel.java +++ b/src/main/java/bisq/core/trade/protocol/ProcessModel.java @@ -23,6 +23,7 @@ import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.TradeWalletService; import bisq.core.filter.FilterManager; +import bisq.core.network.MessageState; import bisq.core.offer.Offer; import bisq.core.offer.OpenOfferManager; import bisq.core.payment.AccountAgeWitnessService; @@ -35,6 +36,7 @@ import bisq.core.trade.messages.TradeMessage; import bisq.core.user.User; +import bisq.network.p2p.AckMessage; import bisq.network.p2p.DecryptedMessageWithPubKey; import bisq.network.p2p.MailboxMessage; import bisq.network.p2p.NodeAddress; @@ -53,6 +55,9 @@ import org.bitcoinj.core.Coin; import org.bitcoinj.core.Transaction; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; + import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -134,6 +139,11 @@ public class ProcessModel implements Model, PersistablePayload { @Setter private NodeAddress tempTradingPeerNodeAddress; + // The only trade message where we want to indicate the user the state of the message delivery is the + // CounterCurrencyTransferStartedMessage. We persist the state with the processModel. + @Setter + private ObjectProperty paymentStartedMessageStateProperty = new SimpleObjectProperty<>(MessageState.UNDEFINED); + public ProcessModel() { } @@ -151,7 +161,8 @@ public PB.ProcessModel toProtoMessage() { .setPubKeyRing(pubKeyRing.toProtoMessage()) .setChangeOutputValue(changeOutputValue) .setUseSavingsWallet(useSavingsWallet) - .setFundsNeededForTradeAsLong(fundsNeededForTradeAsLong); + .setFundsNeededForTradeAsLong(fundsNeededForTradeAsLong) + .setPaymentStartedMessageState(paymentStartedMessageStateProperty.get().name()); Optional.ofNullable(takeOfferFeeTxId).ifPresent(builder::setTakeOfferFeeTxId); Optional.ofNullable(payoutTxSignature).ifPresent(e -> builder.setPayoutTxSignature(ByteString.copyFrom(payoutTxSignature))); @@ -194,6 +205,9 @@ public static ProcessModel fromProto(PB.ProcessModel proto, CoreProtoResolver co processModel.setChangeOutputAddress(ProtoUtil.stringOrNullFromProto(proto.getChangeOutputAddress())); processModel.setMyMultiSigPubKey(ProtoUtil.byteArrayOrNullFromProto(proto.getMyMultiSigPubKey())); processModel.setTempTradingPeerNodeAddress(proto.hasTempTradingPeerNodeAddress() ? NodeAddress.fromProto(proto.getTempTradingPeerNodeAddress()) : null); + String paymentStartedMessageState = proto.getPaymentStartedMessageState().isEmpty() ? MessageState.UNDEFINED.name() : proto.getPaymentStartedMessageState(); + ObjectProperty paymentStartedMessageStateProperty = processModel.getPaymentStartedMessageStateProperty(); + paymentStartedMessageStateProperty.set(ProtoUtil.enumFromProto(MessageState.class, paymentStartedMessageState)); return processModel; } @@ -285,4 +299,16 @@ public Transaction resolveTakeOfferFeeTx(Trade trade) { public NodeAddress getMyNodeAddress() { return p2PService.getAddress(); } + + public void setPaymentStartedAckMessage(AckMessage ackMessage) { + if (ackMessage.isSuccess()) { + setPaymentStartedMessageState(MessageState.ACKNOWLEDGED); + } else { + setPaymentStartedMessageState(MessageState.FAILED); + } + } + + public void setPaymentStartedMessageState(MessageState paymentStartedMessageStateProperty) { + this.paymentStartedMessageStateProperty.set(paymentStartedMessageStateProperty); + } } diff --git a/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java b/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java index aa0eb4e1..ec8a40da 100644 --- a/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java +++ b/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java @@ -85,16 +85,24 @@ public SellerAsMakerProtocol(SellerAsMakerTrade trade) { /////////////////////////////////////////////////////////////////////////////////////////// @Override - public void doApplyMailboxMessage(NetworkEnvelope networkEnvelop, Trade trade) { + public void doApplyMailboxMessage(NetworkEnvelope networkEnvelope, Trade trade) { this.trade = trade; - NodeAddress peerNodeAddress = ((MailboxMessage) networkEnvelop).getSenderNodeAddress(); - if (networkEnvelop instanceof DepositTxPublishedMessage) - handle((DepositTxPublishedMessage) networkEnvelop, peerNodeAddress); - else if (networkEnvelop instanceof CounterCurrencyTransferStartedMessage) - handle((CounterCurrencyTransferStartedMessage) networkEnvelop, peerNodeAddress); - else - log.error("We received an unhandled MailboxMessage" + networkEnvelop.toString()); + if (networkEnvelope instanceof MailboxMessage) { + NodeAddress peerNodeAddress = ((MailboxMessage) networkEnvelope).getSenderNodeAddress(); + if (networkEnvelope instanceof TradeMessage) { + TradeMessage tradeMessage = (TradeMessage) networkEnvelope; + log.info("Received {} as MailboxMessage from {} with tradeId {} and uid {}", + tradeMessage.getClass().getSimpleName(), peerNodeAddress, tradeMessage.getTradeId(), tradeMessage.getUid()); + + if (tradeMessage instanceof DepositTxPublishedMessage) + handle((DepositTxPublishedMessage) tradeMessage, peerNodeAddress); + else if (tradeMessage instanceof CounterCurrencyTransferStartedMessage) + handle((CounterCurrencyTransferStartedMessage) tradeMessage, peerNodeAddress); + else + log.error("We received an unhandled tradeMessage" + tradeMessage.toString()); + } + } } @@ -103,17 +111,17 @@ else if (networkEnvelop instanceof CounterCurrencyTransferStartedMessage) /////////////////////////////////////////////////////////////////////////////////////////// @Override - public void handleTakeOfferRequest(TradeMessage message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { - Validator.checkTradeId(processModel.getOfferId(), message); - checkArgument(message instanceof PayDepositRequest); - processModel.setTradeMessage(message); + public void handleTakeOfferRequest(TradeMessage tradeMessage, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { + Validator.checkTradeId(processModel.getOfferId(), tradeMessage); + checkArgument(tradeMessage instanceof PayDepositRequest); + processModel.setTradeMessage(tradeMessage); processModel.setTempTradingPeerNodeAddress(sender); TradeTaskRunner taskRunner = new TradeTaskRunner(sellerAsMakerTrade, - () -> handleTaskRunnerSuccess("handleTakeOfferRequest"), + () -> handleTaskRunnerSuccess(tradeMessage, "handleTakeOfferRequest"), errorMessage -> { errorMessageHandler.handleErrorMessage(errorMessage); - handleTaskRunnerFault(errorMessage); + handleTaskRunnerFault(tradeMessage, errorMessage); }); taskRunner.addTasks( @@ -146,9 +154,9 @@ private void handle(DepositTxPublishedMessage tradeMessage, NodeAddress sender) TradeTaskRunner taskRunner = new TradeTaskRunner(sellerAsMakerTrade, () -> { - handleTaskRunnerSuccess("DepositTxPublishedMessage"); + handleTaskRunnerSuccess(tradeMessage, "DepositTxPublishedMessage"); }, - this::handleTaskRunnerFault); + errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage)); taskRunner.addTasks( MakerProcessDepositTxPublishedMessage.class, @@ -169,8 +177,8 @@ private void handle(CounterCurrencyTransferStartedMessage tradeMessage, NodeAddr processModel.setTempTradingPeerNodeAddress(sender); TradeTaskRunner taskRunner = new TradeTaskRunner(sellerAsMakerTrade, - () -> handleTaskRunnerSuccess("CounterCurrencyTransferStartedMessage"), - this::handleTaskRunnerFault); + () -> handleTaskRunnerSuccess(tradeMessage, "CounterCurrencyTransferStartedMessage"), + errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage)); taskRunner.addTasks( SellerProcessCounterCurrencyTransferStartedMessage.class, @@ -241,6 +249,9 @@ public void onFiatPaymentReceived(ResultHandler resultHandler, ErrorMessageHandl @Override protected void doHandleDecryptedMessage(TradeMessage tradeMessage, NodeAddress sender) { + log.info("Received {} from {} with tradeId {} and uid {}", + tradeMessage.getClass().getSimpleName(), sender, tradeMessage.getTradeId(), tradeMessage.getUid()); + if (tradeMessage instanceof DepositTxPublishedMessage) { handle((DepositTxPublishedMessage) tradeMessage, sender); } else if (tradeMessage instanceof CounterCurrencyTransferStartedMessage) { diff --git a/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java b/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java index b9748f63..83f5b0cd 100644 --- a/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java +++ b/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java @@ -74,17 +74,22 @@ public SellerAsTakerProtocol(SellerAsTakerTrade trade) { /////////////////////////////////////////////////////////////////////////////////////////// @Override - public void doApplyMailboxMessage(NetworkEnvelope networkEnvelop, Trade trade) { + public void doApplyMailboxMessage(NetworkEnvelope networkEnvelope, Trade trade) { this.trade = trade; - if (networkEnvelop instanceof MailboxMessage) { - NodeAddress peerNodeAddress = ((MailboxMessage) networkEnvelop).getSenderNodeAddress(); - if (networkEnvelop instanceof PublishDepositTxRequest) - handle((PublishDepositTxRequest) networkEnvelop, peerNodeAddress); - else if (networkEnvelop instanceof CounterCurrencyTransferStartedMessage) - handle((CounterCurrencyTransferStartedMessage) networkEnvelop, peerNodeAddress); - else - log.error("We received an unhandled MailboxMessage" + networkEnvelop.toString()); + if (networkEnvelope instanceof MailboxMessage) { + NodeAddress peerNodeAddress = ((MailboxMessage) networkEnvelope).getSenderNodeAddress(); + if (networkEnvelope instanceof TradeMessage) { + TradeMessage tradeMessage = (TradeMessage) networkEnvelope; + log.info("Received {} as MailboxMessage from {} with tradeId {} and uid {}", + tradeMessage.getClass().getSimpleName(), peerNodeAddress, tradeMessage.getTradeId(), tradeMessage.getUid()); + if (tradeMessage instanceof PublishDepositTxRequest) + handle((PublishDepositTxRequest) tradeMessage, peerNodeAddress); + else if (tradeMessage instanceof CounterCurrencyTransferStartedMessage) + handle((CounterCurrencyTransferStartedMessage) tradeMessage, peerNodeAddress); + else + log.error("We received an unhandled tradeMessage" + tradeMessage.toString()); + } } } @@ -127,9 +132,9 @@ private void handle(PublishDepositTxRequest tradeMessage, NodeAddress sender) { TradeTaskRunner taskRunner = new TradeTaskRunner(sellerAsTakerTrade, () -> { stopTimeout(); - handleTaskRunnerSuccess("PublishDepositTxRequest"); + handleTaskRunnerSuccess(tradeMessage, "PublishDepositTxRequest"); }, - this::handleTaskRunnerFault); + errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage)); taskRunner.addTasks( TakerProcessPublishDepositTxRequest.class, @@ -155,8 +160,8 @@ private void handle(CounterCurrencyTransferStartedMessage tradeMessage, NodeAddr processModel.setTempTradingPeerNodeAddress(sender); TradeTaskRunner taskRunner = new TradeTaskRunner(sellerAsTakerTrade, - () -> handleTaskRunnerSuccess("CounterCurrencyTransferStartedMessage"), - this::handleTaskRunnerFault); + () -> handleTaskRunnerSuccess(tradeMessage, "CounterCurrencyTransferStartedMessage"), + errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage)); taskRunner.addTasks( SellerProcessCounterCurrencyTransferStartedMessage.class, @@ -228,6 +233,9 @@ public void onFiatPaymentReceived(ResultHandler resultHandler, ErrorMessageHandl @Override protected void doHandleDecryptedMessage(TradeMessage tradeMessage, NodeAddress sender) { + log.info("Received {} from {} with tradeId {} and uid {}", + tradeMessage.getClass().getSimpleName(), sender, tradeMessage.getTradeId(), tradeMessage.getUid()); + if (tradeMessage instanceof PublishDepositTxRequest) { handle((PublishDepositTxRequest) tradeMessage, sender); } else if (tradeMessage instanceof CounterCurrencyTransferStartedMessage) { diff --git a/src/main/java/bisq/core/trade/protocol/TradeProtocol.java b/src/main/java/bisq/core/trade/protocol/TradeProtocol.java index 2fa30680..3f4dbd04 100644 --- a/src/main/java/bisq/core/trade/protocol/TradeProtocol.java +++ b/src/main/java/bisq/core/trade/protocol/TradeProtocol.java @@ -20,11 +20,17 @@ import bisq.core.trade.MakerTrade; import bisq.core.trade.Trade; import bisq.core.trade.TradeManager; +import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage; +import bisq.core.trade.messages.PayDepositRequest; import bisq.core.trade.messages.TradeMessage; +import bisq.network.p2p.AckMessage; +import bisq.network.p2p.AckMessageSourceType; import bisq.network.p2p.DecryptedDirectMessageListener; import bisq.network.p2p.DecryptedMessageWithPubKey; +import bisq.network.p2p.MailboxMessage; import bisq.network.p2p.NodeAddress; +import bisq.network.p2p.SendMailboxMessageListener; import bisq.common.Timer; import bisq.common.UserThread; @@ -37,6 +43,8 @@ import lombok.extern.slf4j.Slf4j; +import javax.annotation.Nullable; + import static bisq.core.util.Validator.nonEmptyStringOf; @Slf4j @@ -58,14 +66,30 @@ public TradeProtocol(Trade trade) { PubKeyRing tradingPeerPubKeyRing = processModel.getTradingPeer().getPubKeyRing(); PublicKey signaturePubKey = decryptedMessageWithPubKey.getSignaturePubKey(); if (tradingPeerPubKeyRing != null && signaturePubKey.equals(tradingPeerPubKeyRing.getSignaturePubKey())) { - NetworkEnvelope networkEnvelop = decryptedMessageWithPubKey.getNetworkEnvelope(); - log.trace("handleNewMessage: message = " + networkEnvelop.getClass().getSimpleName() + " from " + peersNodeAddress); - if (networkEnvelop instanceof TradeMessage) { - TradeMessage tradeMessage = (TradeMessage) networkEnvelop; + NetworkEnvelope networkEnvelope = decryptedMessageWithPubKey.getNetworkEnvelope(); + log.trace("handleNewMessage: message = " + networkEnvelope.getClass().getSimpleName() + " from " + peersNodeAddress); + if (networkEnvelope instanceof TradeMessage) { + TradeMessage tradeMessage = (TradeMessage) networkEnvelope; nonEmptyStringOf(tradeMessage.getTradeId()); if (tradeMessage.getTradeId().equals(processModel.getOfferId())) doHandleDecryptedMessage(tradeMessage, peersNodeAddress); + } else if (networkEnvelope instanceof AckMessage) { + AckMessage ackMessage = (AckMessage) networkEnvelope; + if (ackMessage.getSourceType() == AckMessageSourceType.TRADE_MESSAGE && + ackMessage.getSourceId().equals(trade.getId())) { + // We only handle the ack for CounterCurrencyTransferStartedMessage + if (ackMessage.getSourceMsgClassName().equals(CounterCurrencyTransferStartedMessage.class.getSimpleName())) + processModel.setPaymentStartedAckMessage(ackMessage); + + if (ackMessage.isSuccess()) { + log.info("Received AckMessage for {} from {} with tradeId {} and uid {}", + ackMessage.getSourceMsgClassName(), peersNodeAddress, ackMessage.getSourceId(), ackMessage.getSourceUid()); + } else { + log.warn("Received AckMessage with error state for {} from {} with tradeId {} and errorMessage={}", + ackMessage.getSourceMsgClassName(), peersNodeAddress, ackMessage.getSourceId(), ackMessage.getErrorMessage()); + } + } } } }; @@ -106,7 +130,7 @@ public void applyMailboxMessage(DecryptedMessageWithPubKey decryptedMessageWithP } } - protected abstract void doApplyMailboxMessage(NetworkEnvelope networkEnvelop, Trade trade); + protected abstract void doApplyMailboxMessage(NetworkEnvelope networkEnvelope, Trade trade); protected abstract void doHandleDecryptedMessage(TradeMessage tradeMessage, NodeAddress peerNodeAddress); @@ -129,15 +153,82 @@ protected void stopTimeout() { } protected void handleTaskRunnerSuccess(String info) { + handleTaskRunnerSuccess(null, info); + } + + protected void handleTaskRunnerSuccess(@Nullable TradeMessage tradeMessage, String info) { log.debug("handleTaskRunnerSuccess " + info); + + sendAckMessage(tradeMessage, true, null); } protected void handleTaskRunnerFault(String errorMessage) { + handleTaskRunnerFault(null, errorMessage); + } + + protected void handleTaskRunnerFault(@Nullable TradeMessage tradeMessage, String errorMessage) { log.error(errorMessage); + + sendAckMessage(tradeMessage, false, errorMessage); + cleanupTradableOnFault(); cleanup(); } + private void sendAckMessage(@Nullable TradeMessage tradeMessage, boolean result, @Nullable String errorMessage) { + // We complete at initial protocol setup with the setup listener tasks. + // Other cases are if we start from an UI event the task runner (payment started, confirmed). + // In such cases we have not set any tradeMessage and we ignore the sendAckMessage call. + if (tradeMessage == null) + return; + + String tradeId = tradeMessage.getTradeId(); + String sourceUid = ""; + if (tradeMessage instanceof MailboxMessage) { + sourceUid = ((MailboxMessage) tradeMessage).getUid(); + } else { + // For direct msg we don't have a mandatory uid so we need to cast to get it + if (tradeMessage instanceof PayDepositRequest) { + sourceUid = tradeMessage.getUid(); + } + } + AckMessage ackMessage = new AckMessage(processModel.getMyNodeAddress(), + AckMessageSourceType.TRADE_MESSAGE, + tradeMessage.getClass().getSimpleName(), + sourceUid, + tradeId, + result, + errorMessage); + final NodeAddress peersNodeAddress = trade.getTradingPeerNodeAddress(); + log.info("Send AckMessage for {} to peer {}. tradeId={}, sourceUid={}", + ackMessage.getSourceMsgClassName(), peersNodeAddress, tradeId, sourceUid); + String finalSourceUid = sourceUid; + processModel.getP2PService().sendEncryptedMailboxMessage( + peersNodeAddress, + processModel.getTradingPeer().getPubKeyRing(), + ackMessage, + new SendMailboxMessageListener() { + @Override + public void onArrived() { + log.info("AckMessage for {} arrived at peer {}. tradeId={}, sourceUid={}", + ackMessage.getSourceMsgClassName(), peersNodeAddress, tradeId, finalSourceUid); + } + + @Override + public void onStoredInMailbox() { + log.info("AckMessage for {} stored in mailbox for peer {}. tradeId={}, sourceUid={}", + ackMessage.getSourceMsgClassName(), peersNodeAddress, tradeId, finalSourceUid); + } + + @Override + public void onFault(String errorMessage) { + log.error("AckMessage for {} failed. Peer {}. tradeId={}, sourceUid={}, errorMessage={}", + ackMessage.getSourceMsgClassName(), peersNodeAddress, tradeId, finalSourceUid, errorMessage); + } + } + ); + } + private void cleanupTradableOnFault() { final Trade.State state = trade.getState(); log.warn("cleanupTradableOnFault tradeState=" + state); diff --git a/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSendCounterCurrencyTransferStartedMessage.java b/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSendCounterCurrencyTransferStartedMessage.java index 38465391..00d13310 100644 --- a/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSendCounterCurrencyTransferStartedMessage.java +++ b/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSendCounterCurrencyTransferStartedMessage.java @@ -23,6 +23,7 @@ import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage; import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.network.p2p.NodeAddress; import bisq.network.p2p.SendMailboxMessageListener; import bisq.common.taskrunner.TaskRunner; @@ -55,31 +56,35 @@ protected void run() { trade.getCounterCurrencyTxId(), UUID.randomUUID().toString() ); - log.info("Send message to peer. tradeId={}, message{}", id, message); + NodeAddress peersNodeAddress = trade.getTradingPeerNodeAddress(); + log.info("Send {} to peer {}. tradeId={}, uid={}", + message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); trade.setState(Trade.State.BUYER_SENT_FIAT_PAYMENT_INITIATED_MSG); - processModel.getP2PService().sendEncryptedMailboxMessage( - trade.getTradingPeerNodeAddress(), + peersNodeAddress, processModel.getTradingPeer().getPubKeyRing(), message, new SendMailboxMessageListener() { @Override public void onArrived() { - log.info("Message arrived at peer. tradeId={}", id); + log.info("{} arrived at peer {}. tradeId={}, uid={}", + message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); trade.setState(Trade.State.BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG); complete(); } @Override public void onStoredInMailbox() { - log.info("Message stored in mailbox. tradeId={}", id); + log.info("{} stored in mailbox for peer {}. tradeId={}, uid={}", + message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); trade.setState(Trade.State.BUYER_STORED_IN_MAILBOX_FIAT_PAYMENT_INITIATED_MSG); complete(); } @Override public void onFault(String errorMessage) { - log.error("sendEncryptedMailboxMessage failed. message=" + message); + log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}", + message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid(), errorMessage); trade.setState(Trade.State.BUYER_SEND_FAILED_FIAT_PAYMENT_INITIATED_MSG); appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage); failed(errorMessage); diff --git a/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendPublishDepositTxRequest.java b/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendPublishDepositTxRequest.java index 4f4eff0a..33f1f8e5 100644 --- a/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendPublishDepositTxRequest.java +++ b/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendPublishDepositTxRequest.java @@ -24,6 +24,7 @@ import bisq.core.trade.messages.PublishDepositTxRequest; import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.network.p2p.NodeAddress; import bisq.network.p2p.SendMailboxMessageListener; import bisq.common.crypto.Sig; @@ -86,28 +87,34 @@ protected void run() { trade.setState(Trade.State.MAKER_SENT_PUBLISH_DEPOSIT_TX_REQUEST); + NodeAddress peersNodeAddress = trade.getTradingPeerNodeAddress(); + log.info("Send {} to peer {}. tradeId={}, uid={}", + message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); processModel.getP2PService().sendEncryptedMailboxMessage( - trade.getTradingPeerNodeAddress(), + peersNodeAddress, processModel.getTradingPeer().getPubKeyRing(), message, new SendMailboxMessageListener() { @Override public void onArrived() { - log.info("Message arrived at peer. tradeId={}", id); + log.info("{} arrived at peer {}. tradeId={}, uid={}", + message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); trade.setState(Trade.State.MAKER_SAW_ARRIVED_PUBLISH_DEPOSIT_TX_REQUEST); complete(); } @Override public void onStoredInMailbox() { - log.info("Message stored in mailbox. tradeId={}", id); + log.info("{} stored in mailbox for peer {}. tradeId={}, uid={}", + message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); trade.setState(Trade.State.MAKER_STORED_IN_MAILBOX_PUBLISH_DEPOSIT_TX_REQUEST); complete(); } @Override public void onFault(String errorMessage) { - log.error("sendEncryptedMailboxMessage failed. message=" + message); + log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}", + message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid(), errorMessage); trade.setState(Trade.State.MAKER_SEND_FAILED_PUBLISH_DEPOSIT_TX_REQUEST); appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage); failed(errorMessage); diff --git a/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendPayoutTxPublishedMessage.java b/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendPayoutTxPublishedMessage.java index 8841bdc9..b277caec 100644 --- a/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendPayoutTxPublishedMessage.java +++ b/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendPayoutTxPublishedMessage.java @@ -21,6 +21,7 @@ import bisq.core.trade.messages.PayoutTxPublishedMessage; import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.network.p2p.NodeAddress; import bisq.network.p2p.SendMailboxMessageListener; import bisq.common.taskrunner.TaskRunner; @@ -49,29 +50,35 @@ protected void run() { UUID.randomUUID().toString() ); trade.setState(Trade.State.SELLER_SENT_PAYOUT_TX_PUBLISHED_MSG); + NodeAddress peersNodeAddress = trade.getTradingPeerNodeAddress(); + log.info("Send {} to peer {}. tradeId={}, uid={}", + message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); processModel.getP2PService().sendEncryptedMailboxMessage( - trade.getTradingPeerNodeAddress(), + peersNodeAddress, processModel.getTradingPeer().getPubKeyRing(), message, new SendMailboxMessageListener() { @Override public void onArrived() { - log.info("Message arrived at peer. tradeId={}", id); + log.info("{} arrived at peer {}. tradeId={}, uid={}", + message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); trade.setState(Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG); complete(); } @Override public void onStoredInMailbox() { - log.info("Message stored in mailbox. tradeId={}", id); + log.info("{} stored in mailbox for peer {}. tradeId={}, uid={}", + message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); trade.setState(Trade.State.SELLER_STORED_IN_MAILBOX_PAYOUT_TX_PUBLISHED_MSG); complete(); } @Override public void onFault(String errorMessage) { - log.error("sendEncryptedMailboxMessage failed. message=" + message); + log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}", + message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid(), errorMessage); trade.setState(Trade.State.SELLER_SEND_FAILED_PAYOUT_TX_PUBLISHED_MSG); appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage); failed(errorMessage); diff --git a/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendDepositTxPublishedMessage.java b/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendDepositTxPublishedMessage.java index 38996790..e720f435 100644 --- a/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendDepositTxPublishedMessage.java +++ b/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendDepositTxPublishedMessage.java @@ -21,6 +21,7 @@ import bisq.core.trade.messages.DepositTxPublishedMessage; import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.network.p2p.NodeAddress; import bisq.network.p2p.SendMailboxMessageListener; import bisq.common.taskrunner.TaskRunner; @@ -48,28 +49,34 @@ protected void run() { UUID.randomUUID().toString()); trade.setState(Trade.State.TAKER_SENT_DEPOSIT_TX_PUBLISHED_MSG); + NodeAddress peersNodeAddress = trade.getTradingPeerNodeAddress(); + log.info("Send {} to peer {}. tradeId={}, uid={}", + message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); processModel.getP2PService().sendEncryptedMailboxMessage( - trade.getTradingPeerNodeAddress(), + peersNodeAddress, processModel.getTradingPeer().getPubKeyRing(), message, new SendMailboxMessageListener() { @Override public void onArrived() { - log.info("Message arrived at peer. tradeId={}", id); + log.info("{} arrived at peer {}. tradeId={}, uid={}", + message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); trade.setState(Trade.State.TAKER_SAW_ARRIVED_DEPOSIT_TX_PUBLISHED_MSG); complete(); } @Override public void onStoredInMailbox() { - log.info("Message stored in mailbox. tradeId={}", id); + log.info("{} stored in mailbox for peer {}. tradeId={}, uid={}", + message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); trade.setState(Trade.State.TAKER_STORED_IN_MAILBOX_DEPOSIT_TX_PUBLISHED_MSG); complete(); } @Override public void onFault(String errorMessage) { - log.error("sendEncryptedMailboxMessage failed. message=" + message); + log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}", + message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid(), errorMessage); trade.setState(Trade.State.TAKER_SEND_FAILED_DEPOSIT_TX_PUBLISHED_MSG); appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage); failed(); diff --git a/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendPayDepositRequest.java b/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendPayDepositRequest.java index 6d5432cd..a3927942 100644 --- a/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendPayDepositRequest.java +++ b/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendPayDepositRequest.java @@ -106,20 +106,25 @@ protected void run() { Version.getP2PMessageVersion(), sig, new Date().getTime()); - + log.info("Send {} with offerId {} and uid {} to peer {}", + message.getClass().getSimpleName(), message.getTradeId(), + message.getUid(), trade.getTradingPeerNodeAddress()); processModel.getP2PService().sendEncryptedDirectMessage( trade.getTradingPeerNodeAddress(), processModel.getTradingPeer().getPubKeyRing(), message, new SendDirectMessageListener() { - @Override public void onArrived() { - log.debug("Message arrived at peer. tradeId={}, message{}", id, message); + log.info("{} arrived at peer: offerId={}; uid={}", + message.getClass().getSimpleName(), message.getTradeId(), message.getUid()); complete(); } @Override - public void onFault() { + public void onFault(String errorMessage) { + log.error("Sending {} failed: uid={}; peer={}; error={}", + message.getClass().getSimpleName(), message.getUid(), + trade.getTradingPeerNodeAddress(), errorMessage); appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage); failed(); } diff --git a/src/main/resources/i18n/displayStrings.properties b/src/main/resources/i18n/displayStrings.properties index 7f160fa9..767742f0 100644 --- a/src/main/resources/i18n/displayStrings.properties +++ b/src/main/resources/i18n/displayStrings.properties @@ -547,8 +547,16 @@ portfolio.pending.step2_seller.waitPayment.msg=The deposit transaction has at le portfolio.pending.step2_seller.warn=The BTC buyer still has not done the {0} payment.\nYou need to wait until he starts the payment.\nIf the trade has not been completed on {1} the arbitrator will investigate. portfolio.pending.step2_seller.openForDispute=The BTC buyer has not started his payment!\nThe max. allowed period for the trade has elapsed.\nYou can wait longer and give the trading peer more time or contact the arbitrator for opening a dispute. +message.state.UNDEFINED=Undefined +message.state.SENT=Message sent +message.state.ARRIVED=Message arrived at peer +message.state.STORED_IN_MAILBOX=Message stored in mailbox +message.state.ACKNOWLEDGED=Peer confirmed message receipt +message.state.FAILED=Sending message failed + portfolio.pending.step3_buyer.wait.headline=Wait for BTC seller's payment confirmation portfolio.pending.step3_buyer.wait.info=Waiting for the BTC seller''s confirmation for the receipt of the {0} payment. +portfolio.pending.step3_buyer.wait.msgStateInfo.label=Payment started message status: portfolio.pending.step3_buyer.warn.part1a=on the {0} blockchain portfolio.pending.step3_buyer.warn.part1b=at your payment provider (e.g. bank) portfolio.pending.step3_buyer.warn.part2=The BTC seller still has not confirmed your payment!\nPlease check {0} if the payment sending was successful.\nIf the BTC seller does not confirm the receipt of your payment by {1} the trade will be investigated by the arbitrator. @@ -732,6 +740,7 @@ support.filter=Filter list: support.noTickets=There are no open tickets support.sendingMessage=Sending Message... support.receiverNotOnline=Receiver is not online. Message is saved to his mailbox. +support.sendMessageError=Sending message failed. Error: {0} support.wrongVersion=The offer in that dispute has been created with an older version of Bisq.\n\ You cannot close that dispute with your version of the application.\n\n\ Please use an older version with protocol version {0} @@ -749,6 +758,8 @@ support.closeTicket=Close ticket support.attachments=Attachments: support.savedInMailbox=Message saved in receiver's mailbox support.arrived=Message arrived at receiver +support.acknowledged=Message arrival confirmed by receiver +support.error=Receiver could not process message. Error: {0} support.buyerAddress=BTC buyer address support.sellerAddress=BTC seller address support.role=Role @@ -790,7 +801,6 @@ support.peerOpenedDispute=Your trading peer has requested a dispute.\n\n{0} #################################################################### # Settings #################################################################### - settings.tab.preferences=Preferences settings.tab.network=Network info settings.tab.about=About diff --git a/src/main/resources/i18n/in_dev/displayStrings_fr.properties b/src/main/resources/i18n/in_dev/displayStrings_fr.properties index d896619e..f33b15d3 100644 --- a/src/main/resources/i18n/in_dev/displayStrings_fr.properties +++ b/src/main/resources/i18n/in_dev/displayStrings_fr.properties @@ -117,15 +117,9 @@ Redémarrez votre application et vérifiez votre connexion réseau. # TODO remove createOffer.validation.minAmountLargerThanAmount=Minimum amount cannot be larger than amount. createOffer.fundsBox.title=Fund your offer -createOffer.fundsBox.totalsNeeded=Funds needed: -createOffer.fundsBox.totalsNeeded.prompt=Will be calculated from the bitcoin amount entered above -createOffer.fundsBox.address=Trade wallet address: # TODO remove createOffer.fundsBox.info=For every offer there is a dedicated trade wallet. You need to fund that trade wallet with the necessary bitcoin amount. Those funds are reserved and will be used in the case that your offer gets executed. If you cancel your offer you can withdraw your funds from that trading wallet. The only payment made when placing an offer is the offer fee payment. https://bisq.network/faq/#6 -createOffer.fundsBox.tradeAmount=Trade amount: -createOffer.fundsBox.securityDeposit=Security deposit: createOffer.fundsBox.offerFee=Create offer fee: createOffer.fundsBox.networkFee=Mining fee: -createOffer.fundsBox.total=Total: # TODO remove createOffer.fundsBox.showAdvanced=Show advanced settings # TODO remove createOffer.fundsBox.hideAdvanced=Hide advanced settings # TODO remove createOffer.fundsBox.placeOffer=Place offer