resend payment sent msg until ack, payment received msg if not published

improve payment confirmation states in ui
This commit is contained in:
woodser 2023-04-20 15:04:35 -04:00
parent aea831bafd
commit 7e8e145c85
4 changed files with 83 additions and 44 deletions

View file

@ -53,9 +53,9 @@ public class BuyerProtocol extends DisputeProtocol {
protected void onInitialized() { protected void onInitialized() {
super.onInitialized(); super.onInitialized();
// re-send payment sent message if not arrived // re-send payment sent message if not acked
synchronized (trade) { synchronized (trade) {
if (trade.getState().ordinal() >= Trade.State.BUYER_SENT_PAYMENT_SENT_MSG.ordinal() && trade.getState().ordinal() <= Trade.State.BUYER_SEND_FAILED_PAYMENT_SENT_MSG.ordinal()) { if (trade.getState().ordinal() >= Trade.State.BUYER_SENT_PAYMENT_SENT_MSG.ordinal() && trade.getState().ordinal() < Trade.State.SELLER_RECEIVED_PAYMENT_SENT_MSG.ordinal()) {
latchTrade(); latchTrade();
given(anyPhase(Trade.Phase.PAYMENT_SENT) given(anyPhase(Trade.Phase.PAYMENT_SENT)
.with(BuyerEvent.STARTUP)) .with(BuyerEvent.STARTUP))

View file

@ -49,9 +49,9 @@ public class SellerProtocol extends DisputeProtocol {
protected void onInitialized() { protected void onInitialized() {
super.onInitialized(); super.onInitialized();
// re-send payment received message if not arrived // re-send payment received message if payout not published
synchronized (trade) { synchronized (trade) {
if (trade.getState().ordinal() >= Trade.State.SELLER_SENT_PAYMENT_RECEIVED_MSG.ordinal() && trade.getState().ordinal() <= Trade.State.SELLER_SEND_FAILED_PAYMENT_RECEIVED_MSG.ordinal()) { if (trade.getState().ordinal() >= Trade.State.SELLER_SENT_PAYMENT_RECEIVED_MSG.ordinal() && !trade.isPayoutPublished()) {
latchTrade(); latchTrade();
given(anyPhase(Trade.Phase.PAYMENT_RECEIVED) given(anyPhase(Trade.Phase.PAYMENT_RECEIVED)
.with(SellerEvent.STARTUP)) .with(SellerEvent.STARTUP))

View file

@ -17,7 +17,10 @@
package haveno.core.trade.protocol.tasks; package haveno.core.trade.protocol.tasks;
import java.util.concurrent.TimeUnit;
import haveno.common.Timer; 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.network.MessageState; import haveno.core.network.MessageState;
@ -45,6 +48,9 @@ import lombok.extern.slf4j.Slf4j;
public abstract class BuyerSendPaymentSentMessage extends SendMailboxMessageTask { public abstract class BuyerSendPaymentSentMessage extends SendMailboxMessageTask {
private ChangeListener<MessageState> listener; private ChangeListener<MessageState> listener;
private Timer timer; private Timer timer;
private static final int MAX_RESEND_ATTEMPTS = 10;
private int delayInMin = 10;
private int resendCounter = 0;
public BuyerSendPaymentSentMessage(TaskRunner<Trade> taskHandler, Trade trade) { public BuyerSendPaymentSentMessage(TaskRunner<Trade> taskHandler, Trade trade) {
super(taskHandler, trade); super(taskHandler, trade);
@ -61,8 +67,6 @@ public abstract class BuyerSendPaymentSentMessage extends SendMailboxMessageTask
super.run(); super.run();
} catch (Throwable t) { } catch (Throwable t) {
failed(t); failed(t);
} finally {
cleanup();
} }
} }
@ -105,22 +109,21 @@ public abstract class BuyerSendPaymentSentMessage extends SendMailboxMessageTask
@Override @Override
protected void setStateSent() { protected void setStateSent() {
if (trade.getState().ordinal() < Trade.State.BUYER_SENT_PAYMENT_SENT_MSG.ordinal()) { if (trade.getState().ordinal() < Trade.State.BUYER_SENT_PAYMENT_SENT_MSG.ordinal()) trade.setStateIfValidTransitionTo(Trade.State.BUYER_SENT_PAYMENT_SENT_MSG);
trade.setStateIfValidTransitionTo(Trade.State.BUYER_SENT_PAYMENT_SENT_MSG); tryToSendAgainLater();
}
processModel.getTradeManager().requestPersistence(); processModel.getTradeManager().requestPersistence();
} }
@Override @Override
protected void setStateArrived() { protected void setStateArrived() {
trade.setStateIfValidTransitionTo(Trade.State.BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG); trade.setStateIfValidTransitionTo(Trade.State.BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG);
processModel.getTradeManager().requestPersistence();
} }
@Override @Override
protected void setStateStoredInMailbox() { protected void setStateStoredInMailbox() {
trade.setStateIfValidTransitionTo(Trade.State.BUYER_STORED_IN_MAILBOX_PAYMENT_SENT_MSG); trade.setStateIfValidTransitionTo(Trade.State.BUYER_STORED_IN_MAILBOX_PAYMENT_SENT_MSG);
processModel.getTradeManager().requestPersistence(); processModel.getTradeManager().requestPersistence();
// TODO: schedule repeat sending like haveno?
} }
@Override @Override
@ -137,4 +140,40 @@ public abstract class BuyerSendPaymentSentMessage extends SendMailboxMessageTask
processModel.getPaymentSentMessageStateProperty().removeListener(listener); processModel.getPaymentSentMessageStateProperty().removeListener(listener);
} }
} }
private void tryToSendAgainLater() {
// skip if already acked
if (trade.getState().ordinal() >= Trade.State.SELLER_RECEIVED_PAYMENT_SENT_MSG.ordinal()) return;
if (resendCounter >= MAX_RESEND_ATTEMPTS) {
cleanup();
log.warn("We never received an ACK message when sending the PaymentSentMessage to the peer. We stop trying to send the message.");
return;
}
log.info("We will send the message again to the peer after a delay of {} min.", delayInMin);
if (timer != null) {
timer.stop();
}
timer = UserThread.runAfter(this::run, delayInMin, TimeUnit.MINUTES);
if (resendCounter == 0) {
listener = (observable, oldValue, newValue) -> onMessageStateChange(newValue);
processModel.getPaymentSentMessageStateProperty().addListener(listener);
onMessageStateChange(processModel.getPaymentSentMessageStateProperty().get());
}
delayInMin = delayInMin * 2;
resendCounter++;
}
private void onMessageStateChange(MessageState newValue) {
if (newValue == MessageState.ACKNOWLEDGED) {
trade.setStateIfValidTransitionTo(Trade.State.SELLER_RECEIVED_PAYMENT_SENT_MSG);
processModel.getTradeManager().requestPersistence();
cleanup();
}
}
} }

View file

@ -149,43 +149,43 @@ public class BuyerStep2View extends TradeStepView {
if (trade.isDepositsUnlocked() && !trade.isPaymentSent()) { if (trade.isDepositsUnlocked() && !trade.isPaymentSent()) {
showPopup(); showPopup();
} else if (state.ordinal() <= Trade.State.BUYER_SEND_FAILED_PAYMENT_SENT_MSG.ordinal()) { } else if (state.ordinal() <= Trade.State.SELLER_RECEIVED_PAYMENT_SENT_MSG.ordinal()) {
switch (state) { switch (state) {
case BUYER_CONFIRMED_IN_UI_PAYMENT_SENT: case BUYER_CONFIRMED_IN_UI_PAYMENT_SENT:
busyAnimation.play(); busyAnimation.play();
statusLabel.setText(Res.get("shared.preparingConfirmation")); statusLabel.setText(Res.get("shared.preparingConfirmation"));
break; break;
case BUYER_SENT_PAYMENT_SENT_MSG: case BUYER_SENT_PAYMENT_SENT_MSG:
busyAnimation.play(); busyAnimation.play();
statusLabel.setText(Res.get("shared.sendingConfirmation")); statusLabel.setText(Res.get("shared.sendingConfirmation"));
model.setMessageStateProperty(MessageState.SENT); model.setMessageStateProperty(MessageState.SENT);
timeoutTimer = UserThread.runAfter(() -> { timeoutTimer = UserThread.runAfter(() -> {
busyAnimation.stop();
statusLabel.setText(Res.get("shared.sendingConfirmationAgain"));
}, 10);
break;
case BUYER_STORED_IN_MAILBOX_PAYMENT_SENT_MSG:
busyAnimation.stop();
statusLabel.setText(Res.get("shared.messageStoredInMailbox"));
model.setMessageStateProperty(MessageState.STORED_IN_MAILBOX);
break;
case SELLER_RECEIVED_PAYMENT_SENT_MSG:
busyAnimation.stop();
statusLabel.setText(Res.get("shared.messageArrived"));
model.setMessageStateProperty(MessageState.ARRIVED);
break;
case BUYER_SEND_FAILED_PAYMENT_SENT_MSG:
// We get a popup and the trade closed, so we dont need to show anything here
busyAnimation.stop();
statusLabel.setText("");
model.setMessageStateProperty(MessageState.FAILED);
break;
default:
log.warn("Unexpected case: State={}, tradeId={} ", state.name(), trade.getId());
busyAnimation.stop(); busyAnimation.stop();
statusLabel.setText(Res.get("shared.sendingConfirmationAgain")); statusLabel.setText(Res.get("shared.sendingConfirmationAgain"));
}, 10); break;
break; }
case BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG:
busyAnimation.stop();
statusLabel.setText(Res.get("shared.messageArrived"));
model.setMessageStateProperty(MessageState.ARRIVED);
break;
case BUYER_STORED_IN_MAILBOX_PAYMENT_SENT_MSG:
busyAnimation.stop();
statusLabel.setText(Res.get("shared.messageStoredInMailbox"));
model.setMessageStateProperty(MessageState.STORED_IN_MAILBOX);
break;
case BUYER_SEND_FAILED_PAYMENT_SENT_MSG:
// We get a popup and the trade closed, so we dont need to show anything here
busyAnimation.stop();
statusLabel.setText("");
model.setMessageStateProperty(MessageState.FAILED);
break;
default:
log.warn("Unexpected case: State={}, tradeId={} ", state.name(), trade.getId());
busyAnimation.stop();
statusLabel.setText(Res.get("shared.sendingConfirmationAgain"));
break;
}
} }
}); });
} }