From b65f83372d5a7eae639c5a73db679fd7d32f7655 Mon Sep 17 00:00:00 2001 From: woodser <13068859+woodser@users.noreply.github.com> Date: Sat, 27 Sep 2025 00:42:58 -0400 Subject: [PATCH] fix message spam by resending payment received messages when applicable --- .../java/haveno/core/trade/SellerTrade.java | 5 ++-- .../main/java/haveno/core/trade/Trade.java | 8 ++--- .../core/trade/protocol/ProcessModel.java | 4 +-- .../haveno/core/trade/protocol/TradePeer.java | 8 +++++ .../SellerSendPaymentReceivedMessage.java | 30 ++++++++++++------- 5 files changed, 37 insertions(+), 18 deletions(-) diff --git a/core/src/main/java/haveno/core/trade/SellerTrade.java b/core/src/main/java/haveno/core/trade/SellerTrade.java index 15d8357592..bf4bd67245 100644 --- a/core/src/main/java/haveno/core/trade/SellerTrade.java +++ b/core/src/main/java/haveno/core/trade/SellerTrade.java @@ -69,7 +69,9 @@ public abstract class SellerTrade extends Trade { } public boolean needsToResendPaymentReceivedMessages() { - return !isShutDownStarted() && getState().ordinal() >= Trade.State.SELLER_SENT_PAYMENT_RECEIVED_MSG.ordinal() && !getProcessModel().isPaymentReceivedMessagesReceived() && resendPaymentReceivedMessagesEnabled() && resendPaymentReceivedMessagesWithinDuration(); + boolean hasNoPaymentReceivedMessages = getBuyer().getPaymentReceivedMessage() == null && getArbitrator().getPaymentReceivedMessage() == null; + if (!walletExists() && !hasNoPaymentReceivedMessages) return false; // cannot provide any updated state + return !isShutDownStarted() && getState().ordinal() >= Trade.State.SELLER_SENT_PAYMENT_RECEIVED_MSG.ordinal() && !getProcessModel().isPaymentReceivedMessagesAckedOrStored() && resendPaymentReceivedMessagesEnabled() && resendPaymentReceivedMessagesWithinDuration(); } private boolean resendPaymentReceivedMessagesEnabled() { @@ -81,4 +83,3 @@ public abstract class SellerTrade extends Trade { return new Date().getTime() <= (startDate.getTime() + resendPaymentReceivedMessagesDurationMs); } } - diff --git a/core/src/main/java/haveno/core/trade/Trade.java b/core/src/main/java/haveno/core/trade/Trade.java index 5a924b6d6a..d0eb5965b0 100644 --- a/core/src/main/java/haveno/core/trade/Trade.java +++ b/core/src/main/java/haveno/core/trade/Trade.java @@ -1704,14 +1704,14 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model { } // TODO: clear other process data - if (processModel.isPaymentReceivedMessagesReceived()) setPayoutTxHex(null); + if (processModel.isPaymentReceivedMessagesAckedOrStored()) setPayoutTxHex(null); for (TradePeer peer : getAllPeers()) { peer.setUpdatedMultisigHex(null); peer.setDisputeClosedMessage(null); peer.setPaymentSentMessage(null); peer.setDepositTxHex(null); peer.setDepositTxKey(null); - if (peer.isPaymentReceivedMessageReceived()) { + if (peer.isPaymentReceivedMessageAckedOrStored()) { peer.setUnsignedPayoutTxHex(null); peer.setPaymentReceivedMessage(null); } @@ -3249,8 +3249,8 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model { persistNow(null); - // send updated payment received message when payout is confirmed - if (resendPaymentReceivedMessages) { + // send updated payment received message if applicable + if (resendPaymentReceivedMessages && walletExists()) { if (!isSeller()) throw new IllegalArgumentException("Only the seller can resend PaymentReceivedMessages after a payout error for " + getClass().getSimpleName() + " " + getId()); if (!isPaymentReceived()) throw new IllegalStateException("Cannot resend PaymentReceivedMessages after a payout error for " + getClass().getSimpleName() + " " + getId() + " because payment not marked received"); log.warn("Sending updated PaymentReceivedMessages for {} {} after payout error", getClass().getSimpleName(), getId()); diff --git a/core/src/main/java/haveno/core/trade/protocol/ProcessModel.java b/core/src/main/java/haveno/core/trade/protocol/ProcessModel.java index 91d23d40f4..68a1dcdbb7 100644 --- a/core/src/main/java/haveno/core/trade/protocol/ProcessModel.java +++ b/core/src/main/java/haveno/core/trade/protocol/ProcessModel.java @@ -309,8 +309,8 @@ public class ProcessModel implements Model, PersistablePayload { return getP2PService().getAddress(); } - public boolean isPaymentReceivedMessagesReceived() { - return getArbitrator().isPaymentReceivedMessageReceived() && getBuyer().isPaymentReceivedMessageReceived(); + public boolean isPaymentReceivedMessagesAckedOrStored() { + return getArbitrator().isPaymentReceivedMessageAckedOrStored() && getBuyer().isPaymentReceivedMessageAckedOrStored(); } void setDepositTxSentAckMessage(AckMessage ackMessage) { diff --git a/core/src/main/java/haveno/core/trade/protocol/TradePeer.java b/core/src/main/java/haveno/core/trade/protocol/TradePeer.java index 3116746dd6..7e3fb6184d 100644 --- a/core/src/main/java/haveno/core/trade/protocol/TradePeer.java +++ b/core/src/main/java/haveno/core/trade/protocol/TradePeer.java @@ -256,9 +256,17 @@ public final class TradePeer implements PersistablePayload { } public boolean isPaymentReceivedMessageReceived() { + return isPaymentReceivedMessageAckedOrStored() || isPaymentReceivedMessageNacked(); + } + + public boolean isPaymentReceivedMessageAckedOrStored() { return paymentReceivedMessageStateProperty.get() == MessageState.ACKNOWLEDGED || paymentReceivedMessageStateProperty.get() == MessageState.STORED_IN_MAILBOX; } + public boolean isPaymentReceivedMessageNacked() { + return paymentReceivedMessageStateProperty.get() == MessageState.NACKED; + } + public boolean isPaymentReceivedMessageArrived() { return paymentReceivedMessageStateProperty.get() == MessageState.ARRIVED; } diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/SellerSendPaymentReceivedMessage.java b/core/src/main/java/haveno/core/trade/protocol/tasks/SellerSendPaymentReceivedMessage.java index 1c2b3bbc69..7ca993db30 100644 --- a/core/src/main/java/haveno/core/trade/protocol/tasks/SellerSendPaymentReceivedMessage.java +++ b/core/src/main/java/haveno/core/trade/protocol/tasks/SellerSendPaymentReceivedMessage.java @@ -74,6 +74,7 @@ public abstract class SellerSendPaymentReceivedMessage extends SendMailboxMessag private String unsignedPayoutTxHex = null; private String signedPayoutTxHex = null; private String updatedMultisigHex = null; + private PaymentReceivedMessage message = null; public SellerSendPaymentReceivedMessage(TaskRunner taskHandler, Trade trade) { super(taskHandler, trade); @@ -96,8 +97,10 @@ public abstract class SellerSendPaymentReceivedMessage extends SendMailboxMessag try { runInterceptHook(); - // reset ack state - getReceiver().setPaymentReceivedMessageState(MessageState.UNDEFINED); + // reset nack state + if (getReceiver().isPaymentReceivedMessageNacked()) { + getReceiver().setPaymentReceivedMessageState(MessageState.UNDEFINED); + } // skip if stopped if (stopSending()) { @@ -105,6 +108,8 @@ public abstract class SellerSendPaymentReceivedMessage extends SendMailboxMessag return; } + // reset ack state + getReceiver().setPaymentReceivedMessageState(MessageState.UNDEFINED); super.run(); } catch (Throwable t) { failed(t); @@ -116,13 +121,17 @@ public abstract class SellerSendPaymentReceivedMessage extends SendMailboxMessag if (getReceiver().getPaymentReceivedMessage() == null) { // sign account witness - AccountAgeWitnessService accountAgeWitnessService = processModel.getAccountAgeWitnessService(); - if (accountAgeWitnessService.isSignWitnessTrade(trade)) { - try { - accountAgeWitnessService.traderSignAndPublishPeersAccountAgeWitness(trade).ifPresent(witness -> signedWitness = witness); - log.info("{} {} signed and published peers account age witness", trade.getClass().getSimpleName(), trade.getId()); - } catch (Exception e) { - log.warn("Failed to sign and publish peer's account age witness for {} {}, error={}\n", getClass().getSimpleName(), trade.getId(), e.getMessage(), e); + if (trade.getSelf().getPaymentAccountPayload() == null) { + log.warn("Cannot sign account age witness for {} {} as no payment account is set", trade.getClass().getSimpleName(), trade.getId()); + } else { + AccountAgeWitnessService accountAgeWitnessService = processModel.getAccountAgeWitnessService(); + if (accountAgeWitnessService.isSignWitnessTrade(trade)) { + try { + accountAgeWitnessService.traderSignAndPublishPeersAccountAgeWitness(trade).ifPresent(witness -> signedWitness = witness); + log.info("{} {} signed and published peers account age witness", trade.getClass().getSimpleName(), trade.getId()); + } catch (Exception e) { + log.warn("Failed to sign and publish peer's account age witness for {} {}, error={}\n", getClass().getSimpleName(), trade.getId(), e.getMessage(), e); + } } } @@ -135,7 +144,7 @@ public abstract class SellerSendPaymentReceivedMessage extends SendMailboxMessag unsignedPayoutTxHex = trade.getPayoutTxHex() == null ? trade.getSelf().getUnsignedPayoutTxHex() : null; // signed signedPayoutTxHex = trade.getPayoutTxHex(); updatedMultisigHex = trade.getSelf().getUpdatedMultisigHex(); - PaymentReceivedMessage message = new PaymentReceivedMessage( + message = new PaymentReceivedMessage( tradeId, processModel.getMyNodeAddress(), deterministicId, @@ -258,6 +267,7 @@ public abstract class SellerSendPaymentReceivedMessage extends SendMailboxMessag if (trade.isPayoutPublished() && !((SellerTrade) trade).resendPaymentReceivedMessagesWithinDuration()) return true; // stop if payout is published and we are not in the resend period // check if message state is outdated + if (message != null && !message.equals(getReceiver().getPaymentReceivedMessage())) return true; if (unsignedPayoutTxHex != null && !StringUtils.equals(unsignedPayoutTxHex, trade.getSelf().getUnsignedPayoutTxHex())) return true; if (signedPayoutTxHex != null && !StringUtils.equals(signedPayoutTxHex, trade.getPayoutTxHex())) return true; if (updatedMultisigHex != null && !StringUtils.equals(updatedMultisigHex, trade.getSelf().getUpdatedMultisigHex())) return true;