diff --git a/core/src/main/java/bisq/core/dao/monitoring/DaoStateMonitoringService.java b/core/src/main/java/bisq/core/dao/monitoring/DaoStateMonitoringService.java index 604746bcbba..34acd7c7b9d 100644 --- a/core/src/main/java/bisq/core/dao/monitoring/DaoStateMonitoringService.java +++ b/core/src/main/java/bisq/core/dao/monitoring/DaoStateMonitoringService.java @@ -109,13 +109,13 @@ public interface Listener { @Getter private boolean isInConflictWithSeedNode; @Getter - private ObservableList utxoMismatches = FXCollections.observableArrayList(); + private final ObservableList utxoMismatches = FXCollections.observableArrayList(); - private List checkpoints = Arrays.asList( + private final List checkpoints = Arrays.asList( new Checkpoint(586920, Utilities.decodeFromHex("523aaad4e760f6ac6196fec1b3ec9a2f42e5b272")) ); private boolean checkpointFailed; - private boolean ignoreDevMsg; + private final boolean ignoreDevMsg; private int numCalls; private long accumulatedDuration; diff --git a/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java b/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java index 7e00146ee8d..e0a20e08da1 100644 --- a/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java +++ b/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java @@ -202,6 +202,10 @@ public void onDisputeResultMessage(DisputeResultMessage disputeResultMessage) { Trade trade = tradeOptional.get(); if (trade.getDisputeState() == Trade.DisputeState.MEDIATION_REQUESTED || trade.getDisputeState() == Trade.DisputeState.MEDIATION_STARTED_BY_PEER) { + trade.getProcessModel().setBuyerPayoutAmountFromMediation(disputeResult.getBuyerPayoutAmount().value); + trade.getProcessModel().setSellerPayoutAmountFromMediation(disputeResult.getSellerPayoutAmount().value); + tradeManager.requestPersistence(); + trade.setDisputeState(Trade.DisputeState.MEDIATION_CLOSED); } } else { diff --git a/core/src/main/java/bisq/core/trade/BuyerTrade.java b/core/src/main/java/bisq/core/trade/BuyerTrade.java index 77e0dc85848..f3afd4eb9a9 100644 --- a/core/src/main/java/bisq/core/trade/BuyerTrade.java +++ b/core/src/main/java/bisq/core/trade/BuyerTrade.java @@ -85,4 +85,8 @@ public Coin getPayoutAmount() { return checkNotNull(getOffer()).getBuyerSecurityDeposit().add(getTradeAmount()); } + @Override + public boolean confirmPermitted() { + return !getDisputeState().isArbitrated(); + } } diff --git a/core/src/main/java/bisq/core/trade/SellerTrade.java b/core/src/main/java/bisq/core/trade/SellerTrade.java index 2825d60a9d1..353ff62bd5d 100644 --- a/core/src/main/java/bisq/core/trade/SellerTrade.java +++ b/core/src/main/java/bisq/core/trade/SellerTrade.java @@ -18,6 +18,7 @@ package bisq.core.trade; import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.locale.CurrencyUtil; import bisq.core.offer.Offer; import bisq.core.trade.protocol.ProcessModel; @@ -83,5 +84,34 @@ public abstract class SellerTrade extends Trade { public Coin getPayoutAmount() { return checkNotNull(getOffer()).getSellerSecurityDeposit(); } + + @Override + public boolean confirmPermitted() { + // For altcoin there is no reason to delay BTC release as no chargeback risk + if (CurrencyUtil.isCryptoCurrency(getOffer().getCurrencyCode())) { + return true; + } + + switch (getDisputeState()) { + case NO_DISPUTE: + return true; + + case DISPUTE_REQUESTED: + case DISPUTE_STARTED_BY_PEER: + case DISPUTE_CLOSED: + case MEDIATION_REQUESTED: + case MEDIATION_STARTED_BY_PEER: + return false; + + case MEDIATION_CLOSED: + return !mediationResultAppliedPenaltyToSeller(); + + case REFUND_REQUESTED: + case REFUND_REQUEST_STARTED_BY_PEER: + case REFUND_REQUEST_CLOSED: + default: + return false; + } + } } diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/Trade.java index 81e9296149e..bb73bc6ea96 100644 --- a/core/src/main/java/bisq/core/trade/Trade.java +++ b/core/src/main/java/bisq/core/trade/Trade.java @@ -244,6 +244,25 @@ public static Trade.DisputeState fromProto(protobuf.Trade.DisputeState disputeSt public static protobuf.Trade.DisputeState toProtoMessage(Trade.DisputeState disputeState) { return protobuf.Trade.DisputeState.valueOf(disputeState.name()); } + + public boolean isNotDisputed() { + return this == Trade.DisputeState.NO_DISPUTE; + } + + public boolean isMediated() { + return this == Trade.DisputeState.MEDIATION_REQUESTED || + this == Trade.DisputeState.MEDIATION_STARTED_BY_PEER || + this == Trade.DisputeState.MEDIATION_CLOSED; + } + + public boolean isArbitrated() { + return this == Trade.DisputeState.DISPUTE_REQUESTED || + this == Trade.DisputeState.DISPUTE_STARTED_BY_PEER || + this == Trade.DisputeState.DISPUTE_CLOSED || + this == Trade.DisputeState.REFUND_REQUESTED || + this == Trade.DisputeState.REFUND_REQUEST_STARTED_BY_PEER || + this == Trade.DisputeState.REFUND_REQUEST_CLOSED; + } } public enum TradePeriodState { @@ -687,6 +706,15 @@ public void appendErrorMessage(String msg) { errorMessage = errorMessage == null ? msg : errorMessage + "\n" + msg; } + public boolean mediationResultAppliedPenaltyToSeller() { + // If mediated payout is same or more then normal payout we enable otherwise a penalty was applied + // by mediators and we keep the confirm disabled to avoid that the seller can complete the trade + // without the penalty. + long payoutAmountFromMediation = processModel.getSellerPayoutAmountFromMediation(); + long normalPayoutAmount = offer.getSellerSecurityDeposit().value; + return payoutAmountFromMediation < normalPayoutAmount; + } + /////////////////////////////////////////////////////////////////////////////////////////// // Model implementation @@ -703,6 +731,7 @@ public void onComplete() { public abstract Coin getPayoutAmount(); + public abstract boolean confirmPermitted(); /////////////////////////////////////////////////////////////////////////////////////////// // Setters diff --git a/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java index 2e7c4d9ac26..64c718ce412 100644 --- a/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java @@ -134,7 +134,7 @@ public void onPaymentStarted(ResultHandler resultHandler, ErrorMessageHandler er BuyerEvent event = BuyerEvent.PAYMENT_SENT; expect(phase(Trade.Phase.DEPOSIT_CONFIRMED) .with(event) - .preCondition(notDisputed())) + .preCondition(trade.confirmPermitted())) .setup(tasks(ApplyFilter.class, getVerifyPeersFeePaymentClass(), BuyerSignPayoutTx.class, @@ -188,5 +188,4 @@ protected void onTradeMessage(TradeMessage message, NodeAddress peer) { } abstract protected Class getVerifyPeersFeePaymentClass(); - } diff --git a/core/src/main/java/bisq/core/trade/protocol/DisputeProtocol.java b/core/src/main/java/bisq/core/trade/protocol/DisputeProtocol.java index adbcb8ee08f..23807c96f56 100644 --- a/core/src/main/java/bisq/core/trade/protocol/DisputeProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/DisputeProtocol.java @@ -55,10 +55,6 @@ public DisputeProtocol(Trade trade) { super(trade); } - protected boolean notDisputed() { - return trade.getDisputeState() == Trade.DisputeState.NO_DISPUTE; - } - /////////////////////////////////////////////////////////////////////////////////////////// // User interaction: Trader accepts mediation result diff --git a/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java index d8a06cfd4d7..35eea700b6c 100644 --- a/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java @@ -123,7 +123,7 @@ public void onPaymentReceived(ResultHandler resultHandler, ErrorMessageHandler e SellerEvent event = SellerEvent.PAYMENT_RECEIVED; expect(anyPhase(Trade.Phase.FIAT_SENT, Trade.Phase.PAYOUT_PUBLISHED) .with(event) - .preCondition(notDisputed())) + .preCondition(trade.confirmPermitted())) .setup(tasks( ApplyFilter.class, getVerifyPeersFeePaymentClass(), @@ -159,4 +159,5 @@ protected void onTradeMessage(TradeMessage message, NodeAddress peer) { } abstract protected Class getVerifyPeersFeePaymentClass(); + } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java index bfae6900381..64f93ff3579 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java @@ -430,7 +430,7 @@ protected String getPeriodOverWarnText() { protected void applyOnDisputeOpened() { } - private void updateDisputeState(Trade.DisputeState disputeState) { + protected void updateDisputeState(Trade.DisputeState disputeState) { Optional ownDispute; switch (disputeState) { case NO_DISPUTE: @@ -513,8 +513,6 @@ private void updateDisputeState(Trade.DisputeState disputeState) { default: break; } - - updateConfirmButtonDisableState(isDisputed()); } private void updateMediationResultState(boolean blockOpeningOfResultAcceptedPopup) { @@ -674,10 +672,6 @@ private void openMediationResultPopup(String headLine) { acceptMediationResultPopup.show(); } - protected void updateConfirmButtonDisableState(boolean isDisabled) { - // By default do nothing. Only overwritten in certain trade steps - } - protected String getCurrencyName(Trade trade) { return CurrencyUtil.getNameByCode(getCurrencyCode(trade)); } @@ -722,10 +716,6 @@ private void updateTradePeriodState(Trade.TradePeriodState tradePeriodState) { } } - protected boolean isDisputed() { - return trade.getDisputeState() != Trade.DisputeState.NO_DISPUTE; - } - private void checkIfLockTimeIsOver() { if (trade.getDisputeState() == Trade.DisputeState.MEDIATION_CLOSED) { Transaction delayedPayoutTx = trade.getDelayedPayoutTx(); diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java index 0693505e77c..32043989fa8 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java @@ -134,12 +134,10 @@ public void activate() { case BUYER_CONFIRMED_IN_UI_FIAT_PAYMENT_INITIATED: case BUYER_SENT_FIAT_PAYMENT_INITIATED_MSG: busyAnimation.play(); - // confirmButton.setDisable(true); statusLabel.setText(Res.get("shared.sendingConfirmation")); model.setMessageStateProperty(MessageState.SENT); timeoutTimer = UserThread.runAfter(() -> { busyAnimation.stop(); - // confirmButton.setDisable(false); statusLabel.setText(Res.get("shared.sendingConfirmationAgain")); }, 10); break; @@ -156,27 +154,22 @@ public void activate() { case BUYER_SEND_FAILED_FIAT_PAYMENT_INITIATED_MSG: // We get a popup and the trade closed, so we dont need to show anything here busyAnimation.stop(); - // confirmButton.setDisable(false); statusLabel.setText(""); model.setMessageStateProperty(MessageState.FAILED); break; default: log.warn("Unexpected case: State={}, tradeId={} " + state.name(), trade.getId()); busyAnimation.stop(); - // confirmButton.setDisable(false); statusLabel.setText(Res.get("shared.sendingConfirmationAgain")); break; } } else { - log.warn("confirmButton gets disabled because trade contains error message {}", trade.getErrorMessage()); - // confirmButton.setDisable(true); + log.warn("Trade contains error message {}", trade.getErrorMessage()); statusLabel.setText(""); } } }); } - - confirmButton.setDisable(isDisputed()); } @Override @@ -372,15 +365,19 @@ protected String getPeriodOverWarnText() { protected void applyOnDisputeOpened() { } + @Override + protected void updateDisputeState(Trade.DisputeState disputeState) { + super.updateDisputeState(disputeState); + + confirmButton.setDisable(!trade.confirmPermitted()); + } + + /////////////////////////////////////////////////////////////////////////////////////////// // UI Handlers /////////////////////////////////////////////////////////////////////////////////////////// private void onPaymentStarted() { - if (isDisputed()) { - return; - } - if (!model.dataModel.isBootstrappedOrShowPopup()) { return; } @@ -633,9 +630,4 @@ private void validatePayoutTx() { } } } - - @Override - protected void updateConfirmButtonDisableState(boolean isDisabled) { - confirmButton.setDisable(isDisabled); - } } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index eb0ef46b91d..116e18bf4e3 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -45,6 +45,7 @@ import bisq.core.payment.payload.USPostalMoneyOrderAccountPayload; import bisq.core.payment.payload.WesternUnionAccountPayload; import bisq.core.trade.Contract; +import bisq.core.trade.Trade; import bisq.core.trade.txproof.AssetTxProofResult; import bisq.core.user.DontShowAgainLookup; @@ -119,12 +120,10 @@ public void activate() { case SELLER_PUBLISHED_PAYOUT_TX: case SELLER_SENT_PAYOUT_TX_PUBLISHED_MSG: busyAnimation.play(); - // confirmButton.setDisable(true); statusLabel.setText(Res.get("shared.sendingConfirmation")); timeoutTimer = UserThread.runAfter(() -> { busyAnimation.stop(); - // confirmButton.setDisable(false); statusLabel.setText(Res.get("shared.sendingConfirmationAgain")); }, 10); break; @@ -139,19 +138,16 @@ public void activate() { case SELLER_SEND_FAILED_PAYOUT_TX_PUBLISHED_MSG: // We get a popup and the trade closed, so we dont need to show anything here busyAnimation.stop(); - // confirmButton.setDisable(false); statusLabel.setText(""); break; default: log.warn("Unexpected case: State={}, tradeId={} " + state.name(), trade.getId()); busyAnimation.stop(); - // confirmButton.setDisable(false); statusLabel.setText(Res.get("shared.sendingConfirmationAgain")); break; } } else { - log.warn("confirmButton gets disabled because trade contains error message {}", trade.getErrorMessage()); - // confirmButton.setDisable(true); + log.warn("Trade contains error message {}", trade.getErrorMessage()); statusLabel.setText(""); } } @@ -308,11 +304,6 @@ protected void addContent() { statusLabel = tuple.third; } - @Override - protected void updateConfirmButtonDisableState(boolean isDisabled) { - confirmButton.setDisable(isDisabled); - } - /////////////////////////////////////////////////////////////////////////////////////////// // Info @@ -355,15 +346,19 @@ protected String getPeriodOverWarnText() { protected void applyOnDisputeOpened() { } + @Override + protected void updateDisputeState(Trade.DisputeState disputeState) { + super.updateDisputeState(disputeState); + + confirmButton.setDisable(!trade.confirmPermitted()); + } + + //////////////////////////////////////////////////////////////////////////////////////// // UI Handlers /////////////////////////////////////////////////////////////////////////////////////////// private void onPaymentReceived() { - if (isDisputed()) { - return; - } - // The confirmPaymentReceived call will trigger the trade protocol to do the payout tx. We want to be sure that we // are well connected to the Bitcoin network before triggering the broadcast. if (model.dataModel.isReadyForTxBroadcast()) { @@ -459,13 +454,7 @@ private void confirmPaymentReceived() { statusLabel.setText(Res.get("shared.sendingConfirmation")); model.dataModel.onFiatPaymentReceived(() -> { - // In case the first send failed we got the support button displayed. - // If it succeeds at a second try we remove the support button again. - //TODO check for support. in case of a dispute we dont want to hide the button - //if (notificationGroup != null) - // notificationGroup.setButtonVisible(false); }, errorMessage -> { - // confirmButton.setDisable(false); busyAnimation.stop(); new Popup().warning(Res.get("popup.warning.sendMsgFailed")).show(); });