diff --git a/core/src/main/java/haveno/core/trade/ArbitratorTrade.java b/core/src/main/java/haveno/core/trade/ArbitratorTrade.java index ea179a655a..0329763ad9 100644 --- a/core/src/main/java/haveno/core/trade/ArbitratorTrade.java +++ b/core/src/main/java/haveno/core/trade/ArbitratorTrade.java @@ -26,6 +26,7 @@ import haveno.network.p2p.NodeAddress; import lombok.extern.slf4j.Slf4j; import java.math.BigInteger; +import java.util.Date; import java.util.UUID; import javax.annotation.Nullable; @@ -36,62 +37,69 @@ import javax.annotation.Nullable; @Slf4j public class ArbitratorTrade extends Trade { - public ArbitratorTrade(Offer offer, - BigInteger tradeAmount, - long tradePrice, - XmrWalletService xmrWalletService, - ProcessModel processModel, - String uid, - NodeAddress makerNodeAddress, - NodeAddress takerNodeAddress, - NodeAddress arbitratorNodeAddress, - @Nullable String challenge) { - super(offer, tradeAmount, tradePrice, xmrWalletService, processModel, uid, makerNodeAddress, takerNodeAddress, arbitratorNodeAddress, challenge); - } + private static final long resendDisputeOpenedMessageDurationMs = 1L * 30 * 24 * 60 * 60 * 1000; // 30 days - @Override - public BigInteger getPayoutAmountBeforeCost() { - throw new RuntimeException("Arbitrator does not have a payout amount"); - } + public ArbitratorTrade(Offer offer, + BigInteger tradeAmount, + long tradePrice, + XmrWalletService xmrWalletService, + ProcessModel processModel, + String uid, + NodeAddress makerNodeAddress, + NodeAddress takerNodeAddress, + NodeAddress arbitratorNodeAddress, + @Nullable String challenge) { + super(offer, tradeAmount, tradePrice, xmrWalletService, processModel, uid, makerNodeAddress, takerNodeAddress, arbitratorNodeAddress, challenge); + } - /////////////////////////////////////////////////////////////////////////////////////////// - // PROTO BUFFER - /////////////////////////////////////////////////////////////////////////////////////////// + @Override + public BigInteger getPayoutAmountBeforeCost() { + throw new RuntimeException("Arbitrator does not have a payout amount"); + } - @Override - public protobuf.Tradable toProtoMessage() { - return protobuf.Tradable.newBuilder() - .setArbitratorTrade(protobuf.ArbitratorTrade.newBuilder() - .setTrade((protobuf.Trade) super.toProtoMessage())) - .build(); - } + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// - public static Tradable fromProto(protobuf.ArbitratorTrade arbitratorTradeProto, - XmrWalletService xmrWalletService, - CoreProtoResolver coreProtoResolver) { - protobuf.Trade proto = arbitratorTradeProto.getTrade(); - ProcessModel processModel = ProcessModel.fromProto(proto.getProcessModel(), coreProtoResolver); - String uid = ProtoUtil.stringOrNullFromProto(proto.getUid()); - if (uid == null) { - uid = UUID.randomUUID().toString(); - } - return fromProto(new ArbitratorTrade( - Offer.fromProto(proto.getOffer()), - BigInteger.valueOf(proto.getAmount()), - proto.getPrice(), - xmrWalletService, - processModel, - uid, - proto.getProcessModel().getMaker().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getMaker().getNodeAddress()) : null, - proto.getProcessModel().getTaker().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getTaker().getNodeAddress()) : null, - proto.getProcessModel().getArbitrator().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getArbitrator().getNodeAddress()) : null, - ProtoUtil.stringOrNullFromProto(proto.getChallenge())), - proto, - coreProtoResolver); - } + @Override + public protobuf.Tradable toProtoMessage() { + return protobuf.Tradable.newBuilder() + .setArbitratorTrade(protobuf.ArbitratorTrade.newBuilder() + .setTrade((protobuf.Trade) super.toProtoMessage())) + .build(); + } - @Override - public boolean confirmPermitted() { - throw new RuntimeException("ArbitratorTrade.confirmPermitted() not implemented"); // TODO (woodser): implement - } + public static Tradable fromProto(protobuf.ArbitratorTrade arbitratorTradeProto, + XmrWalletService xmrWalletService, + CoreProtoResolver coreProtoResolver) { + protobuf.Trade proto = arbitratorTradeProto.getTrade(); + ProcessModel processModel = ProcessModel.fromProto(proto.getProcessModel(), coreProtoResolver); + String uid = ProtoUtil.stringOrNullFromProto(proto.getUid()); + if (uid == null) { + uid = UUID.randomUUID().toString(); + } + return fromProto(new ArbitratorTrade( + Offer.fromProto(proto.getOffer()), + BigInteger.valueOf(proto.getAmount()), + proto.getPrice(), + xmrWalletService, + processModel, + uid, + proto.getProcessModel().getMaker().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getMaker().getNodeAddress()) : null, + proto.getProcessModel().getTaker().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getTaker().getNodeAddress()) : null, + proto.getProcessModel().getArbitrator().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getArbitrator().getNodeAddress()) : null, + ProtoUtil.stringOrNullFromProto(proto.getChallenge())), + proto, + coreProtoResolver); + } + + @Override + public boolean confirmPermitted() { + throw new RuntimeException("ArbitratorTrade.confirmPermitted() not implemented"); // TODO (woodser): implement + } + + public boolean resendDisputeOpenedMessageWithinDuration() { + Date startDate = getMaxTradePeriodDate(); + return new Date().getTime() <= (startDate.getTime() + resendDisputeOpenedMessageDurationMs); + } } diff --git a/core/src/main/java/haveno/core/trade/SellerTrade.java b/core/src/main/java/haveno/core/trade/SellerTrade.java index 0369d1b52a..44f5149a6e 100644 --- a/core/src/main/java/haveno/core/trade/SellerTrade.java +++ b/core/src/main/java/haveno/core/trade/SellerTrade.java @@ -30,7 +30,7 @@ import java.util.Date; @Slf4j public abstract class SellerTrade extends Trade { - private static final long resendPaymentReceivedMessagesDurationMs = 1L * 30 * 24 * 60 * 60 * 1000; // ~1 month + private static final long resendPaymentReceivedMessagesDurationMs = 1L * 30 * 24 * 60 * 60 * 1000; // 30 days SellerTrade(Offer offer, BigInteger tradeAmount, diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/ArbitratorSendDisputeOpenedMessage.java b/core/src/main/java/haveno/core/trade/protocol/tasks/ArbitratorSendDisputeOpenedMessage.java index cb3665b023..81bf59710b 100644 --- a/core/src/main/java/haveno/core/trade/protocol/tasks/ArbitratorSendDisputeOpenedMessage.java +++ b/core/src/main/java/haveno/core/trade/protocol/tasks/ArbitratorSendDisputeOpenedMessage.java @@ -25,7 +25,9 @@ import haveno.common.UserThread; import haveno.common.taskrunner.TaskRunner; import haveno.core.network.MessageState; import haveno.core.support.dispute.Dispute; +import haveno.core.support.dispute.messages.DisputeOpenedMessage; import haveno.core.support.messages.ChatMessage; +import haveno.core.trade.ArbitratorTrade; import haveno.core.trade.HavenoUtils; import haveno.core.trade.Trade; import haveno.network.p2p.mailbox.MailboxMessage; @@ -50,6 +52,7 @@ public abstract class ArbitratorSendDisputeOpenedMessage extends SendMailboxMess private static final int MAX_RESEND_ATTEMPTS = 20; private int delayInMin = 10; private int resendCounter = 0; + private DisputeOpenedMessage message = null; public ArbitratorSendDisputeOpenedMessage(TaskRunner taskHandler, Trade trade) { super(taskHandler, trade); @@ -60,12 +63,19 @@ public abstract class ArbitratorSendDisputeOpenedMessage extends SendMailboxMess try { runInterceptHook(); + // reset nack state + if (getReceiver().isDisputeOpenedMessageReceived()) { + getReceiver().setDisputeOpenedMessageState(MessageState.UNDEFINED); + } + // skip if not applicable or already acked - if (getReceiver().getDisputeOpenedMessage() == null || isAckedByReceiver()) { + if (stopSending()) { if (!isCompleted()) complete(); return; } + // reset ack state + getReceiver().setPaymentReceivedMessageState(MessageState.UNDEFINED); super.run(); } catch (Throwable t) { failed(t); @@ -82,7 +92,8 @@ public abstract class ArbitratorSendDisputeOpenedMessage extends SendMailboxMess @Override protected MailboxMessage getMailboxMessage(String tradeId) { - return getReceiver().getDisputeOpenedMessage(); + if (message == null) message = getReceiver().getDisputeOpenedMessage(); + return message; } @Override @@ -125,7 +136,7 @@ public abstract class ArbitratorSendDisputeOpenedMessage extends SendMailboxMess private void tryToSendAgainLater() { // skip if already acked - if (isAckedByReceiver()) return; + if (stopSending()) return; if (resendCounter >= MAX_RESEND_ATTEMPTS) { cleanup(); @@ -159,12 +170,21 @@ public abstract class ArbitratorSendDisputeOpenedMessage extends SendMailboxMess } private void onMessageStateChange(MessageState newValue) { - if (isAckedByReceiver()) { + if (isMessageReceived()) { cleanup(); } } - protected boolean isAckedByReceiver() { + protected boolean isMessageReceived() { return getReceiver().isDisputeOpenedMessageReceived(); } + + protected boolean stopSending() { + if (getReceiver().getDisputeOpenedMessage() == null) return true; // stop if no message to send + if (isMessageReceived()) return true; // stop if message received + if (trade.isPayoutPublished()) return true; // stop if payout is published + if (!((ArbitratorTrade) trade).resendDisputeOpenedMessageWithinDuration()) return true; // stop if payout is published and we are not in the resend period + if (message != null && !message.equals(getReceiver().getDisputeOpenedMessage())) return true; // stop if message state is outdated + return false; + } }