mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-08-09 07:02:24 -04:00
reprocess payout messages on error to improve resilience
reprocess on curved schedule, restart, or connection change invalid messages are nacked using IllegalArgumentException disputes are considered open by ack on chat message don't show trade completion screen until payout published cannot confirm payment sent/received while disconnected from monerod add operation manual w/ instructions to manually open dispute close account before deletion fix popup with error "still unconfirmed after X hours" for arbitrator misc refactoring and cleanup
This commit is contained in:
parent
ef4c55e32f
commit
15d2c24a82
49 changed files with 841 additions and 471 deletions
|
@ -409,7 +409,7 @@ public class OfferDetailsWindow extends Overlay<OfferDetailsWindow> {
|
|||
placeOfferHandlerOptional.ifPresent(Runnable::run);
|
||||
} else {
|
||||
State lastState = Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS;
|
||||
spinnerInfoLabel.setText(Res.get("takeOffer.fundsBox.takeOfferSpinnerInfo") + " 1/" + (lastState.ordinal()));
|
||||
spinnerInfoLabel.setText(Res.get("takeOffer.fundsBox.takeOfferSpinnerInfo") + " 1/" + (lastState.ordinal() + 1));
|
||||
takeOfferHandlerOptional.ifPresent(Runnable::run);
|
||||
|
||||
// update trade state progress
|
||||
|
@ -417,7 +417,7 @@ public class OfferDetailsWindow extends Overlay<OfferDetailsWindow> {
|
|||
Trade trade = tradeManager.getTrade(offer.getId());
|
||||
if (trade == null) return;
|
||||
tradeStateSubscription = EasyBind.subscribe(trade.stateProperty(), newState -> {
|
||||
String progress = (newState.ordinal() + 1) + "/" + (lastState.ordinal());
|
||||
String progress = (newState.ordinal() + 1) + "/" + (lastState.ordinal() + 1);
|
||||
spinnerInfoLabel.setText(Res.get("takeOffer.fundsBox.takeOfferSpinnerInfo") + " " + progress);
|
||||
|
||||
// unsubscribe when done
|
||||
|
|
|
@ -299,7 +299,7 @@ public class TradeDetailsWindow extends Overlay<TradeDetailsWindow> {
|
|||
textArea.scrollTopProperty().addListener(changeListener);
|
||||
textArea.setScrollTop(30);
|
||||
|
||||
addConfirmationLabelTextField(gridPane, ++rowIndex, Res.get("tradeDetailsWindow.tradeState"), trade.getPhase().name());
|
||||
addConfirmationLabelTextField(gridPane, ++rowIndex, Res.get("tradeDetailsWindow.tradePhase"), trade.getPhase().name());
|
||||
}
|
||||
|
||||
Tuple3<Button, Button, HBox> tuple = add2ButtonsWithBox(gridPane, ++rowIndex,
|
||||
|
@ -322,10 +322,13 @@ public class TradeDetailsWindow extends Overlay<TradeDetailsWindow> {
|
|||
viewContractButton.setOnAction(e -> {
|
||||
TextArea textArea = new HavenoTextArea();
|
||||
textArea.setText(trade.getContractAsJson());
|
||||
String data = "Contract as json:\n";
|
||||
String data = "Trade state: " + trade.getState();
|
||||
data += "\nTrade payout state: " + trade.getPayoutState();
|
||||
data += "\nTrade dispute state: " + trade.getDisputeState();
|
||||
data += "\n\nContract as json:\n";
|
||||
data += trade.getContractAsJson();
|
||||
data += "\n\nOther detail data:";
|
||||
if (!trade.isDepositPublished()) {
|
||||
if (!trade.isDepositsPublished()) {
|
||||
data += "\n\n" + (trade.getMaker() == trade.getBuyer() ? "Buyer" : "Seller") + " as maker reserve tx hex: " + trade.getMaker().getReserveTxHex();
|
||||
data += "\n\n" + (trade.getTaker() == trade.getBuyer() ? "Buyer" : "Seller") + " as taker reserve tx hex: " + trade.getTaker().getReserveTxHex();
|
||||
}
|
||||
|
|
|
@ -32,7 +32,6 @@ import bisq.core.provider.mempool.MempoolService;
|
|||
import bisq.core.trade.ArbitratorTrade;
|
||||
import bisq.core.trade.BuyerTrade;
|
||||
import bisq.core.trade.ClosedTradableManager;
|
||||
import bisq.core.trade.Contract;
|
||||
import bisq.core.trade.HavenoUtils;
|
||||
import bisq.core.trade.SellerTrade;
|
||||
import bisq.core.trade.Trade;
|
||||
|
@ -433,21 +432,19 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
|
|||
buyerState.set(BuyerState.STEP2);
|
||||
break;
|
||||
|
||||
// seller step 3
|
||||
case SELLER_RECEIVED_PAYMENT_SENT_MSG: // PAYMENT_SENT_MSG received
|
||||
sellerState.set(SellerState.STEP3);
|
||||
break;
|
||||
|
||||
// seller step 4
|
||||
case SELLER_CONFIRMED_IN_UI_PAYMENT_RECEIPT: // UI action
|
||||
// payment received
|
||||
case SELLER_SENT_PAYMENT_RECEIVED_MSG:
|
||||
if (trade instanceof BuyerTrade) buyerState.set(BuyerState.STEP4);
|
||||
else if (trade instanceof SellerTrade) sellerState.set(SellerState.STEP3);
|
||||
else if (trade instanceof SellerTrade) sellerState.set(trade.isPayoutPublished() ? SellerState.STEP4 : SellerState.STEP3);
|
||||
break;
|
||||
case SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG:
|
||||
case SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG:
|
||||
|
||||
// seller step 3
|
||||
case SELLER_RECEIVED_PAYMENT_SENT_MSG: // PAYMENT_SENT_MSG received
|
||||
case SELLER_CONFIRMED_IN_UI_PAYMENT_RECEIPT:
|
||||
case SELLER_SEND_FAILED_PAYMENT_RECEIVED_MSG:
|
||||
sellerState.set(SellerState.STEP4);
|
||||
case SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG:
|
||||
case SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG:
|
||||
sellerState.set(trade.isPayoutPublished() ? SellerState.STEP4 : SellerState.STEP3);
|
||||
break;
|
||||
|
||||
case TRADE_COMPLETED:
|
||||
|
|
|
@ -801,8 +801,9 @@ public abstract class TradeStepView extends AnchorPane {
|
|||
// }
|
||||
// }
|
||||
|
||||
protected void checkForTimeout() {
|
||||
long unconfirmedHours = Duration.between(trade.getTakeOfferDate().toInstant(), Instant.now()).toHours();
|
||||
protected void checkForUnconfirmedTimeout() {
|
||||
if (trade.isDepositsConfirmed()) return;
|
||||
long unconfirmedHours = Duration.between(trade.getDate().toInstant(), Instant.now()).toHours();
|
||||
if (unconfirmedHours >= 3 && !trade.hasFailed()) {
|
||||
String key = "tradeUnconfirmedTooLong_" + trade.getShortId();
|
||||
if (DontShowAgainLookup.showAgain(key)) {
|
||||
|
|
|
@ -37,7 +37,7 @@ public class BuyerStep1View extends TradeStepView {
|
|||
super.onPendingTradesInitialized();
|
||||
//validatePayoutTx(); // TODO (woodser): no payout tx in xmr integration, do something else?
|
||||
//validateDepositInputs();
|
||||
checkForTimeout();
|
||||
checkForUnconfirmedTimeout();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
package bisq.desktop.main.portfolio.pendingtrades.steps.buyer;
|
||||
|
||||
import bisq.desktop.components.AutoTooltipButton;
|
||||
import bisq.desktop.components.BusyAnimation;
|
||||
import bisq.desktop.components.TextFieldWithCopyIcon;
|
||||
import bisq.desktop.components.TitledGroupBg;
|
||||
|
@ -155,7 +154,7 @@ public class BuyerStep2View extends TradeStepView {
|
|||
if (timeoutTimer != null)
|
||||
timeoutTimer.stop();
|
||||
|
||||
if (trade.isDepositUnlocked() && !trade.isPaymentSent()) {
|
||||
if (trade.isDepositsUnlocked() && !trade.isPaymentSent()) {
|
||||
showPopup();
|
||||
} else if (state.ordinal() <= Trade.State.BUYER_SEND_FAILED_PAYMENT_SENT_MSG.ordinal()) {
|
||||
if (!trade.hasFailed()) {
|
||||
|
@ -481,6 +480,10 @@ public class BuyerStep2View extends TradeStepView {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!model.dataModel.isReadyForTxBroadcast()) {
|
||||
return;
|
||||
}
|
||||
|
||||
PaymentAccountPayload sellersPaymentAccountPayload = model.dataModel.getSellersPaymentAccountPayload();
|
||||
Trade trade = checkNotNull(model.dataModel.getTrade(), "trade must not be null");
|
||||
if (sellersPaymentAccountPayload instanceof CashDepositAccountPayload) {
|
||||
|
|
|
@ -37,7 +37,7 @@ public class SellerStep1View extends TradeStepView {
|
|||
super.onPendingTradesInitialized();
|
||||
//validateDepositInputs();
|
||||
log.warn("Need to validate fee and/or deposit txs in SellerStep1View for XMR?"); // TODO (woodser): need to validate fee and/or deposit txs in SellerStep1View?
|
||||
checkForTimeout();
|
||||
checkForUnconfirmedTimeout();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -306,11 +306,16 @@ public class SellerStep3View extends TradeStepView {
|
|||
HBox hBox = tuple.fourth;
|
||||
GridPane.setColumnSpan(tuple.fourth, 2);
|
||||
confirmButton = tuple.first;
|
||||
confirmButton.setDisable(!confirmPaymentReceivedPermitted());
|
||||
confirmButton.setOnAction(e -> onPaymentReceived());
|
||||
busyAnimation = tuple.second;
|
||||
statusLabel = tuple.third;
|
||||
}
|
||||
|
||||
private boolean confirmPaymentReceivedPermitted() {
|
||||
if (!trade.confirmPermitted()) return false;
|
||||
return trade.getState().ordinal() >= Trade.State.BUYER_SENT_PAYMENT_SENT_MSG.ordinal() && trade.getState().ordinal() < Trade.State.SELLER_SENT_PAYMENT_RECEIVED_MSG.ordinal(); // TODO: test that can resen with same payout tx hex if delivery failed
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Info
|
||||
|
@ -357,7 +362,7 @@ public class SellerStep3View extends TradeStepView {
|
|||
protected void updateDisputeState(Trade.DisputeState disputeState) {
|
||||
super.updateDisputeState(disputeState);
|
||||
|
||||
confirmButton.setDisable(!trade.confirmPermitted());
|
||||
confirmButton.setDisable(!confirmPaymentReceivedPermitted());
|
||||
}
|
||||
|
||||
|
||||
|
@ -463,11 +468,14 @@ public class SellerStep3View extends TradeStepView {
|
|||
log.info("User pressed the [Confirm payment receipt] button for Trade {}", trade.getShortId());
|
||||
busyAnimation.play();
|
||||
statusLabel.setText(Res.get("shared.sendingConfirmation"));
|
||||
confirmButton.setDisable(true);
|
||||
|
||||
model.dataModel.onPaymentReceived(() -> {
|
||||
}, errorMessage -> {
|
||||
busyAnimation.stop();
|
||||
new Popup().warning(Res.get("popup.warning.sendMsgFailed")).show();
|
||||
confirmButton.setDisable(!confirmPaymentReceivedPermitted());
|
||||
UserThread.execute(() -> statusLabel.setText("Error confirming payment received."));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -50,6 +50,7 @@ import bisq.core.support.messages.ChatMessage;
|
|||
import bisq.core.trade.Contract;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.trade.Trade.DisputeState;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.util.FormattingUtils;
|
||||
import bisq.core.util.coin.CoinFormatter;
|
||||
|
@ -1341,18 +1342,21 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
|
||||
ReadOnlyBooleanProperty closedProperty;
|
||||
ChangeListener<Boolean> listener;
|
||||
Subscription subscription;
|
||||
|
||||
@Override
|
||||
public void updateItem(final Dispute item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
UserThread.execute(() -> {
|
||||
if (item != null && !empty) {
|
||||
if (closedProperty != null) {
|
||||
closedProperty.removeListener(listener);
|
||||
if (closedProperty != null) closedProperty.removeListener(listener);
|
||||
if (subscription != null) {
|
||||
subscription.unsubscribe();
|
||||
subscription = null;
|
||||
}
|
||||
|
||||
listener = (observable, oldValue, newValue) -> {
|
||||
setText(newValue ? Res.get("support.closed") : Res.get("support.open"));
|
||||
setText(getDisputeStateText(item));
|
||||
if (getTableRow() != null)
|
||||
getTableRow().setOpacity(newValue && item.getBadgeCountProperty().get() == 0 ? 0.4 : 1);
|
||||
if (item.isClosed() && item == chatPopup.getSelectedDispute())
|
||||
|
@ -1361,14 +1365,23 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
closedProperty = item.isClosedProperty();
|
||||
closedProperty.addListener(listener);
|
||||
boolean isClosed = item.isClosed();
|
||||
setText(isClosed ? Res.get("support.closed") : Res.get("support.open"));
|
||||
setText(getDisputeStateText(item));
|
||||
if (getTableRow() != null)
|
||||
getTableRow().setOpacity(isClosed && item.getBadgeCountProperty().get() == 0 ? 0.4 : 1);
|
||||
|
||||
// subscribe to trade's dispute state
|
||||
Trade trade = tradeManager.getTrade(item.getTradeId());
|
||||
if (trade == null) log.warn("Dispute's trade is null for trade {}", item.getTradeId());
|
||||
else subscription = EasyBind.subscribe(trade.disputeStateProperty(), disputeState -> setText(getDisputeStateText(disputeState)));
|
||||
} else {
|
||||
if (closedProperty != null) {
|
||||
closedProperty.removeListener(listener);
|
||||
closedProperty = null;
|
||||
}
|
||||
if (subscription != null) {
|
||||
subscription.unsubscribe();
|
||||
subscription = null;
|
||||
}
|
||||
setText("");
|
||||
}
|
||||
});
|
||||
|
@ -1379,6 +1392,33 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
return column;
|
||||
}
|
||||
|
||||
private String getDisputeStateText(DisputeState disputeState) {
|
||||
switch (disputeState) {
|
||||
case DISPUTE_REQUESTED:
|
||||
return Res.get("support.requested");
|
||||
case DISPUTE_CLOSED:
|
||||
return Res.get("support.closed");
|
||||
default:
|
||||
return Res.get("support.open");
|
||||
}
|
||||
}
|
||||
|
||||
private String getDisputeStateText(Dispute dispute) {
|
||||
Trade trade = tradeManager.getTrade(dispute.getTradeId());
|
||||
if (trade == null) {
|
||||
log.warn("Dispute's trade is null for trade {}", dispute.getTradeId());
|
||||
return Res.get("support.closed");
|
||||
}
|
||||
switch (trade.getDisputeState()) {
|
||||
case DISPUTE_REQUESTED:
|
||||
return Res.get("support.requested");
|
||||
case DISPUTE_CLOSED:
|
||||
return Res.get("support.closed");
|
||||
default:
|
||||
return Res.get("support.open");
|
||||
}
|
||||
}
|
||||
|
||||
private void openChat(Dispute dispute) {
|
||||
chatPopup.openChat(dispute, getConcreteDisputeChatSession(dispute), getCounterpartyName());
|
||||
dispute.setDisputeSeen(senderFlag());
|
||||
|
|
|
@ -738,11 +738,18 @@ public class GUIUtil {
|
|||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
connectionService.verifyConnection();
|
||||
} catch (Exception e) {
|
||||
new Popup().information(e.getMessage()).show();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isChainHeightSyncedWithinToleranceOrShowPopup(CoreMoneroConnectionsService connectionService) {
|
||||
if (!connectionService.isChainHeightSyncedWithinTolerance()) {
|
||||
if (!connectionService.isSyncedWithinTolerance()) {
|
||||
new Popup().information(Res.get("popup.warning.chainNotSynced")).show();
|
||||
return false;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue