mirror of
https://github.com/haveno-dex/haveno.git
synced 2024-11-17 08:17:57 +00:00
re-send deposits confirmed messages until acked
This commit is contained in:
parent
5c1cfdcff9
commit
1fdb02bd1f
8 changed files with 92 additions and 35 deletions
|
@ -220,9 +220,9 @@ public class OfferFilterService {
|
||||||
|
|
||||||
public boolean hasValidArbitrator(Offer offer) {
|
public boolean hasValidArbitrator(Offer offer) {
|
||||||
Arbitrator arbitrator = user.getAcceptedArbitratorByAddress(offer.getOfferPayload().getArbitratorSigner());
|
Arbitrator arbitrator = user.getAcceptedArbitratorByAddress(offer.getOfferPayload().getArbitratorSigner());
|
||||||
if (arbitrator == null) {
|
if (arbitrator == null && offer.getOfferPayload().getArbitratorSigner() != null) {
|
||||||
List<NodeAddress> arbitratorAddresses = user.getAcceptedArbitrators().stream().map(Arbitrator::getNodeAddress).collect(Collectors.toList());
|
List<NodeAddress> arbitratorAddresses = user.getAcceptedArbitrators().stream().map(Arbitrator::getNodeAddress).collect(Collectors.toList());
|
||||||
log.warn("No arbitrator registered with offer's signer. offerId={}. Accepted arbitrators={}", offer.getOfferPayload().getArbitratorSigner(), arbitratorAddresses);
|
log.warn("No arbitrator is registered with offer's signer. offerId={}, arbitrator signer={}, accepted arbitrators={}", offer.getId(), offer.getOfferPayload().getArbitratorSigner(), arbitratorAddresses);
|
||||||
}
|
}
|
||||||
return arbitrator != null;
|
return arbitrator != null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -658,17 +658,6 @@ public abstract class Trade implements Tradable, Model {
|
||||||
xmrWalletService.addWalletListener(idlePayoutSyncer);
|
xmrWalletService.addWalletListener(idlePayoutSyncer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// send deposit confirmed message on startup or event
|
|
||||||
if (isDepositsConfirmed()) {
|
|
||||||
new Thread(() -> getProtocol().maybeSendDepositsConfirmedMessages()).start();
|
|
||||||
} else {
|
|
||||||
EasyBind.subscribe(stateProperty(), state -> {
|
|
||||||
if (isDepositsConfirmed()) {
|
|
||||||
new Thread(() -> getProtocol().maybeSendDepositsConfirmedMessages()).start();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// reprocess pending payout messages
|
// reprocess pending payout messages
|
||||||
this.getProtocol().maybeReprocessPaymentReceivedMessage(false);
|
this.getProtocol().maybeReprocessPaymentReceivedMessage(false);
|
||||||
HavenoUtils.arbitrationManager.maybeReprocessDisputeClosedMessage(this, false);
|
HavenoUtils.arbitrationManager.maybeReprocessDisputeClosedMessage(this, false);
|
||||||
|
|
|
@ -487,7 +487,6 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||||
if (getTradeProtocol(trade) != null) return;
|
if (getTradeProtocol(trade) != null) return;
|
||||||
initTradeAndProtocol(trade, createTradeProtocol(trade));
|
initTradeAndProtocol(trade, createTradeProtocol(trade));
|
||||||
requestPersistence();
|
requestPersistence();
|
||||||
listenForCleanup(trade);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initTradeAndProtocol(Trade trade, TradeProtocol tradeProtocol) {
|
private void initTradeAndProtocol(Trade trade, TradeProtocol tradeProtocol) {
|
||||||
|
|
|
@ -148,10 +148,6 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||||
@Setter
|
@Setter
|
||||||
private String multisigAddress;
|
private String multisigAddress;
|
||||||
@Nullable
|
@Nullable
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private boolean isDepositsConfirmedMessagesDelivered;
|
|
||||||
@Nullable
|
|
||||||
@Setter
|
@Setter
|
||||||
@Getter
|
@Getter
|
||||||
private PaymentSentMessage paymentSentMessage;
|
private PaymentSentMessage paymentSentMessage;
|
||||||
|
@ -207,8 +203,7 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||||
.setFundsNeededForTrade(fundsNeededForTrade)
|
.setFundsNeededForTrade(fundsNeededForTrade)
|
||||||
.setPaymentSentMessageState(paymentSentMessageStateProperty.get().name())
|
.setPaymentSentMessageState(paymentSentMessageStateProperty.get().name())
|
||||||
.setBuyerPayoutAmountFromMediation(buyerPayoutAmountFromMediation)
|
.setBuyerPayoutAmountFromMediation(buyerPayoutAmountFromMediation)
|
||||||
.setSellerPayoutAmountFromMediation(sellerPayoutAmountFromMediation)
|
.setSellerPayoutAmountFromMediation(sellerPayoutAmountFromMediation);
|
||||||
.setDepositsConfirmedMessagesDelivered(isDepositsConfirmedMessagesDelivered);
|
|
||||||
Optional.ofNullable(maker).ifPresent(e -> builder.setMaker((protobuf.TradePeer) maker.toProtoMessage()));
|
Optional.ofNullable(maker).ifPresent(e -> builder.setMaker((protobuf.TradePeer) maker.toProtoMessage()));
|
||||||
Optional.ofNullable(taker).ifPresent(e -> builder.setTaker((protobuf.TradePeer) taker.toProtoMessage()));
|
Optional.ofNullable(taker).ifPresent(e -> builder.setTaker((protobuf.TradePeer) taker.toProtoMessage()));
|
||||||
Optional.ofNullable(arbitrator).ifPresent(e -> builder.setArbitrator((protobuf.TradePeer) arbitrator.toProtoMessage()));
|
Optional.ofNullable(arbitrator).ifPresent(e -> builder.setArbitrator((protobuf.TradePeer) arbitrator.toProtoMessage()));
|
||||||
|
@ -234,7 +229,6 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||||
processModel.setFundsNeededForTrade(proto.getFundsNeededForTrade());
|
processModel.setFundsNeededForTrade(proto.getFundsNeededForTrade());
|
||||||
processModel.setBuyerPayoutAmountFromMediation(proto.getBuyerPayoutAmountFromMediation());
|
processModel.setBuyerPayoutAmountFromMediation(proto.getBuyerPayoutAmountFromMediation());
|
||||||
processModel.setSellerPayoutAmountFromMediation(proto.getSellerPayoutAmountFromMediation());
|
processModel.setSellerPayoutAmountFromMediation(proto.getSellerPayoutAmountFromMediation());
|
||||||
processModel.setDepositsConfirmedMessagesDelivered(proto.getDepositsConfirmedMessagesDelivered());
|
|
||||||
|
|
||||||
// nullable
|
// nullable
|
||||||
processModel.setTakeOfferFeeTxId(ProtoUtil.stringOrNullFromProto(proto.getTakeOfferFeeTxId()));
|
processModel.setTakeOfferFeeTxId(ProtoUtil.stringOrNullFromProto(proto.getTakeOfferFeeTxId()));
|
||||||
|
|
|
@ -119,6 +119,9 @@ public final class TradePeer implements PersistablePayload {
|
||||||
private long securityDeposit;
|
private long securityDeposit;
|
||||||
@Nullable
|
@Nullable
|
||||||
private String updatedMultisigHex;
|
private String updatedMultisigHex;
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
boolean depositsConfirmedMessageAcked;
|
||||||
|
|
||||||
public TradePeer() {
|
public TradePeer() {
|
||||||
}
|
}
|
||||||
|
@ -163,6 +166,7 @@ public final class TradePeer implements PersistablePayload {
|
||||||
Optional.ofNullable(depositTxKey).ifPresent(e -> builder.setDepositTxKey(depositTxKey));
|
Optional.ofNullable(depositTxKey).ifPresent(e -> builder.setDepositTxKey(depositTxKey));
|
||||||
Optional.ofNullable(securityDeposit).ifPresent(e -> builder.setSecurityDeposit(securityDeposit));
|
Optional.ofNullable(securityDeposit).ifPresent(e -> builder.setSecurityDeposit(securityDeposit));
|
||||||
Optional.ofNullable(updatedMultisigHex).ifPresent(e -> builder.setUpdatedMultisigHex(updatedMultisigHex));
|
Optional.ofNullable(updatedMultisigHex).ifPresent(e -> builder.setUpdatedMultisigHex(updatedMultisigHex));
|
||||||
|
builder.setDepositsConfirmedMessageAcked(depositsConfirmedMessageAcked);
|
||||||
|
|
||||||
builder.setCurrentDate(currentDate);
|
builder.setCurrentDate(currentDate);
|
||||||
return builder.build();
|
return builder.build();
|
||||||
|
@ -204,6 +208,7 @@ public final class TradePeer implements PersistablePayload {
|
||||||
tradePeer.setDepositTxKey(ProtoUtil.stringOrNullFromProto(proto.getDepositTxKey()));
|
tradePeer.setDepositTxKey(ProtoUtil.stringOrNullFromProto(proto.getDepositTxKey()));
|
||||||
tradePeer.setSecurityDeposit(BigInteger.valueOf(proto.getSecurityDeposit()));
|
tradePeer.setSecurityDeposit(BigInteger.valueOf(proto.getSecurityDeposit()));
|
||||||
tradePeer.setUpdatedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getUpdatedMultisigHex()));
|
tradePeer.setUpdatedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getUpdatedMultisigHex()));
|
||||||
|
tradePeer.setDepositsConfirmedMessageAcked(proto.getDepositsConfirmedMessageAcked());
|
||||||
return tradePeer;
|
return tradePeer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,7 +75,6 @@ import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public abstract class TradeProtocol implements DecryptedDirectMessageListener, DecryptedMailboxListener {
|
public abstract class TradeProtocol implements DecryptedDirectMessageListener, DecryptedMailboxListener {
|
||||||
|
@ -255,6 +254,21 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||||
MailboxMessageService mailboxMessageService = processModel.getP2PService().getMailboxMessageService();
|
MailboxMessageService mailboxMessageService = processModel.getP2PService().getMailboxMessageService();
|
||||||
if (!trade.isCompleted()) mailboxMessageService.addDecryptedMailboxListener(this);
|
if (!trade.isCompleted()) mailboxMessageService.addDecryptedMailboxListener(this);
|
||||||
handleMailboxCollection(mailboxMessageService.getMyDecryptedMailboxMessages());
|
handleMailboxCollection(mailboxMessageService.getMyDecryptedMailboxMessages());
|
||||||
|
|
||||||
|
// send deposits confirmed message if applicable
|
||||||
|
maybeSendDepositsConfirmedMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeSendDepositsConfirmedMessage() {
|
||||||
|
if (trade.isDepositsConfirmed()) {
|
||||||
|
new Thread(() -> maybeSendDepositsConfirmedMessages()).start();
|
||||||
|
} else {
|
||||||
|
EasyBind.subscribe(trade.stateProperty(), state -> {
|
||||||
|
if (trade.isDepositsConfirmed()) {
|
||||||
|
new Thread(() -> maybeSendDepositsConfirmedMessages()).start();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void maybeReprocessPaymentReceivedMessage(boolean reprocessOnError) {
|
public void maybeReprocessPaymentReceivedMessage(boolean reprocessOnError) {
|
||||||
|
@ -617,6 +631,13 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||||
if (ackMessage.isSuccess()) {
|
if (ackMessage.isSuccess()) {
|
||||||
log.info("Received AckMessage for {} from {} with tradeId {} and uid {}",
|
log.info("Received AckMessage for {} from {} with tradeId {} and uid {}",
|
||||||
ackMessage.getSourceMsgClassName(), peer, trade.getId(), ackMessage.getSourceUid());
|
ackMessage.getSourceMsgClassName(), peer, trade.getId(), ackMessage.getSourceUid());
|
||||||
|
|
||||||
|
// handle ack for DepositsConfirmedMessage, which automatically re-sends if not ACKed in a certain time
|
||||||
|
if (ackMessage.getSourceMsgClassName().equals(DepositsConfirmedMessage.class.getSimpleName())) {
|
||||||
|
if (trade.getTradePeer(peer) != null) {
|
||||||
|
trade.getTradePeer(peer).setDepositsConfirmedMessageAcked(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
String err = "Received AckMessage with error state for " + ackMessage.getSourceMsgClassName() + " from "+ peer + " with tradeId " + trade.getId() + " and errorMessage=" + ackMessage.getErrorMessage();
|
String err = "Received AckMessage with error state for " + ackMessage.getSourceMsgClassName() + " from "+ peer + " with tradeId " + trade.getId() + " and errorMessage=" + ackMessage.getErrorMessage();
|
||||||
log.warn(err);
|
log.warn(err);
|
||||||
|
@ -834,8 +855,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||||
return tradeMessage.getTradeId().equals(trade.getId());
|
return tradeMessage.getTradeId().equals(trade.getId());
|
||||||
} else if (message instanceof AckMessage) {
|
} else if (message instanceof AckMessage) {
|
||||||
AckMessage ackMessage = (AckMessage) message;
|
AckMessage ackMessage = (AckMessage) message;
|
||||||
return ackMessage.getSourceType() == AckMessageSourceType.TRADE_MESSAGE &&
|
return ackMessage.getSourceType() == AckMessageSourceType.TRADE_MESSAGE && ackMessage.getSourceId().equals(trade.getId());
|
||||||
ackMessage.getSourceId().equals(trade.getId());
|
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -845,22 +865,15 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||||
if (trade.isShutDownStarted()) return;
|
if (trade.isShutDownStarted()) return;
|
||||||
synchronized (trade) {
|
synchronized (trade) {
|
||||||
if (!trade.isInitialized()) return; // skip if shutting down
|
if (!trade.isInitialized()) return; // skip if shutting down
|
||||||
if (trade.getProcessModel().isDepositsConfirmedMessagesDelivered()) return; // skip if already delivered
|
|
||||||
latchTrade();
|
latchTrade();
|
||||||
expect(new Condition(trade))
|
expect(new Condition(trade))
|
||||||
.setup(tasks(getDepositsConfirmedTasks())
|
.setup(tasks(getDepositsConfirmedTasks())
|
||||||
.using(new TradeTaskRunner(trade,
|
.using(new TradeTaskRunner(trade,
|
||||||
() -> {
|
() -> {
|
||||||
trade.getProcessModel().setDepositsConfirmedMessagesDelivered(true);
|
handleTaskRunnerSuccess(null, null, "maybeSendDepositsConfirmedMessages");
|
||||||
handleTaskRunnerSuccess(null, null, "SendDepositsConfirmedMessages");
|
|
||||||
},
|
},
|
||||||
(errorMessage) -> {
|
(errorMessage) -> {
|
||||||
|
handleTaskRunnerFault(null, null, "maybeSendDepositsConfirmedMessages", errorMessage);
|
||||||
// retry in 15 minutes
|
|
||||||
UserThread.runAfter(() -> {
|
|
||||||
maybeSendDepositsConfirmedMessages();
|
|
||||||
}, 15, TimeUnit.MINUTES);
|
|
||||||
handleTaskRunnerFault(null, null, "SendDepositsConfirmedMessages", errorMessage);
|
|
||||||
})))
|
})))
|
||||||
.executeTasks(true);
|
.executeTasks(true);
|
||||||
awaitTradeLatch();
|
awaitTradeLatch();
|
||||||
|
|
|
@ -17,12 +17,17 @@
|
||||||
|
|
||||||
package haveno.core.trade.protocol.tasks;
|
package haveno.core.trade.protocol.tasks;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import haveno.common.Timer;
|
||||||
|
import haveno.common.UserThread;
|
||||||
import haveno.common.crypto.PubKeyRing;
|
import haveno.common.crypto.PubKeyRing;
|
||||||
import haveno.common.taskrunner.TaskRunner;
|
import haveno.common.taskrunner.TaskRunner;
|
||||||
import haveno.core.trade.HavenoUtils;
|
import haveno.core.trade.HavenoUtils;
|
||||||
import haveno.core.trade.Trade;
|
import haveno.core.trade.Trade;
|
||||||
import haveno.core.trade.messages.DepositsConfirmedMessage;
|
import haveno.core.trade.messages.DepositsConfirmedMessage;
|
||||||
import haveno.core.trade.messages.TradeMailboxMessage;
|
import haveno.core.trade.messages.TradeMailboxMessage;
|
||||||
|
import haveno.core.trade.protocol.TradePeer;
|
||||||
import haveno.network.p2p.NodeAddress;
|
import haveno.network.p2p.NodeAddress;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@ -31,6 +36,11 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public abstract class SendDepositsConfirmedMessage extends SendMailboxMessageTask {
|
public abstract class SendDepositsConfirmedMessage extends SendMailboxMessageTask {
|
||||||
|
private Timer timer;
|
||||||
|
private static final int MAX_RESEND_ATTEMPTS = 10;
|
||||||
|
private int delayInMin = 10;
|
||||||
|
private int resendCounter = 0;
|
||||||
|
|
||||||
private DepositsConfirmedMessage message;
|
private DepositsConfirmedMessage message;
|
||||||
|
|
||||||
public SendDepositsConfirmedMessage(TaskRunner<Trade> taskHandler, Trade trade) {
|
public SendDepositsConfirmedMessage(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||||
|
@ -41,6 +51,13 @@ public abstract class SendDepositsConfirmedMessage extends SendMailboxMessageTas
|
||||||
protected void run() {
|
protected void run() {
|
||||||
try {
|
try {
|
||||||
runInterceptHook();
|
runInterceptHook();
|
||||||
|
|
||||||
|
// skip if already acked by receiver
|
||||||
|
if (ackedByReceiver()) {
|
||||||
|
complete();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
super.run();
|
super.run();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
failed(t);
|
failed(t);
|
||||||
|
@ -81,7 +98,8 @@ public abstract class SendDepositsConfirmedMessage extends SendMailboxMessageTas
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void setStateSent() {
|
protected void setStateSent() {
|
||||||
// no additional handling
|
tryToSendAgainLater();
|
||||||
|
processModel.getTradeManager().requestPersistence();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -98,4 +116,43 @@ public abstract class SendDepositsConfirmedMessage extends SendMailboxMessageTas
|
||||||
protected void setStateFault() {
|
protected void setStateFault() {
|
||||||
// no additional handling
|
// no additional handling
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void cleanup() {
|
||||||
|
if (timer != null) {
|
||||||
|
timer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryToSendAgainLater() {
|
||||||
|
|
||||||
|
// skip if already acked
|
||||||
|
if (ackedByReceiver()) return;
|
||||||
|
|
||||||
|
if (resendCounter >= MAX_RESEND_ATTEMPTS) {
|
||||||
|
cleanup();
|
||||||
|
log.warn("We never received an ACK message when sending the DepositsConfirmedMessage to the peer. We stop trying to send the message.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timer != null) {
|
||||||
|
timer.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// first re-send is after 2 minutes, then double the delay each iteration
|
||||||
|
if (resendCounter == 0) {
|
||||||
|
int shortDelay = 2;
|
||||||
|
log.info("We will send the message again to the peer after a delay of {} min.", shortDelay);
|
||||||
|
timer = UserThread.runAfter(this::run, shortDelay, TimeUnit.MINUTES);
|
||||||
|
} else {
|
||||||
|
log.info("We will send the message again to the peer after a delay of {} min.", delayInMin);
|
||||||
|
timer = UserThread.runAfter(this::run, delayInMin, TimeUnit.MINUTES);
|
||||||
|
delayInMin = delayInMin * 2;
|
||||||
|
}
|
||||||
|
resendCounter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean ackedByReceiver() {
|
||||||
|
TradePeer peer = trade.getTradePeer(getReceiverNodeAddress());
|
||||||
|
return peer.isDepositsConfirmedMessageAcked();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1530,7 +1530,6 @@ message ProcessModel {
|
||||||
bool use_savings_wallet = 6;
|
bool use_savings_wallet = 6;
|
||||||
int64 funds_needed_for_trade = 7;
|
int64 funds_needed_for_trade = 7;
|
||||||
string payment_sent_message_state = 8;
|
string payment_sent_message_state = 8;
|
||||||
bool deposits_confirmed_messages_delivered = 9;
|
|
||||||
bytes maker_signature = 10;
|
bytes maker_signature = 10;
|
||||||
TradePeer maker = 11;
|
TradePeer maker = 11;
|
||||||
TradePeer taker = 12;
|
TradePeer taker = 12;
|
||||||
|
@ -1576,6 +1575,7 @@ message TradePeer {
|
||||||
string deposit_tx_key = 1010;
|
string deposit_tx_key = 1010;
|
||||||
int64 security_deposit = 1011;
|
int64 security_deposit = 1011;
|
||||||
string updated_multisig_hex = 1012;
|
string updated_multisig_hex = 1012;
|
||||||
|
bool deposits_confirmed_message_acked = 1013;
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
Loading…
Reference in a new issue