From 4f1cbbd00ec77ee6d8dc18bd07ea3552cb359037 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Fri, 11 Sep 2020 15:35:47 -0500 Subject: [PATCH 01/31] Add check for refund agent if donation address is valid --- .../main/java/bisq/core/dao/DaoFacade.java | 9 ++ .../bisq/core/support/dispute/Dispute.java | 12 +++ .../core/support/dispute/DisputeManager.java | 45 +++++++++- .../arbitration/ArbitrationManager.java | 6 +- .../dispute/mediation/MediationManager.java | 6 +- .../support/dispute/refund/RefundManager.java | 6 +- .../core/trade/DelayedPayoutTxValidation.java | 88 ++++++++++++++++++- .../resources/i18n/displayStrings.properties | 9 +- .../main/offer/MutableOfferViewModel.java | 2 +- .../main/overlays/windows/ContractWindow.java | 7 ++ .../windows/DisputeSummaryWindow.java | 36 +++++++- .../pendingtrades/PendingTradesDataModel.java | 41 +++++++-- .../dispute/agent/DisputeAgentView.java | 31 +++++++ .../agent/arbitration/ArbitratorView.java | 3 + .../dispute/agent/mediation/MediatorView.java | 3 + .../dispute/agent/refund/RefundAgentView.java | 6 +- proto/src/main/proto/pb.proto | 1 + 17 files changed, 285 insertions(+), 26 deletions(-) diff --git a/core/src/main/java/bisq/core/dao/DaoFacade.java b/core/src/main/java/bisq/core/dao/DaoFacade.java index 1485ae2546..50b6402c5a 100644 --- a/core/src/main/java/bisq/core/dao/DaoFacade.java +++ b/core/src/main/java/bisq/core/dao/DaoFacade.java @@ -95,6 +95,7 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; @@ -750,4 +751,12 @@ public class DaoFacade implements DaoSetupService { long baseFactor = daoStateService.getParamValueAsCoin(Param.BONDED_ROLE_FACTOR, height).value; return requiredBondUnit * baseFactor; } + + public Set getAllPastParamValues(Param param) { + Set set = new HashSet<>(); + periodService.getCycles().forEach(cycle -> { + set.add(getParamValue(param, cycle.getHeightOfFirstBlock())); + }); + return set; + } } diff --git a/core/src/main/java/bisq/core/support/dispute/Dispute.java b/core/src/main/java/bisq/core/support/dispute/Dispute.java index d5f45a7a18..6a79a9f740 100644 --- a/core/src/main/java/bisq/core/support/dispute/Dispute.java +++ b/core/src/main/java/bisq/core/support/dispute/Dispute.java @@ -103,6 +103,11 @@ public final class Dispute implements NetworkPayload { @Nullable private String delayedPayoutTxId; + // Added at v1.3.9 + @Setter + @Nullable + private String donationAddressOfDelayedPayoutTx; + /////////////////////////////////////////////////////////////////////////////////////////// // Constructor @@ -228,6 +233,7 @@ public final class Dispute implements NetworkPayload { Optional.ofNullable(supportType).ifPresent(result -> builder.setSupportType(SupportType.toProtoMessage(supportType))); Optional.ofNullable(mediatorsDisputeResult).ifPresent(result -> builder.setMediatorsDisputeResult(mediatorsDisputeResult)); Optional.ofNullable(delayedPayoutTxId).ifPresent(result -> builder.setDelayedPayoutTxId(delayedPayoutTxId)); + Optional.ofNullable(donationAddressOfDelayedPayoutTx).ifPresent(result -> builder.setDonationAddressOfDelayedPayoutTx(donationAddressOfDelayedPayoutTx)); return builder.build(); } @@ -271,6 +277,11 @@ public final class Dispute implements NetworkPayload { dispute.setDelayedPayoutTxId(delayedPayoutTxId); } + String donationAddressOfDelayedPayoutTx = proto.getDonationAddressOfDelayedPayoutTx(); + if (!donationAddressOfDelayedPayoutTx.isEmpty()) { + dispute.setDonationAddressOfDelayedPayoutTx(donationAddressOfDelayedPayoutTx); + } + return dispute; } @@ -382,6 +393,7 @@ public final class Dispute implements NetworkPayload { ",\n supportType=" + supportType + ",\n mediatorsDisputeResult='" + mediatorsDisputeResult + '\'' + ",\n delayedPayoutTxId='" + delayedPayoutTxId + '\'' + + ",\n donationAddressOfDelayedPayoutTx='" + donationAddressOfDelayedPayoutTx + '\'' + "\n}"; } } diff --git a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java index 9998e1f2eb..5a01fdba56 100644 --- a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java +++ b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java @@ -21,6 +21,7 @@ import bisq.core.btc.setup.WalletsSetup; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.Restrictions; import bisq.core.btc.wallet.TradeWalletService; +import bisq.core.dao.DaoFacade; import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; import bisq.core.monetary.Altcoin; @@ -35,6 +36,7 @@ import bisq.core.support.dispute.messages.OpenNewDisputeMessage; import bisq.core.support.dispute.messages.PeerOpenedDisputeMessage; import bisq.core.support.messages.ChatMessage; import bisq.core.trade.Contract; +import bisq.core.trade.DelayedPayoutTxValidation; import bisq.core.trade.Trade; import bisq.core.trade.TradeManager; import bisq.core.trade.closed.ClosedTradableManager; @@ -58,6 +60,7 @@ import org.bitcoinj.utils.Fiat; import javafx.beans.property.IntegerProperty; +import javafx.collections.FXCollections; import javafx.collections.ObservableList; import java.util.List; @@ -66,10 +69,12 @@ import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import javax.annotation.Nullable; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @Slf4j @@ -82,6 +87,10 @@ public abstract class DisputeManager disputeListService; private final PriceFeedService priceFeedService; + private final DaoFacade daoFacade; + + @Getter + protected final ObservableList disputesWithInvalidDonationAddress = FXCollections.observableArrayList(); /////////////////////////////////////////////////////////////////////////////////////////// @@ -95,6 +104,7 @@ public abstract class DisputeManager disputeListService, PriceFeedService priceFeedService) { @@ -105,6 +115,7 @@ public abstract class DisputeManager e.getTradeId().equals(tradeId)).findAny(); } + /////////////////////////////////////////////////////////////////////////////////////////// // Message handler /////////////////////////////////////////////////////////////////////////////////////////// @@ -278,6 +290,10 @@ public abstract class DisputeManager optionalTrade = tradeManager.getTradeById(dispute.getTradeId()); + checkArgument(optionalTrade.isPresent()); + Trade trade = optionalTrade.get(); + try { + DelayedPayoutTxValidation.validatePayoutTx(trade, + trade.getDelayedPayoutTx(), + dispute, + daoFacade, + btcWalletService); + } catch (DelayedPayoutTxValidation.DonationAddressException | + DelayedPayoutTxValidation.InvalidTxException | + DelayedPayoutTxValidation.InvalidLockTimeException | + DelayedPayoutTxValidation.MissingDelayedPayoutTxException | + DelayedPayoutTxValidation.AmountMismatchException e) { + // The peer sent us an invalid donation address. We do not return here as we don't want to break + // mediation/arbitration and log only the issue. The dispute agent will run validation as well and will get + // a popup displayed to react. + log.error("Donation address invalid. {}", e.toString()); + } + if (!isAgent(dispute)) { if (!disputeList.contains(dispute)) { Optional storedDisputeOptional = findDispute(dispute); if (!storedDisputeOptional.isPresent()) { dispute.setStorage(disputeListService.getStorage()); disputeList.add(dispute); - Optional tradeOptional = tradeManager.getTradeById(dispute.getTradeId()); - tradeOptional.ifPresent(trade -> trade.setDisputeState(getDisputeState_StartedByPeer())); + trade.setDisputeState(getDisputeStateStartedByPeer()); errorMessage = null; } else { // valid case if both have opened a dispute and agent was not online. @@ -516,6 +552,7 @@ public abstract class DisputeManager storedDisputeOptional = findDispute(dispute); diff --git a/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java b/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java index e77daf2b04..2ddacf350f 100644 --- a/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java +++ b/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java @@ -25,6 +25,7 @@ import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.TradeWalletService; import bisq.core.btc.wallet.TxBroadcaster; import bisq.core.btc.wallet.WalletService; +import bisq.core.dao.DaoFacade; import bisq.core.locale.Res; import bisq.core.offer.OpenOffer; import bisq.core.offer.OpenOfferManager; @@ -88,11 +89,12 @@ public final class ArbitrationManager extends DisputeManager TradeManager tradeManager, ClosedTradableManager closedTradableManager, OpenOfferManager openOfferManager, + DaoFacade daoFacade, PubKeyRing pubKeyRing, MediationDisputeListService mediationDisputeListService, PriceFeedService priceFeedService) { super(p2PService, tradeWalletService, walletService, walletsSetup, tradeManager, closedTradableManager, - openOfferManager, pubKeyRing, mediationDisputeListService, priceFeedService); + openOfferManager, daoFacade, pubKeyRing, mediationDisputeListService, priceFeedService); } /////////////////////////////////////////////////////////////////////////////////////////// @@ -117,7 +119,7 @@ public final class MediationManager extends DisputeManager } @Override - protected Trade.DisputeState getDisputeState_StartedByPeer() { + protected Trade.DisputeState getDisputeStateStartedByPeer() { return Trade.DisputeState.MEDIATION_STARTED_BY_PEER; } diff --git a/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java b/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java index f28208f050..f00aca5fbd 100644 --- a/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java +++ b/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java @@ -20,6 +20,7 @@ package bisq.core.support.dispute.refund; import bisq.core.btc.setup.WalletsSetup; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.TradeWalletService; +import bisq.core.dao.DaoFacade; import bisq.core.locale.Res; import bisq.core.offer.OpenOffer; import bisq.core.offer.OpenOfferManager; @@ -74,11 +75,12 @@ public final class RefundManager extends DisputeManager { TradeManager tradeManager, ClosedTradableManager closedTradableManager, OpenOfferManager openOfferManager, + DaoFacade daoFacade, PubKeyRing pubKeyRing, RefundDisputeListService refundDisputeListService, PriceFeedService priceFeedService) { super(p2PService, tradeWalletService, walletService, walletsSetup, tradeManager, closedTradableManager, - openOfferManager, pubKeyRing, refundDisputeListService, priceFeedService); + openOfferManager, daoFacade, pubKeyRing, refundDisputeListService, priceFeedService); } /////////////////////////////////////////////////////////////////////////////////////////// @@ -111,7 +113,7 @@ public final class RefundManager extends DisputeManager { } @Override - protected Trade.DisputeState getDisputeState_StartedByPeer() { + protected Trade.DisputeState getDisputeStateStartedByPeer() { return Trade.DisputeState.REFUND_REQUEST_STARTED_BY_PEER; } diff --git a/core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java b/core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java index 6c835d2408..7b0997d57a 100644 --- a/core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java +++ b/core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java @@ -21,6 +21,7 @@ import bisq.core.btc.wallet.BtcWalletService; import bisq.core.dao.DaoFacade; import bisq.core.dao.governance.param.Param; import bisq.core.offer.Offer; +import bisq.core.support.dispute.Dispute; import bisq.common.config.Config; @@ -33,9 +34,14 @@ import org.bitcoinj.core.TransactionOutPoint; import org.bitcoinj.core.TransactionOutput; import java.util.List; +import java.util.Set; +import java.util.function.Consumer; import lombok.extern.slf4j.Slf4j; +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @Slf4j @@ -77,12 +83,80 @@ public class DelayedPayoutTxValidation { } } + public static boolean isValidDonationAddress(Dispute dispute, DaoFacade daoFacade) { + String addressAsString = dispute.getDonationAddressOfDelayedPayoutTx(); + + // Old clients don't have it set yet. Can be removed after a forced update + if (addressAsString == null) { + return true; + } + + // We use all past addresses from DAO param changes as the dispute case might have been opened later and the + // DAO param changed in the meantime. + Set allPastParamValues = daoFacade.getAllPastParamValues(Param.RECIPIENT_BTC_ADDRESS); + + if (allPastParamValues.contains(addressAsString)) { + return true; + } + + log.warn("Donation address is not a valid DAO donation address." + + "\nAddress used in the dispute: " + addressAsString + + "\nAll DAO param donation addresses:" + allPastParamValues); + return false; + } + public static void validatePayoutTx(Trade trade, Transaction delayedPayoutTx, DaoFacade daoFacade, BtcWalletService btcWalletService) throws DonationAddressException, MissingDelayedPayoutTxException, InvalidTxException, InvalidLockTimeException, AmountMismatchException { + validatePayoutTx(trade, + delayedPayoutTx, + null, + daoFacade, + btcWalletService, + null); + } + + public static void validatePayoutTx(Trade trade, + Transaction delayedPayoutTx, + @Nullable Dispute dispute, + DaoFacade daoFacade, + BtcWalletService btcWalletService) + throws DonationAddressException, MissingDelayedPayoutTxException, + InvalidTxException, InvalidLockTimeException, AmountMismatchException { + validatePayoutTx(trade, + delayedPayoutTx, + dispute, + daoFacade, + btcWalletService, + null); + } + + public static void validatePayoutTx(Trade trade, + Transaction delayedPayoutTx, + DaoFacade daoFacade, + BtcWalletService btcWalletService, + @Nullable Consumer addressConsumer) + throws DonationAddressException, MissingDelayedPayoutTxException, + InvalidTxException, InvalidLockTimeException, AmountMismatchException { + validatePayoutTx(trade, + delayedPayoutTx, + null, + daoFacade, + btcWalletService, + addressConsumer); + } + + public static void validatePayoutTx(Trade trade, + Transaction delayedPayoutTx, + @Nullable Dispute dispute, + DaoFacade daoFacade, + BtcWalletService btcWalletService, + @Nullable Consumer addressConsumer) + throws DonationAddressException, MissingDelayedPayoutTxException, + InvalidTxException, InvalidLockTimeException, AmountMismatchException { String errorMsg; if (delayedPayoutTx == null) { errorMsg = "DelayedPayoutTx must not be null"; @@ -144,7 +218,6 @@ public class DelayedPayoutTxValidation { // We do not support past DAO param addresses to avoid that those receive funds (no bond set up anymore). // Users who have not synced the DAO cannot trade. - NetworkParameters params = btcWalletService.getParams(); Address address = output.getAddressFromP2PKHScript(params); if (address == null) { @@ -159,6 +232,9 @@ public class DelayedPayoutTxValidation { } String addressAsString = address.toString(); + if (addressConsumer != null) { + addressConsumer.accept(addressAsString); + } // In case the seller has deactivated the DAO the default address will be used. String defaultDonationAddressString = Param.RECIPIENT_BTC_ADDRESS.getDefaultValue(); @@ -190,6 +266,16 @@ public class DelayedPayoutTxValidation { log.error(delayedPayoutTx.toString()); throw new DonationAddressException(errorMsg); } + + if (dispute != null) { + // Verify that address in the dispute matches the one in the trade. + String donationAddressOfDelayedPayoutTx = dispute.getDonationAddressOfDelayedPayoutTx(); + // Old clients don't have it set yet. Can be removed after a forced update + if (donationAddressOfDelayedPayoutTx != null) { + checkArgument(addressAsString.equals(donationAddressOfDelayedPayoutTx), + "donationAddressOfDelayedPayoutTx from dispute does not match address from delayed payout tx"); + } + } } public static void validatePayoutTxInput(Transaction depositTx, diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 753d8bc339..4156441334 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -214,7 +214,8 @@ shared.mediator=Mediator shared.arbitrator=Arbitrator shared.refundAgent=Arbitrator shared.refundAgentForSupportStaff=Refund agent -shared.delayedPayoutTxId=Refund collateral transaction ID +shared.delayedPayoutTxId=Delayed payout transaction ID +shared.delayedPayoutTxReceiverAddress=Delayed payout transaction sent to shared.unconfirmedTransactionsLimitReached=You have too many unconfirmed transactions at the moment. Please try again later. @@ -1080,6 +1081,12 @@ support.peerOpenedDispute=Your trading peer has requested a dispute.\n\n{0}\n\nB support.peerOpenedDisputeForMediation=Your trading peer has requested mediation.\n\n{0}\n\nBisq version: {1} support.mediatorsDisputeSummary=System message: Mediator''s dispute summary:\n{0} support.mediatorsAddress=Mediator''s node address: {0} +support.warning.disputesWithInvalidDonationAddress=The delayed payout transaction has used an invalid receiver address. \ + It does not match any of the DAO parameter values for the valid donation addresses.\n\nThis might be a scam attempt. \ + Please inform the developers about that incident and do not close that case before the situation is resolved!\n\n\ + Address used in the dispute: {0}\n\n\ + All DAO param donation addresses: {1}\n\n\ + Trade ID: {2} #################################################################### diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java index 989d063b25..588c06cb56 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java @@ -231,7 +231,7 @@ public abstract class MutableOfferViewModel ext if (DevEnv.isDevMode()) { UserThread.runAfter(() -> { amount.set("0.001"); - price.set("0.008"); + price.set("70000"); minAmount.set(amount.get()); onFocusOutPriceAsPercentageTextField(true, false); applyMakerFee(); diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/ContractWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/ContractWindow.java index ff053d9033..ec8e10ede0 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/ContractWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/ContractWindow.java @@ -141,6 +141,8 @@ public class ContractWindow extends Overlay { rows++; if (dispute.getDelayedPayoutTxId() != null) rows++; + if (dispute.getDonationAddressOfDelayedPayoutTx() != null) + rows++; if (showAcceptedCountryCodes) rows++; if (showAcceptedBanks) @@ -248,6 +250,11 @@ public class ContractWindow extends Overlay { if (dispute.getDelayedPayoutTxId() != null) addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("shared.delayedPayoutTxId"), dispute.getDelayedPayoutTxId()); + if (dispute.getDonationAddressOfDelayedPayoutTx() != null) { + addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("shared.delayedPayoutTxReceiverAddress"), + dispute.getDonationAddressOfDelayedPayoutTx()); + } + if (dispute.getPayoutTxSerialized() != null) addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("shared.payoutTxId"), dispute.getPayoutTxId()); diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java index 6202b6b204..d78723ec53 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java @@ -34,6 +34,8 @@ import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.Restrictions; import bisq.core.btc.wallet.TradeWalletService; import bisq.core.btc.wallet.TxBroadcaster; +import bisq.core.dao.DaoFacade; +import bisq.core.dao.governance.param.Param; import bisq.core.locale.Res; import bisq.core.offer.Offer; import bisq.core.provider.fee.FeeService; @@ -45,6 +47,7 @@ import bisq.core.support.dispute.DisputeResult; import bisq.core.support.dispute.mediation.MediationManager; import bisq.core.support.dispute.refund.RefundManager; import bisq.core.trade.Contract; +import bisq.core.trade.DelayedPayoutTxValidation; import bisq.core.util.FormattingUtils; import bisq.core.util.ParsingUtils; import bisq.core.util.coin.CoinFormatter; @@ -84,6 +87,7 @@ import javafx.beans.value.ChangeListener; import java.util.Date; import java.util.Optional; +import java.util.Set; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; @@ -105,6 +109,7 @@ public class DisputeSummaryWindow extends Overlay { private final BtcWalletService btcWalletService; private final TxFeeEstimationService txFeeEstimationService; private final FeeService feeService; + private final DaoFacade daoFacade; private Dispute dispute; private Optional finalizeDisputeHandlerOptional = Optional.empty(); private ToggleGroup tradeAmountToggleGroup, reasonToggleGroup; @@ -141,7 +146,8 @@ public class DisputeSummaryWindow extends Overlay { TradeWalletService tradeWalletService, BtcWalletService btcWalletService, TxFeeEstimationService txFeeEstimationService, - FeeService feeService) { + FeeService feeService, + DaoFacade daoFacade) { this.formatter = formatter; this.mediationManager = mediationManager; @@ -150,6 +156,7 @@ public class DisputeSummaryWindow extends Overlay { this.btcWalletService = btcWalletService; this.txFeeEstimationService = txFeeEstimationService; this.feeService = feeService; + this.daoFacade = daoFacade; type = Type.Confirmation; } @@ -642,7 +649,10 @@ public class DisputeSummaryWindow extends Overlay { log.warn("dispute.getDepositTxSerialized is null"); return; } - if (dispute.getSupportType() == SupportType.REFUND && + + if (!DelayedPayoutTxValidation.isValidDonationAddress(dispute, daoFacade)) { + showInValidDonationAddressPopup(); + } else if (dispute.getSupportType() == SupportType.REFUND && peersDisputeOptional.isPresent() && !peersDisputeOptional.get().isClosed()) { showPayoutTxConfirmation(contract, disputeResult, @@ -741,6 +751,17 @@ public class DisputeSummaryWindow extends Overlay { } private void doClose(Button closeTicketButton) { + var disputeManager = checkNotNull(getDisputeManager(dispute)); + if (!DelayedPayoutTxValidation.isValidDonationAddress(dispute, daoFacade)) { + showInValidDonationAddressPopup(); + + // For mediators we do not enforce that the case cannot be closed to stay flexible, + // but for refund agents we do. + if (disputeManager instanceof RefundManager) { + return; + } + } + disputeResult.setLoserPublisher(isLoserPublisherCheckBox.isSelected()); disputeResult.setCloseDate(new Date()); dispute.setDisputeResult(disputeResult); @@ -765,7 +786,7 @@ public class DisputeSummaryWindow extends Overlay { text += Res.get("disputeSummaryWindow.close.nextStepsForRefundAgentArbitration"); } - checkNotNull(getDisputeManager(dispute)).sendDisputeResultMessage(disputeResult, dispute, text); + disputeManager.sendDisputeResultMessage(disputeResult, dispute, text); if (peersDisputeOptional.isPresent() && !peersDisputeOptional.get().isClosed() && !DevEnv.isDevMode()) { UserThread.runAfter(() -> new Popup() @@ -781,6 +802,15 @@ public class DisputeSummaryWindow extends Overlay { hide(); } + private void showInValidDonationAddressPopup() { + String addressAsString = dispute.getDonationAddressOfDelayedPayoutTx(); + Set allPastParamValues = daoFacade.getAllPastParamValues(Param.RECIPIENT_BTC_ADDRESS); + String tradeId = dispute.getTradeId(); + new Popup().warning(Res.get("support.warning.disputesWithInvalidDonationAddress", + addressAsString, allPastParamValues, tradeId)) + .show(); + } + private DisputeManager> getDisputeManager(Dispute dispute) { if (dispute.getSupportType() != null) { switch (dispute.getSupportType()) { diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java index 6d1a288f11..e1f2b10d9b 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java @@ -47,6 +47,7 @@ import bisq.core.support.dispute.refund.RefundManager; import bisq.core.support.messages.ChatMessage; import bisq.core.support.traderchat.TraderChatManager; import bisq.core.trade.BuyerTrade; +import bisq.core.trade.DelayedPayoutTxValidation; import bisq.core.trade.SellerTrade; import bisq.core.trade.Trade; import bisq.core.trade.TradeManager; @@ -82,6 +83,7 @@ import javafx.collections.ObservableList; import org.spongycastle.crypto.params.KeyParameter; import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import lombok.Getter; @@ -536,6 +538,25 @@ public class PendingTradesDataModel extends ActivatableDataModel { // In case we re-open a dispute we allow Trade.DisputeState.REFUND_REQUESTED useRefundAgent = disputeState == Trade.DisputeState.MEDIATION_CLOSED || disputeState == Trade.DisputeState.REFUND_REQUESTED; + AtomicReference donationAddressString = new AtomicReference<>(""); + Transaction delayedPayoutTx = trade.getDelayedPayoutTx(); + try { + DelayedPayoutTxValidation.validatePayoutTx(trade, + delayedPayoutTx, + daoFacade, + btcWalletService, + donationAddressString::set); + } catch (DelayedPayoutTxValidation.DonationAddressException | + DelayedPayoutTxValidation.InvalidTxException | + DelayedPayoutTxValidation.InvalidLockTimeException | + DelayedPayoutTxValidation.MissingDelayedPayoutTxException | + DelayedPayoutTxValidation.AmountMismatchException e) { + // The peer sent us an invalid donation address. We do not return here as we don't want to break + // mediation/arbitration and log only the issue. The dispute agent will run validation as well and will get + // a popup displayed to react. + log.error("Donation address invalid. {}", e.toString()); + } + ResultHandler resultHandler; if (useMediation) { // If no dispute state set we start with mediation @@ -564,6 +585,11 @@ public class PendingTradesDataModel extends ActivatableDataModel { isSupportTicket, SupportType.MEDIATION); + dispute.setDonationAddressOfDelayedPayoutTx(donationAddressString.get()); + if (delayedPayoutTx != null) { + dispute.setDelayedPayoutTxId(delayedPayoutTx.getHashAsString()); + } + trade.setDisputeState(Trade.DisputeState.MEDIATION_REQUESTED); disputeManager.sendOpenNewDisputeMessage(dispute, false, @@ -588,7 +614,7 @@ public class PendingTradesDataModel extends ActivatableDataModel { } else if (useRefundAgent) { resultHandler = () -> navigation.navigateTo(MainView.class, SupportView.class, RefundClientView.class); - if (trade.getDelayedPayoutTx() == null) { + if (delayedPayoutTx == null) { log.error("Delayed payout tx is missing"); return; } @@ -603,13 +629,12 @@ public class PendingTradesDataModel extends ActivatableDataModel { return; } - long lockTime = trade.getDelayedPayoutTx().getLockTime(); + long lockTime = delayedPayoutTx.getLockTime(); int bestChainHeight = btcWalletService.getBestChainHeight(); long remaining = lockTime - bestChainHeight; if (remaining > 0) { - new Popup() - .instruction(Res.get("portfolio.pending.timeLockNotOver", - FormattingUtils.getDateFromBlockHeight(remaining), remaining)) + new Popup().instruction(Res.get("portfolio.pending.timeLockNotOver", + FormattingUtils.getDateFromBlockHeight(remaining), remaining)) .show(); return; } @@ -639,6 +664,9 @@ public class PendingTradesDataModel extends ActivatableDataModel { isSupportTicket, SupportType.REFUND); + dispute.setDonationAddressOfDelayedPayoutTx(donationAddressString.get()); + dispute.setDelayedPayoutTxId(delayedPayoutTx.getHashAsString()); + String tradeId = dispute.getTradeId(); mediationManager.findDispute(tradeId) .ifPresent(mediatorsDispute -> { @@ -651,9 +679,6 @@ public class PendingTradesDataModel extends ActivatableDataModel { dispute.setMediatorsDisputeResult(message); } }); - - dispute.setDelayedPayoutTxId(trade.getDelayedPayoutTx().getHashAsString()); - trade.setDisputeState(Trade.DisputeState.REFUND_REQUESTED); //todo add UI spinner as it can take a bit if peer is offline diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java index 13e5260867..6f6efdd206 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java @@ -27,6 +27,8 @@ import bisq.desktop.main.support.dispute.DisputeView; import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.alert.PrivateNotificationManager; +import bisq.core.dao.DaoFacade; +import bisq.core.dao.governance.param.Param; import bisq.core.locale.Res; import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.DisputeList; @@ -54,13 +56,18 @@ import javafx.geometry.Insets; import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.collections.ListChangeListener; + import java.util.List; +import java.util.Set; import static bisq.desktop.util.FormBuilder.getIconForLabel; public abstract class DisputeAgentView extends DisputeView implements MultipleHolderNameDetection.Listener { private final MultipleHolderNameDetection multipleHolderNameDetection; + private final DaoFacade daoFacade; + private ListChangeListener disputesWithInvalidDonationAddressListener; public DisputeAgentView(DisputeManager> disputeManager, KeyRing keyRing, @@ -71,6 +78,7 @@ public abstract class DisputeAgentView extends DisputeView implements MultipleHo ContractWindow contractWindow, TradeDetailsWindow tradeDetailsWindow, AccountAgeWitnessService accountAgeWitnessService, + DaoFacade daoFacade, boolean useDevPrivilegeKeys) { super(disputeManager, keyRing, @@ -84,6 +92,7 @@ public abstract class DisputeAgentView extends DisputeView implements MultipleHo useDevPrivilegeKeys); multipleHolderNameDetection = new MultipleHolderNameDetection(disputeManager); + this.daoFacade = daoFacade; } @@ -107,6 +116,23 @@ public abstract class DisputeAgentView extends DisputeView implements MultipleHo fullReportButton.setManaged(true); multipleHolderNameDetection.detectMultipleHolderNames(); + + disputesWithInvalidDonationAddressListener = c -> { + c.next(); + if (c.wasAdded()) { + showWarningForInvalidDonationAddress(c.getAddedSubList()); + } + }; + } + + protected void showWarningForInvalidDonationAddress(List disputes) { + disputes.forEach(dispute -> { + String addressAsString = dispute.getDonationAddressOfDelayedPayoutTx(); + Set allPastParamValues = daoFacade.getAllPastParamValues(Param.RECIPIENT_BTC_ADDRESS); + new Popup().warning(Res.get("support.warning.disputesWithInvalidDonationAddress", + addressAsString, allPastParamValues, dispute.getTradeId())) + .show(); + }); } @Override @@ -117,6 +143,9 @@ public abstract class DisputeAgentView extends DisputeView implements MultipleHo if (multipleHolderNameDetection.hasSuspiciousDisputesDetected()) { suspiciousDisputeDetected(); } + + disputeManager.getDisputesWithInvalidDonationAddress().addListener(disputesWithInvalidDonationAddressListener); + showWarningForInvalidDonationAddress(disputeManager.getDisputesWithInvalidDonationAddress()); } @Override @@ -124,6 +153,8 @@ public abstract class DisputeAgentView extends DisputeView implements MultipleHo super.deactivate(); multipleHolderNameDetection.removeListener(this); + + disputeManager.getDisputesWithInvalidDonationAddress().removeListener(disputesWithInvalidDonationAddressListener); } diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/arbitration/ArbitratorView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/arbitration/ArbitratorView.java index dcb33c6112..7c2278de28 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/arbitration/ArbitratorView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/arbitration/ArbitratorView.java @@ -25,6 +25,7 @@ import bisq.desktop.main.support.dispute.agent.DisputeAgentView; import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.alert.PrivateNotificationManager; +import bisq.core.dao.DaoFacade; import bisq.core.support.SupportType; import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.DisputeSession; @@ -53,6 +54,7 @@ public class ArbitratorView extends DisputeAgentView { ContractWindow contractWindow, TradeDetailsWindow tradeDetailsWindow, AccountAgeWitnessService accountAgeWitnessService, + DaoFacade daoFacade, @Named(Config.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) { super(arbitrationManager, keyRing, @@ -63,6 +65,7 @@ public class ArbitratorView extends DisputeAgentView { contractWindow, tradeDetailsWindow, accountAgeWitnessService, + daoFacade, useDevPrivilegeKeys); } diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/mediation/MediatorView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/mediation/MediatorView.java index ba939c34d2..d96fb9e8f2 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/mediation/MediatorView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/mediation/MediatorView.java @@ -25,6 +25,7 @@ import bisq.desktop.main.support.dispute.agent.DisputeAgentView; import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.alert.PrivateNotificationManager; +import bisq.core.dao.DaoFacade; import bisq.core.support.SupportType; import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.DisputeSession; @@ -53,6 +54,7 @@ public class MediatorView extends DisputeAgentView { ContractWindow contractWindow, TradeDetailsWindow tradeDetailsWindow, AccountAgeWitnessService accountAgeWitnessService, + DaoFacade daoFacade, @Named(Config.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) { super(mediationManager, keyRing, @@ -63,6 +65,7 @@ public class MediatorView extends DisputeAgentView { contractWindow, tradeDetailsWindow, accountAgeWitnessService, + daoFacade, useDevPrivilegeKeys); } diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/refund/RefundAgentView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/refund/RefundAgentView.java index 5aae5f53f8..71f27f8954 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/refund/RefundAgentView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/refund/RefundAgentView.java @@ -25,6 +25,7 @@ import bisq.desktop.main.support.dispute.agent.DisputeAgentView; import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.alert.PrivateNotificationManager; +import bisq.core.dao.DaoFacade; import bisq.core.support.SupportType; import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.DisputeSession; @@ -37,9 +38,8 @@ import bisq.core.util.coin.CoinFormatter; import bisq.common.config.Config; import bisq.common.crypto.KeyRing; -import javax.inject.Named; - import javax.inject.Inject; +import javax.inject.Named; @FxmlView public class RefundAgentView extends DisputeAgentView { @@ -54,6 +54,7 @@ public class RefundAgentView extends DisputeAgentView { ContractWindow contractWindow, TradeDetailsWindow tradeDetailsWindow, AccountAgeWitnessService accountAgeWitnessService, + DaoFacade daoFacade, @Named(Config.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) { super(refundManager, keyRing, @@ -64,6 +65,7 @@ public class RefundAgentView extends DisputeAgentView { contractWindow, tradeDetailsWindow, accountAgeWitnessService, + daoFacade, useDevPrivilegeKeys); } diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 2f334e06e0..b10a4a02da 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -792,6 +792,7 @@ message Dispute { SupportType support_type = 24; string mediators_dispute_result = 25; string delayed_payout_tx_id = 26; + string donation_address_of_delayed_payout_tx = 27; } message Attachment { From 2b0433870cb859239f6b8462838103b00533b990 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Fri, 11 Sep 2020 17:00:24 -0500 Subject: [PATCH 02/31] Dont allow opening refudn agent dispute if delayed payout tx is invalid. --- .../portfolio/pendingtrades/PendingTradesDataModel.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java index e1f2b10d9b..07be33a3c4 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java @@ -554,7 +554,14 @@ public class PendingTradesDataModel extends ActivatableDataModel { // The peer sent us an invalid donation address. We do not return here as we don't want to break // mediation/arbitration and log only the issue. The dispute agent will run validation as well and will get // a popup displayed to react. - log.error("Donation address invalid. {}", e.toString()); + log.error("DelayedPayoutTxValidation failed. {}", e.toString()); + + if (useRefundAgent) { + // We don't allow to continue and publish payout tx and open refund agent case. + // In case it was caused by some bug we want to prevent a wrong payout. In case its a scam attempt we + // want to protect the refund agent. + return; + } } ResultHandler resultHandler; From c48abbf575531f9e26fe00072ce48a1dd3c3bd8b Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Fri, 11 Sep 2020 17:37:03 -0500 Subject: [PATCH 03/31] Improve address validation code --- .../core/support/dispute/DisputeManager.java | 4 +- .../core/trade/DelayedPayoutTxValidation.java | 75 ++++++------------- .../windows/DisputeSummaryWindow.java | 25 ++++--- 3 files changed, 39 insertions(+), 65 deletions(-) diff --git a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java index 5a01fdba56..d327eaad72 100644 --- a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java +++ b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java @@ -290,7 +290,9 @@ public abstract class DisputeManager allPastParamValues = daoFacade.getAllPastParamValues(Param.RECIPIENT_BTC_ADDRESS); - if (allPastParamValues.contains(addressAsString)) { - return true; - } + // If Dao is deactivated we need to add the default address as getAllPastParamValues will not return us any. + allPastParamValues.add(Param.RECIPIENT_BTC_ADDRESS.getDefaultValue()); - log.warn("Donation address is not a valid DAO donation address." + - "\nAddress used in the dispute: " + addressAsString + - "\nAll DAO param donation addresses:" + allPastParamValues); - return false; + // If Dao is deactivated we need to add the past addresses used as well. + // This list need to be updated once a new address gets defined. + allPastParamValues.add("3EtUWqsGThPtjwUczw27YCo6EWvQdaPUyp"); // burning man 2019 + allPastParamValues.add("3A8Zc1XioE2HRzYfbb5P8iemCS72M6vRJV"); // burningman2 + + + if (!allPastParamValues.contains(addressAsString)) { + String errorMsg = "Donation address is not a valid DAO donation address." + + "\nAddress used in the dispute: " + addressAsString + + "\nAll DAO param donation addresses:" + allPastParamValues; + log.error(errorMsg); + throw new DonationAddressException(errorMsg); + } } public static void validatePayoutTx(Trade trade, @@ -212,16 +216,10 @@ public class DelayedPayoutTxValidation { throw new AmountMismatchException(errorMsg); } - - // Validate donation address - // Get most recent donation address. - // We do not support past DAO param addresses to avoid that those receive funds (no bond set up anymore). - // Users who have not synced the DAO cannot trade. - NetworkParameters params = btcWalletService.getParams(); Address address = output.getAddressFromP2PKHScript(params); if (address == null) { - // The donation address can be as well be a multisig address. + // The donation address can be a multisig address as well. address = output.getAddressFromP2SH(params); if (address == null) { errorMsg = "Donation address cannot be resolved (not of type P2PKHScript or P2SH). Output: " + output; @@ -236,36 +234,7 @@ public class DelayedPayoutTxValidation { addressConsumer.accept(addressAsString); } - // In case the seller has deactivated the DAO the default address will be used. - String defaultDonationAddressString = Param.RECIPIENT_BTC_ADDRESS.getDefaultValue(); - boolean defaultNotMatching = !defaultDonationAddressString.equals(addressAsString); - String recentDonationAddressString = daoFacade.getParamValue(Param.RECIPIENT_BTC_ADDRESS); - boolean recentFromDaoNotMatching = !recentDonationAddressString.equals(addressAsString); - - // If buyer has DAO deactivated or not synced he will not be able to see recent address used by the seller, so - // we add it hard coded here. We need to support also the default one as - // FIXME This is a quick fix and should be improved in future. - // We use the default addresses for non mainnet networks. For dev testing it need to be changed here. - // We use a list to gain more flexibility at updates of DAO param, but still might fail if buyer has not updated - // software. Needs a better solution.... - List hardCodedAddresses = Config.baseCurrencyNetwork().isMainnet() ? - List.of("3EtUWqsGThPtjwUczw27YCo6EWvQdaPUyp", "3A8Zc1XioE2HRzYfbb5P8iemCS72M6vRJV") : // mainnet - Config.baseCurrencyNetwork().isDaoBetaNet() ? List.of("1BVxNn3T12veSK6DgqwU4Hdn7QHcDDRag7") : // daoBetaNet - Config.baseCurrencyNetwork().isTestnet() ? List.of("2N4mVTpUZAnhm9phnxB7VrHB4aBhnWrcUrV") : // testnet - List.of("2MzBNTJDjjXgViKBGnatDU3yWkJ8pJkEg9w"); // regtest or DAO testnet (regtest) - - boolean noneOfHardCodedMatching = hardCodedAddresses.stream().noneMatch(e -> e.equals(addressAsString)); - - // If seller has DAO deactivated as well we get default address - if (recentFromDaoNotMatching && defaultNotMatching && noneOfHardCodedMatching) { - errorMsg = "Donation address is invalid." + - "\nAddress used by BTC seller: " + addressAsString + - "\nRecent donation address:" + recentDonationAddressString + - "\nDefault donation address: " + defaultDonationAddressString; - log.error(errorMsg); - log.error(delayedPayoutTx.toString()); - throw new DonationAddressException(errorMsg); - } + validateDonationAddress(addressAsString, daoFacade); if (dispute != null) { // Verify that address in the dispute matches the one in the trade. diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java index d78723ec53..d7ce950ea1 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java @@ -650,17 +650,18 @@ public class DisputeSummaryWindow extends Overlay { return; } - if (!DelayedPayoutTxValidation.isValidDonationAddress(dispute, daoFacade)) { + try { + DelayedPayoutTxValidation.validateDonationAddress(dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade); + + if (dispute.getSupportType() == SupportType.REFUND && + peersDisputeOptional.isPresent() && + !peersDisputeOptional.get().isClosed()) { + showPayoutTxConfirmation(contract, disputeResult, () -> doClose(closeTicketButton)); + } else { + doClose(closeTicketButton); + } + } catch (DelayedPayoutTxValidation.DonationAddressException exception) { showInValidDonationAddressPopup(); - } else if (dispute.getSupportType() == SupportType.REFUND && - peersDisputeOptional.isPresent() && - !peersDisputeOptional.get().isClosed()) { - showPayoutTxConfirmation(contract, disputeResult, - () -> { - doClose(closeTicketButton); - }); - } else { - doClose(closeTicketButton); } }); @@ -752,7 +753,9 @@ public class DisputeSummaryWindow extends Overlay { private void doClose(Button closeTicketButton) { var disputeManager = checkNotNull(getDisputeManager(dispute)); - if (!DelayedPayoutTxValidation.isValidDonationAddress(dispute, daoFacade)) { + try { + DelayedPayoutTxValidation.validateDonationAddress(dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade); + } catch (DelayedPayoutTxValidation.DonationAddressException exception) { showInValidDonationAddressPopup(); // For mediators we do not enforce that the case cannot be closed to stay flexible, From d82631f52ce71bb9b1ff1718ee6cbcc30445a85e Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Fri, 11 Sep 2020 19:41:08 -0500 Subject: [PATCH 04/31] Fix some issues found during testing Refactorings --- .../main/java/bisq/core/dao/DaoFacade.java | 16 +++ .../core/support/dispute/DisputeManager.java | 14 +- .../core/trade/DelayedPayoutTxValidation.java | 125 +++++++++--------- .../java/bisq/core/trade/TradeManager.java | 6 +- .../BuyerVerifiesFinalDelayedPayoutTx.java | 7 +- .../BuyerVerifiesPreparedDelayedPayoutTx.java | 6 +- .../resources/i18n/displayStrings.properties | 5 +- .../windows/DisputeSummaryWindow.java | 65 ++++----- .../pendingtrades/PendingTradesDataModel.java | 6 +- .../steps/buyer/BuyerStep1View.java | 11 +- .../steps/buyer/BuyerStep2View.java | 11 +- .../dispute/agent/DisputeAgentView.java | 19 +-- 12 files changed, 145 insertions(+), 146 deletions(-) diff --git a/core/src/main/java/bisq/core/dao/DaoFacade.java b/core/src/main/java/bisq/core/dao/DaoFacade.java index 50b6402c5a..bfc542a862 100644 --- a/core/src/main/java/bisq/core/dao/DaoFacade.java +++ b/core/src/main/java/bisq/core/dao/DaoFacade.java @@ -759,4 +759,20 @@ public class DaoFacade implements DaoSetupService { }); return set; } + + public Set getAllDonationAddresses() { + // We support any of the past addresses as well as in case the peer has not enabled the DAO or is out of sync we + // do not want to break validation. + Set allPastParamValues = getAllPastParamValues(Param.RECIPIENT_BTC_ADDRESS); + + // If Dao is deactivated we need to add the default address as getAllPastParamValues will not return us any. + allPastParamValues.add(Param.RECIPIENT_BTC_ADDRESS.getDefaultValue()); + + // If Dao is deactivated we need to add the past addresses used as well. + // This list need to be updated once a new address gets defined. + allPastParamValues.add("3EtUWqsGThPtjwUczw27YCo6EWvQdaPUyp"); // burning man 2019 + allPastParamValues.add("3A8Zc1XioE2HRzYfbb5P8iemCS72M6vRJV"); // burningman2 + + return allPastParamValues; + } } diff --git a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java index d327eaad72..3fc8538e15 100644 --- a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java +++ b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java @@ -74,7 +74,6 @@ import lombok.extern.slf4j.Slf4j; import javax.annotation.Nullable; -import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @Slf4j @@ -292,7 +291,7 @@ public abstract class DisputeManager optionalTrade = tradeManager.getTradeById(dispute.getTradeId()); - checkArgument(optionalTrade.isPresent()); + if (!optionalTrade.isPresent()) { + return; + } + Trade trade = optionalTrade.get(); try { DelayedPayoutTxValidation.validatePayoutTx(trade, @@ -348,11 +350,7 @@ public abstract class DisputeManager allPastParamValues = daoFacade.getAllPastParamValues(Param.RECIPIENT_BTC_ADDRESS); - - // If Dao is deactivated we need to add the default address as getAllPastParamValues will not return us any. - allPastParamValues.add(Param.RECIPIENT_BTC_ADDRESS.getDefaultValue()); - - // If Dao is deactivated we need to add the past addresses used as well. - // This list need to be updated once a new address gets defined. - allPastParamValues.add("3EtUWqsGThPtjwUczw27YCo6EWvQdaPUyp"); // burning man 2019 - allPastParamValues.add("3A8Zc1XioE2HRzYfbb5P8iemCS72M6vRJV"); // burningman2 - - + Set allPastParamValues = daoFacade.getAllDonationAddresses(); if (!allPastParamValues.contains(addressAsString)) { String errorMsg = "Donation address is not a valid DAO donation address." + "\nAddress used in the dispute: " + addressAsString + "\nAll DAO param donation addresses:" + allPastParamValues; log.error(errorMsg); - throw new DonationAddressException(errorMsg); + throw new AddressException(errorMsg); } } @@ -113,8 +65,8 @@ public class DelayedPayoutTxValidation { Transaction delayedPayoutTx, DaoFacade daoFacade, BtcWalletService btcWalletService) - throws DonationAddressException, MissingDelayedPayoutTxException, - InvalidTxException, InvalidLockTimeException, AmountMismatchException { + throws AddressException, MissingTxException, + InvalidTxException, InvalidLockTimeException, InvalidAmountException { validatePayoutTx(trade, delayedPayoutTx, null, @@ -128,8 +80,8 @@ public class DelayedPayoutTxValidation { @Nullable Dispute dispute, DaoFacade daoFacade, BtcWalletService btcWalletService) - throws DonationAddressException, MissingDelayedPayoutTxException, - InvalidTxException, InvalidLockTimeException, AmountMismatchException { + throws AddressException, MissingTxException, + InvalidTxException, InvalidLockTimeException, InvalidAmountException { validatePayoutTx(trade, delayedPayoutTx, dispute, @@ -143,8 +95,8 @@ public class DelayedPayoutTxValidation { DaoFacade daoFacade, BtcWalletService btcWalletService, @Nullable Consumer addressConsumer) - throws DonationAddressException, MissingDelayedPayoutTxException, - InvalidTxException, InvalidLockTimeException, AmountMismatchException { + throws AddressException, MissingTxException, + InvalidTxException, InvalidLockTimeException, InvalidAmountException { validatePayoutTx(trade, delayedPayoutTx, null, @@ -159,13 +111,13 @@ public class DelayedPayoutTxValidation { DaoFacade daoFacade, BtcWalletService btcWalletService, @Nullable Consumer addressConsumer) - throws DonationAddressException, MissingDelayedPayoutTxException, - InvalidTxException, InvalidLockTimeException, AmountMismatchException { + throws AddressException, MissingTxException, + InvalidTxException, InvalidLockTimeException, InvalidAmountException { String errorMsg; if (delayedPayoutTx == null) { errorMsg = "DelayedPayoutTx must not be null"; log.error(errorMsg); - throw new MissingDelayedPayoutTxException("DelayedPayoutTx must not be null"); + throw new MissingTxException("DelayedPayoutTx must not be null"); } // Validate tx structure @@ -213,7 +165,7 @@ public class DelayedPayoutTxValidation { errorMsg = "Output value of deposit tx and delayed payout tx is not matching. Output: " + output + " / msOutputAmount: " + msOutputAmount; log.error(errorMsg); log.error(delayedPayoutTx.toString()); - throw new AmountMismatchException(errorMsg); + throw new InvalidAmountException(errorMsg); } NetworkParameters params = btcWalletService.getParams(); @@ -225,7 +177,7 @@ public class DelayedPayoutTxValidation { errorMsg = "Donation address cannot be resolved (not of type P2PKHScript or P2SH). Output: " + output; log.error(errorMsg); log.error(delayedPayoutTx.toString()); - throw new DonationAddressException(errorMsg); + throw new AddressException(errorMsg); } } @@ -261,4 +213,51 @@ public class DelayedPayoutTxValidation { "Deposit tx=" + depositTx); } } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Exceptions + /////////////////////////////////////////////////////////////////////////////////////////// + + public static class ValidationException extends Exception { + ValidationException(String msg) { + super(msg); + } + } + + public static class AddressException extends ValidationException { + AddressException(String msg) { + super(msg); + } + } + + public static class MissingTxException extends ValidationException { + MissingTxException(String msg) { + super(msg); + } + } + + public static class InvalidTxException extends ValidationException { + InvalidTxException(String msg) { + super(msg); + } + } + + public static class InvalidAmountException extends ValidationException { + InvalidAmountException(String msg) { + super(msg); + } + } + + public static class InvalidLockTimeException extends ValidationException { + InvalidLockTimeException(String msg) { + super(msg); + } + } + + public static class InvalidInputException extends ValidationException { + InvalidInputException(String msg) { + super(msg); + } + } } diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index 66962f10d6..6c0f3d79a3 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -309,11 +309,7 @@ public class TradeManager implements PersistedDataHost { trade.getDelayedPayoutTx(), daoFacade, btcWalletService); - } catch (DelayedPayoutTxValidation.DonationAddressException | - DelayedPayoutTxValidation.InvalidTxException | - DelayedPayoutTxValidation.InvalidLockTimeException | - DelayedPayoutTxValidation.MissingDelayedPayoutTxException | - DelayedPayoutTxValidation.AmountMismatchException e) { + } catch (DelayedPayoutTxValidation.ValidationException e) { log.warn("Delayed payout tx exception, trade {}, exception {}", trade.getId(), e.getMessage()); if (!allowFaultyDelayedTxs) { // We move it to failed trades so it cannot be continued. diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesFinalDelayedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesFinalDelayedPayoutTx.java index fa1aeacdbe..3aaf36a39d 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesFinalDelayedPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesFinalDelayedPayoutTx.java @@ -55,12 +55,7 @@ public class BuyerVerifiesFinalDelayedPayoutTx extends TradeTask { DelayedPayoutTxValidation.validatePayoutTxInput(depositTx, delayedPayoutTx); complete(); - } catch (DelayedPayoutTxValidation.DonationAddressException | - DelayedPayoutTxValidation.MissingDelayedPayoutTxException | - DelayedPayoutTxValidation.InvalidTxException | - DelayedPayoutTxValidation.InvalidLockTimeException | - DelayedPayoutTxValidation.AmountMismatchException | - DelayedPayoutTxValidation.InvalidInputException e) { + } catch (DelayedPayoutTxValidation.ValidationException e) { failed(e.getMessage()); } catch (Throwable t) { failed(t); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesPreparedDelayedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesPreparedDelayedPayoutTx.java index 3242ba8cf6..7853767d27 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesPreparedDelayedPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesPreparedDelayedPayoutTx.java @@ -43,11 +43,7 @@ public class BuyerVerifiesPreparedDelayedPayoutTx extends TradeTask { processModel.getBtcWalletService()); complete(); - } catch (DelayedPayoutTxValidation.DonationAddressException | - DelayedPayoutTxValidation.MissingDelayedPayoutTxException | - DelayedPayoutTxValidation.InvalidTxException | - DelayedPayoutTxValidation.InvalidLockTimeException | - DelayedPayoutTxValidation.AmountMismatchException e) { + } catch (DelayedPayoutTxValidation.ValidationException e) { failed(e.getMessage()); } catch (Throwable t) { failed(t); diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 4156441334..2f41ef75b0 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -1086,7 +1086,10 @@ support.warning.disputesWithInvalidDonationAddress=The delayed payout transactio Please inform the developers about that incident and do not close that case before the situation is resolved!\n\n\ Address used in the dispute: {0}\n\n\ All DAO param donation addresses: {1}\n\n\ - Trade ID: {2} + Trade ID: {2}\ + {3} +support.warning.disputesWithInvalidDonationAddress.mediator=\n\nDo you still want to close the dispute? +support.warning.disputesWithInvalidDonationAddress.refundAgent=\n\nYou must not do the payout. #################################################################### diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java index d7ce950ea1..3e349efc4d 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java @@ -35,7 +35,6 @@ import bisq.core.btc.wallet.Restrictions; import bisq.core.btc.wallet.TradeWalletService; import bisq.core.btc.wallet.TxBroadcaster; import bisq.core.dao.DaoFacade; -import bisq.core.dao.governance.param.Param; import bisq.core.locale.Res; import bisq.core.offer.Offer; import bisq.core.provider.fee.FeeService; @@ -87,7 +86,6 @@ import javafx.beans.value.ChangeListener; import java.util.Date; import java.util.Optional; -import java.util.Set; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; @@ -650,18 +648,12 @@ public class DisputeSummaryWindow extends Overlay { return; } - try { - DelayedPayoutTxValidation.validateDonationAddress(dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade); - - if (dispute.getSupportType() == SupportType.REFUND && - peersDisputeOptional.isPresent() && - !peersDisputeOptional.get().isClosed()) { - showPayoutTxConfirmation(contract, disputeResult, () -> doClose(closeTicketButton)); - } else { - doClose(closeTicketButton); - } - } catch (DelayedPayoutTxValidation.DonationAddressException exception) { - showInValidDonationAddressPopup(); + if (dispute.getSupportType() == SupportType.REFUND && + peersDisputeOptional.isPresent() && + !peersDisputeOptional.get().isClosed()) { + showPayoutTxConfirmation(contract, disputeResult, () -> doCloseIfValid(closeTicketButton)); + } else { + doCloseIfValid(closeTicketButton); } }); @@ -742,7 +734,6 @@ public class DisputeSummaryWindow extends Overlay { public void onFailure(TxBroadcastException exception) { log.error("TxBroadcastException at doPayout", exception); new Popup().error(exception.toString()).show(); - ; } }); } catch (InsufficientMoneyException | WalletException | TransactionVerificationException e) { @@ -751,20 +742,43 @@ public class DisputeSummaryWindow extends Overlay { } } - private void doClose(Button closeTicketButton) { + private void doCloseIfValid(Button closeTicketButton) { var disputeManager = checkNotNull(getDisputeManager(dispute)); try { DelayedPayoutTxValidation.validateDonationAddress(dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade); - } catch (DelayedPayoutTxValidation.DonationAddressException exception) { - showInValidDonationAddressPopup(); + doClose(closeTicketButton); + } catch (DelayedPayoutTxValidation.AddressException exception) { + String addressAsString = dispute.getDonationAddressOfDelayedPayoutTx(); + String tradeId = dispute.getTradeId(); // For mediators we do not enforce that the case cannot be closed to stay flexible, // but for refund agents we do. - if (disputeManager instanceof RefundManager) { - return; + if (disputeManager instanceof MediationManager) { + new Popup().width(900) + .warning(Res.get("support.warning.disputesWithInvalidDonationAddress", + addressAsString, + daoFacade.getAllDonationAddresses(), + tradeId, + Res.get("support.warning.disputesWithInvalidDonationAddress.mediator"))) + .onAction(() -> { + doClose(closeTicketButton); + }) + .actionButtonText(Res.get("shared.yes")) + .closeButtonText(Res.get("shared.no")) + .show(); + } else { + new Popup().width(900) + .warning(Res.get("support.warning.disputesWithInvalidDonationAddress", + addressAsString, + daoFacade.getAllDonationAddresses(), + tradeId, + Res.get("support.warning.disputesWithInvalidDonationAddress.refundAgent"))) + .show(); } } + } + private void doClose(Button closeTicketButton) { disputeResult.setLoserPublisher(isLoserPublisherCheckBox.isSelected()); disputeResult.setCloseDate(new Date()); dispute.setDisputeResult(disputeResult); @@ -789,7 +803,7 @@ public class DisputeSummaryWindow extends Overlay { text += Res.get("disputeSummaryWindow.close.nextStepsForRefundAgentArbitration"); } - disputeManager.sendDisputeResultMessage(disputeResult, dispute, text); + checkNotNull(getDisputeManager(dispute)).sendDisputeResultMessage(disputeResult, dispute, text); if (peersDisputeOptional.isPresent() && !peersDisputeOptional.get().isClosed() && !DevEnv.isDevMode()) { UserThread.runAfter(() -> new Popup() @@ -805,15 +819,6 @@ public class DisputeSummaryWindow extends Overlay { hide(); } - private void showInValidDonationAddressPopup() { - String addressAsString = dispute.getDonationAddressOfDelayedPayoutTx(); - Set allPastParamValues = daoFacade.getAllPastParamValues(Param.RECIPIENT_BTC_ADDRESS); - String tradeId = dispute.getTradeId(); - new Popup().warning(Res.get("support.warning.disputesWithInvalidDonationAddress", - addressAsString, allPastParamValues, tradeId)) - .show(); - } - private DisputeManager> getDisputeManager(Dispute dispute) { if (dispute.getSupportType() != null) { switch (dispute.getSupportType()) { diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java index 07be33a3c4..3da51c70c7 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java @@ -546,11 +546,7 @@ public class PendingTradesDataModel extends ActivatableDataModel { daoFacade, btcWalletService, donationAddressString::set); - } catch (DelayedPayoutTxValidation.DonationAddressException | - DelayedPayoutTxValidation.InvalidTxException | - DelayedPayoutTxValidation.InvalidLockTimeException | - DelayedPayoutTxValidation.MissingDelayedPayoutTxException | - DelayedPayoutTxValidation.AmountMismatchException e) { + } catch (DelayedPayoutTxValidation.ValidationException e) { // The peer sent us an invalid donation address. We do not return here as we don't want to break // mediation/arbitration and log only the issue. The dispute agent will run validation as well and will get // a popup displayed to react. diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep1View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep1View.java index 184dcd038f..75dfb517c4 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep1View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep1View.java @@ -43,16 +43,13 @@ public class BuyerStep1View extends TradeStepView { trade.getDelayedPayoutTx(), model.dataModel.daoFacade, model.dataModel.btcWalletService); - } catch (DelayedPayoutTxValidation.DonationAddressException | - DelayedPayoutTxValidation.InvalidTxException | - DelayedPayoutTxValidation.AmountMismatchException | - DelayedPayoutTxValidation.InvalidLockTimeException e) { + } catch (DelayedPayoutTxValidation.MissingTxException ignore) { + // We don't react on those errors as a failed trade might get listed initially but getting removed from the + // trade manager after initPendingTrades which happens after activate might be called. + } catch (DelayedPayoutTxValidation.ValidationException e) { if (!model.dataModel.tradeManager.isAllowFaultyDelayedTxs()) { new Popup().warning(Res.get("portfolio.pending.invalidDelayedPayoutTx", e.getMessage())).show(); } - } catch (DelayedPayoutTxValidation.MissingDelayedPayoutTxException ignore) { - // We don't react on those errors as a failed trade might get listed initially but getting removed from the - // trade manager after initPendingTrades which happens after activate might be called. } } 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 d6d2aeabe3..9011390f59 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 @@ -122,16 +122,13 @@ public class BuyerStep2View extends TradeStepView { trade.getDelayedPayoutTx(), model.dataModel.daoFacade, model.dataModel.btcWalletService); - } catch (DelayedPayoutTxValidation.DonationAddressException | - DelayedPayoutTxValidation.InvalidTxException | - DelayedPayoutTxValidation.AmountMismatchException | - DelayedPayoutTxValidation.InvalidLockTimeException e) { + } catch (DelayedPayoutTxValidation.MissingTxException ignore) { + // We don't react on those errors as a failed trade might get listed initially but getting removed from the + // trade manager after initPendingTrades which happens after activate might be called. + } catch (DelayedPayoutTxValidation.ValidationException e) { if (!model.dataModel.tradeManager.isAllowFaultyDelayedTxs()) { new Popup().warning(Res.get("portfolio.pending.invalidDelayedPayoutTx", e.getMessage())).show(); } - } catch (DelayedPayoutTxValidation.MissingDelayedPayoutTxException ignore) { - // We don't react on those errors as a failed trade might get listed initially but getting removed from the - // trade manager after initPendingTrades which happens after activate might be called. } if (timeoutTimer != null) diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java index 6f6efdd206..f7d059b110 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java @@ -28,7 +28,6 @@ import bisq.desktop.main.support.dispute.DisputeView; import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.alert.PrivateNotificationManager; import bisq.core.dao.DaoFacade; -import bisq.core.dao.governance.param.Param; import bisq.core.locale.Res; import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.DisputeList; @@ -59,7 +58,6 @@ import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.collections.ListChangeListener; import java.util.List; -import java.util.Set; import static bisq.desktop.util.FormBuilder.getIconForLabel; @@ -126,13 +124,16 @@ public abstract class DisputeAgentView extends DisputeView implements MultipleHo } protected void showWarningForInvalidDonationAddress(List disputes) { - disputes.forEach(dispute -> { - String addressAsString = dispute.getDonationAddressOfDelayedPayoutTx(); - Set allPastParamValues = daoFacade.getAllPastParamValues(Param.RECIPIENT_BTC_ADDRESS); - new Popup().warning(Res.get("support.warning.disputesWithInvalidDonationAddress", - addressAsString, allPastParamValues, dispute.getTradeId())) - .show(); - }); + disputes.stream() + .filter(dispute -> !dispute.isClosed()) + .forEach(dispute -> { + new Popup().warning(Res.get("support.warning.disputesWithInvalidDonationAddress", + dispute.getDonationAddressOfDelayedPayoutTx(), + daoFacade.getAllDonationAddresses(), + dispute.getTradeId(), + "")) + .show(); + }); } @Override From 677211badf15297b1ef6c419ced865679dcbdb0d Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Fri, 11 Sep 2020 20:24:31 -0500 Subject: [PATCH 05/31] Allow close dispute for refund agent without payout --- .../resources/i18n/displayStrings.properties | 3 + .../windows/DisputeSummaryWindow.java | 69 +++++++++++-------- 2 files changed, 43 insertions(+), 29 deletions(-) diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 2f41ef75b0..b72799295c 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -2466,6 +2466,9 @@ disputeSummaryWindow.close.txDetails=Spending: {0}\n\ Transaction size: {5} Kb\n\n\ Are you sure you want to publish this transaction? +disputeSummaryWindow.close.noPayout.headline=Close without any payout +disputeSummaryWindow.close.noPayout.text=Do you want to close without doing any payout? + emptyWalletWindow.headline={0} emergency wallet tool emptyWalletWindow.info=Please use that only in emergency case if you cannot access your fund from the UI.\n\n\ Please note that all open offers will be closed automatically when using this tool.\n\n\ diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java index 3e349efc4d..b374f457b2 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java @@ -387,14 +387,15 @@ public class DisputeSummaryWindow extends Overlay { .add(offer.getSellerSecurityDeposit()); Coin totalAmount = buyerAmount.add(sellerAmount); - if (!totalAmount.isPositive()) { - return false; - } - - if (getDisputeManager(dispute) instanceof RefundManager) { - // We allow to spend less in case of RefundAgent + boolean isRefundAgent = getDisputeManager(dispute) instanceof RefundManager; + if (isRefundAgent) { + // We allow to spend less in case of RefundAgent or even zero to both, so in that case no payout tx will + // be made return totalAmount.compareTo(available) <= 0; } else { + if (!totalAmount.isPositive()) { + return false; + } return totalAmount.compareTo(available) == 0; } } @@ -651,7 +652,9 @@ public class DisputeSummaryWindow extends Overlay { if (dispute.getSupportType() == SupportType.REFUND && peersDisputeOptional.isPresent() && !peersDisputeOptional.get().isClosed()) { - showPayoutTxConfirmation(contract, disputeResult, () -> doCloseIfValid(closeTicketButton)); + showPayoutTxConfirmation(contract, + disputeResult, + () -> doCloseIfValid(closeTicketButton)); } else { doCloseIfValid(closeTicketButton); } @@ -687,28 +690,36 @@ public class DisputeSummaryWindow extends Overlay { formatter.formatCoinWithCode(sellerPayoutAmount), sellerPayoutAddressString); } - new Popup().width(900) - .headLine(Res.get("disputeSummaryWindow.close.txDetails.headline")) - .confirmation(Res.get("disputeSummaryWindow.close.txDetails", - formatter.formatCoinWithCode(inputAmount), - buyerDetails, - sellerDetails, - formatter.formatCoinWithCode(fee), - feePerByte, - kb)) - .actionButtonText(Res.get("shared.yes")) - .onAction(() -> { - doPayout(buyerPayoutAmount, - sellerPayoutAmount, - fee, - buyerPayoutAddressString, - sellerPayoutAddressString, - resultHandler); - }) - .closeButtonText(Res.get("shared.cancel")) - .onClose(() -> { - }) - .show(); + if (outputAmount.isPositive()) { + new Popup().width(900) + .headLine(Res.get("disputeSummaryWindow.close.txDetails.headline")) + .confirmation(Res.get("disputeSummaryWindow.close.txDetails", + formatter.formatCoinWithCode(inputAmount), + buyerDetails, + sellerDetails, + formatter.formatCoinWithCode(fee), + feePerByte, + kb)) + .actionButtonText(Res.get("shared.yes")) + .onAction(() -> { + doPayout(buyerPayoutAmount, + sellerPayoutAmount, + fee, + buyerPayoutAddressString, + sellerPayoutAddressString, + resultHandler); + }) + .closeButtonText(Res.get("shared.cancel")) + .show(); + } else { + // No payout will be made + new Popup().headLine(Res.get("disputeSummaryWindow.close.noPayout.headline")) + .confirmation(Res.get("disputeSummaryWindow.close.noPayout.text")) + .actionButtonText(Res.get("shared.yes")) + .onAction(resultHandler::handleResult) + .closeButtonText(Res.get("shared.cancel")) + .show(); + } } private void doPayout(Coin buyerPayoutAmount, From 08fb5966296c013cce5c5384a10e747260379d56 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Fri, 11 Sep 2020 20:25:48 -0500 Subject: [PATCH 06/31] Call validatePayoutTx only after trades are initialized --- core/src/main/java/bisq/core/trade/Trade.java | 16 ++++-- .../java/bisq/core/trade/TradeManager.java | 1 + .../steps/buyer/BuyerStep1View.java | 53 ++++++++++++++----- .../steps/buyer/BuyerStep2View.java | 45 +++++++++++----- 4 files changed, 88 insertions(+), 27 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/Trade.java index a00fe2dca8..3242bd535e 100644 --- a/core/src/main/java/bisq/core/trade/Trade.java +++ b/core/src/main/java/bisq/core/trade/Trade.java @@ -725,9 +725,19 @@ public abstract class Trade implements Tradable, Model { @Nullable public Transaction getDelayedPayoutTx() { if (delayedPayoutTx == null) { - delayedPayoutTx = delayedPayoutTxBytes != null && processModel.getBtcWalletService() != null ? - processModel.getBtcWalletService().getTxFromSerializedTx(delayedPayoutTxBytes) : - null; + BtcWalletService btcWalletService = processModel.getBtcWalletService(); + if (btcWalletService == null) { + log.warn("btcWalletService is null. You might call that method before the tradeManager has " + + "initialized all trades"); + return null; + } + + if (delayedPayoutTxBytes == null) { + log.warn("delayedPayoutTxBytes are null"); + return null; + } + + delayedPayoutTx = btcWalletService.getTxFromSerializedTx(delayedPayoutTxBytes); } return delayedPayoutTx; } diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index 6c0f3d79a3..ea9bcdf293 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -135,6 +135,7 @@ public class TradeManager implements PersistedDataHost { private final Storage> tradableListStorage; private TradableList tradableList; + @Getter private final BooleanProperty pendingTradesInitialized = new SimpleBooleanProperty(); private List tradesForStatistics; @Setter diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep1View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep1View.java index 75dfb517c4..716d6bae04 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep1View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep1View.java @@ -24,7 +24,13 @@ import bisq.desktop.main.portfolio.pendingtrades.steps.TradeStepView; import bisq.core.locale.Res; import bisq.core.trade.DelayedPayoutTxValidation; +import bisq.common.UserThread; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.value.ChangeListener; + public class BuyerStep1View extends TradeStepView { + private ChangeListener pendingTradesInitializedListener; /////////////////////////////////////////////////////////////////////////////////////////// // Constructor, Initialisation @@ -38,18 +44,18 @@ public class BuyerStep1View extends TradeStepView { public void activate() { super.activate(); - try { - DelayedPayoutTxValidation.validatePayoutTx(trade, - trade.getDelayedPayoutTx(), - model.dataModel.daoFacade, - model.dataModel.btcWalletService); - } catch (DelayedPayoutTxValidation.MissingTxException ignore) { - // We don't react on those errors as a failed trade might get listed initially but getting removed from the - // trade manager after initPendingTrades which happens after activate might be called. - } catch (DelayedPayoutTxValidation.ValidationException e) { - if (!model.dataModel.tradeManager.isAllowFaultyDelayedTxs()) { - new Popup().warning(Res.get("portfolio.pending.invalidDelayedPayoutTx", e.getMessage())).show(); - } + // We need to have the trades initialized before we can call validatePayoutTx. + BooleanProperty pendingTradesInitialized = model.dataModel.tradeManager.getPendingTradesInitialized(); + if (pendingTradesInitialized.get()) { + validatePayoutTx(); + } else { + pendingTradesInitializedListener = (observable, oldValue, newValue) -> { + if (newValue) { + validatePayoutTx(); + UserThread.execute(() -> pendingTradesInitialized.removeListener(pendingTradesInitializedListener)); + } + }; + pendingTradesInitialized.addListener(pendingTradesInitializedListener); } } @@ -85,6 +91,29 @@ public class BuyerStep1View extends TradeStepView { protected String getPeriodOverWarnText() { return Res.get("portfolio.pending.step1.openForDispute"); } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Private + /////////////////////////////////////////////////////////////////////////////////////////// + + private void validatePayoutTx() { + try { + DelayedPayoutTxValidation.validatePayoutTx(trade, + trade.getDelayedPayoutTx(), + model.dataModel.daoFacade, + model.dataModel.btcWalletService); + } catch (DelayedPayoutTxValidation.MissingTxException ignore) { + // We don't react on those errors as a failed trade might get listed initially but getting removed from the + // trade manager after initPendingTrades which happens after activate might be called. + log.error(""); + } catch (DelayedPayoutTxValidation.ValidationException e) { + if (!model.dataModel.tradeManager.isAllowFaultyDelayedTxs()) { + new Popup().warning(Res.get("portfolio.pending.invalidDelayedPayoutTx", e.getMessage())).show(); + } + } + } + } 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 9011390f59..6096e0cf3d 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 @@ -88,6 +88,9 @@ import javafx.scene.layout.Priority; import org.fxmisc.easybind.EasyBind; import org.fxmisc.easybind.Subscription; +import javafx.beans.property.BooleanProperty; +import javafx.beans.value.ChangeListener; + import java.util.List; import java.util.concurrent.TimeUnit; @@ -104,6 +107,7 @@ public class BuyerStep2View extends TradeStepView { private BusyAnimation busyAnimation; private Subscription tradeStatePropertySubscription; private Timer timeoutTimer; + private ChangeListener pendingTradesInitializedListener; /////////////////////////////////////////////////////////////////////////////////////////// // Constructor, Initialisation @@ -117,18 +121,18 @@ public class BuyerStep2View extends TradeStepView { public void activate() { super.activate(); - try { - DelayedPayoutTxValidation.validatePayoutTx(trade, - trade.getDelayedPayoutTx(), - model.dataModel.daoFacade, - model.dataModel.btcWalletService); - } catch (DelayedPayoutTxValidation.MissingTxException ignore) { - // We don't react on those errors as a failed trade might get listed initially but getting removed from the - // trade manager after initPendingTrades which happens after activate might be called. - } catch (DelayedPayoutTxValidation.ValidationException e) { - if (!model.dataModel.tradeManager.isAllowFaultyDelayedTxs()) { - new Popup().warning(Res.get("portfolio.pending.invalidDelayedPayoutTx", e.getMessage())).show(); - } + // We need to have the trades initialized before we can call validatePayoutTx. + BooleanProperty pendingTradesInitialized = model.dataModel.tradeManager.getPendingTradesInitialized(); + if (pendingTradesInitialized.get()) { + validatePayoutTx(); + } else { + pendingTradesInitializedListener = (observable, oldValue, newValue) -> { + if (newValue) { + validatePayoutTx(); + UserThread.execute(() -> pendingTradesInitialized.removeListener(pendingTradesInitializedListener)); + } + }; + pendingTradesInitialized.addListener(pendingTradesInitializedListener); } if (timeoutTimer != null) @@ -629,6 +633,23 @@ public class BuyerStep2View extends TradeStepView { } } + private void validatePayoutTx() { + try { + DelayedPayoutTxValidation.validatePayoutTx(trade, + trade.getDelayedPayoutTx(), + model.dataModel.daoFacade, + model.dataModel.btcWalletService); + } catch (DelayedPayoutTxValidation.MissingTxException ignore) { + // We don't react on those errors as a failed trade might get listed initially but getting removed from the + // trade manager after initPendingTrades which happens after activate might be called. + log.error(""); + } catch (DelayedPayoutTxValidation.ValidationException e) { + if (!model.dataModel.tradeManager.isAllowFaultyDelayedTxs()) { + new Popup().warning(Res.get("portfolio.pending.invalidDelayedPayoutTx", e.getMessage())).show(); + } + } + } + @Override protected void updateConfirmButtonDisableState(boolean isDisabled) { confirmButton.setDisable(isDisabled); From 05e1039423c265b4319bf40d065746ccb8e1c829 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Fri, 11 Sep 2020 20:28:23 -0500 Subject: [PATCH 07/31] Call validatePayoutTx only after trades are initialized --- core/src/main/java/bisq/core/trade/Trade.java | 16 ++++-- .../java/bisq/core/trade/TradeManager.java | 1 + .../steps/buyer/BuyerStep1View.java | 53 ++++++++++++++----- .../steps/buyer/BuyerStep2View.java | 45 +++++++++++----- 4 files changed, 88 insertions(+), 27 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/Trade.java index a00fe2dca8..3242bd535e 100644 --- a/core/src/main/java/bisq/core/trade/Trade.java +++ b/core/src/main/java/bisq/core/trade/Trade.java @@ -725,9 +725,19 @@ public abstract class Trade implements Tradable, Model { @Nullable public Transaction getDelayedPayoutTx() { if (delayedPayoutTx == null) { - delayedPayoutTx = delayedPayoutTxBytes != null && processModel.getBtcWalletService() != null ? - processModel.getBtcWalletService().getTxFromSerializedTx(delayedPayoutTxBytes) : - null; + BtcWalletService btcWalletService = processModel.getBtcWalletService(); + if (btcWalletService == null) { + log.warn("btcWalletService is null. You might call that method before the tradeManager has " + + "initialized all trades"); + return null; + } + + if (delayedPayoutTxBytes == null) { + log.warn("delayedPayoutTxBytes are null"); + return null; + } + + delayedPayoutTx = btcWalletService.getTxFromSerializedTx(delayedPayoutTxBytes); } return delayedPayoutTx; } diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index 6c0f3d79a3..ea9bcdf293 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -135,6 +135,7 @@ public class TradeManager implements PersistedDataHost { private final Storage> tradableListStorage; private TradableList tradableList; + @Getter private final BooleanProperty pendingTradesInitialized = new SimpleBooleanProperty(); private List tradesForStatistics; @Setter diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep1View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep1View.java index 75dfb517c4..716d6bae04 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep1View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep1View.java @@ -24,7 +24,13 @@ import bisq.desktop.main.portfolio.pendingtrades.steps.TradeStepView; import bisq.core.locale.Res; import bisq.core.trade.DelayedPayoutTxValidation; +import bisq.common.UserThread; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.value.ChangeListener; + public class BuyerStep1View extends TradeStepView { + private ChangeListener pendingTradesInitializedListener; /////////////////////////////////////////////////////////////////////////////////////////// // Constructor, Initialisation @@ -38,18 +44,18 @@ public class BuyerStep1View extends TradeStepView { public void activate() { super.activate(); - try { - DelayedPayoutTxValidation.validatePayoutTx(trade, - trade.getDelayedPayoutTx(), - model.dataModel.daoFacade, - model.dataModel.btcWalletService); - } catch (DelayedPayoutTxValidation.MissingTxException ignore) { - // We don't react on those errors as a failed trade might get listed initially but getting removed from the - // trade manager after initPendingTrades which happens after activate might be called. - } catch (DelayedPayoutTxValidation.ValidationException e) { - if (!model.dataModel.tradeManager.isAllowFaultyDelayedTxs()) { - new Popup().warning(Res.get("portfolio.pending.invalidDelayedPayoutTx", e.getMessage())).show(); - } + // We need to have the trades initialized before we can call validatePayoutTx. + BooleanProperty pendingTradesInitialized = model.dataModel.tradeManager.getPendingTradesInitialized(); + if (pendingTradesInitialized.get()) { + validatePayoutTx(); + } else { + pendingTradesInitializedListener = (observable, oldValue, newValue) -> { + if (newValue) { + validatePayoutTx(); + UserThread.execute(() -> pendingTradesInitialized.removeListener(pendingTradesInitializedListener)); + } + }; + pendingTradesInitialized.addListener(pendingTradesInitializedListener); } } @@ -85,6 +91,29 @@ public class BuyerStep1View extends TradeStepView { protected String getPeriodOverWarnText() { return Res.get("portfolio.pending.step1.openForDispute"); } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Private + /////////////////////////////////////////////////////////////////////////////////////////// + + private void validatePayoutTx() { + try { + DelayedPayoutTxValidation.validatePayoutTx(trade, + trade.getDelayedPayoutTx(), + model.dataModel.daoFacade, + model.dataModel.btcWalletService); + } catch (DelayedPayoutTxValidation.MissingTxException ignore) { + // We don't react on those errors as a failed trade might get listed initially but getting removed from the + // trade manager after initPendingTrades which happens after activate might be called. + log.error(""); + } catch (DelayedPayoutTxValidation.ValidationException e) { + if (!model.dataModel.tradeManager.isAllowFaultyDelayedTxs()) { + new Popup().warning(Res.get("portfolio.pending.invalidDelayedPayoutTx", e.getMessage())).show(); + } + } + } + } 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 9011390f59..6096e0cf3d 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 @@ -88,6 +88,9 @@ import javafx.scene.layout.Priority; import org.fxmisc.easybind.EasyBind; import org.fxmisc.easybind.Subscription; +import javafx.beans.property.BooleanProperty; +import javafx.beans.value.ChangeListener; + import java.util.List; import java.util.concurrent.TimeUnit; @@ -104,6 +107,7 @@ public class BuyerStep2View extends TradeStepView { private BusyAnimation busyAnimation; private Subscription tradeStatePropertySubscription; private Timer timeoutTimer; + private ChangeListener pendingTradesInitializedListener; /////////////////////////////////////////////////////////////////////////////////////////// // Constructor, Initialisation @@ -117,18 +121,18 @@ public class BuyerStep2View extends TradeStepView { public void activate() { super.activate(); - try { - DelayedPayoutTxValidation.validatePayoutTx(trade, - trade.getDelayedPayoutTx(), - model.dataModel.daoFacade, - model.dataModel.btcWalletService); - } catch (DelayedPayoutTxValidation.MissingTxException ignore) { - // We don't react on those errors as a failed trade might get listed initially but getting removed from the - // trade manager after initPendingTrades which happens after activate might be called. - } catch (DelayedPayoutTxValidation.ValidationException e) { - if (!model.dataModel.tradeManager.isAllowFaultyDelayedTxs()) { - new Popup().warning(Res.get("portfolio.pending.invalidDelayedPayoutTx", e.getMessage())).show(); - } + // We need to have the trades initialized before we can call validatePayoutTx. + BooleanProperty pendingTradesInitialized = model.dataModel.tradeManager.getPendingTradesInitialized(); + if (pendingTradesInitialized.get()) { + validatePayoutTx(); + } else { + pendingTradesInitializedListener = (observable, oldValue, newValue) -> { + if (newValue) { + validatePayoutTx(); + UserThread.execute(() -> pendingTradesInitialized.removeListener(pendingTradesInitializedListener)); + } + }; + pendingTradesInitialized.addListener(pendingTradesInitializedListener); } if (timeoutTimer != null) @@ -629,6 +633,23 @@ public class BuyerStep2View extends TradeStepView { } } + private void validatePayoutTx() { + try { + DelayedPayoutTxValidation.validatePayoutTx(trade, + trade.getDelayedPayoutTx(), + model.dataModel.daoFacade, + model.dataModel.btcWalletService); + } catch (DelayedPayoutTxValidation.MissingTxException ignore) { + // We don't react on those errors as a failed trade might get listed initially but getting removed from the + // trade manager after initPendingTrades which happens after activate might be called. + log.error(""); + } catch (DelayedPayoutTxValidation.ValidationException e) { + if (!model.dataModel.tradeManager.isAllowFaultyDelayedTxs()) { + new Popup().warning(Res.get("portfolio.pending.invalidDelayedPayoutTx", e.getMessage())).show(); + } + } + } + @Override protected void updateConfirmButtonDisableState(boolean isDisabled) { confirmButton.setDisable(isDisabled); From 7ac6e715d361450aa40d9cc233b95f769e1f0b11 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sat, 12 Sep 2020 00:35:58 -0500 Subject: [PATCH 08/31] Dispute agent sign summary. Add tool for verification --- .../core/support/dispute/DisputeManager.java | 15 ++- .../arbitration/ArbitrationManager.java | 5 +- .../dispute/mediation/MediationManager.java | 6 +- .../support/dispute/refund/RefundManager.java | 6 +- .../resources/i18n/displayStrings.properties | 30 ++++- .../windows/DisputeSummaryWindow.java | 33 ++++-- .../VerifyDisputeResultSignatureWindow.java | 97 ++++++++++++++++ .../dispute/DisputeSummaryVerification.java | 107 ++++++++++++++++++ .../main/support/dispute/DisputeView.java | 20 +++- .../dispute/agent/DisputeAgentView.java | 6 + .../agent/arbitration/ArbitratorView.java | 6 + .../dispute/agent/mediation/MediatorView.java | 6 + .../dispute/agent/refund/RefundAgentView.java | 6 + .../dispute/client/DisputeClientView.java | 7 +- .../arbitration/ArbitrationClientView.java | 9 +- .../client/mediation/MediationClientView.java | 6 +- .../client/refund/RefundClientView.java | 9 +- 17 files changed, 334 insertions(+), 40 deletions(-) create mode 100644 desktop/src/main/java/bisq/desktop/main/overlays/windows/VerifyDisputeResultSignatureWindow.java create mode 100644 desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeSummaryVerification.java diff --git a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java index 3fc8538e15..5084653df5 100644 --- a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java +++ b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java @@ -48,6 +48,7 @@ import bisq.network.p2p.SendMailboxMessageListener; import bisq.common.UserThread; import bisq.common.app.Version; +import bisq.common.crypto.KeyRing; import bisq.common.crypto.PubKeyRing; import bisq.common.handlers.FaultHandler; import bisq.common.handlers.ResultHandler; @@ -63,6 +64,8 @@ import javafx.beans.property.IntegerProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import java.security.KeyPair; + import java.util.List; import java.util.Optional; import java.util.UUID; @@ -90,6 +93,8 @@ public abstract class DisputeManager disputesWithInvalidDonationAddress = FXCollections.observableArrayList(); + @Getter + private final KeyPair signatureKeyPair; /////////////////////////////////////////////////////////////////////////////////////////// @@ -104,7 +109,7 @@ public abstract class DisputeManager disputeListService, PriceFeedService priceFeedService) { super(p2PService, walletsSetup); @@ -115,7 +120,8 @@ public abstract class DisputeManager e.getTradeId().equals(tradeId)).findAny(); } - /////////////////////////////////////////////////////////////////////////////////////////// // Message handler /////////////////////////////////////////////////////////////////////////////////////////// @@ -646,7 +651,7 @@ public abstract class DisputeManager ClosedTradableManager closedTradableManager, OpenOfferManager openOfferManager, DaoFacade daoFacade, - PubKeyRing pubKeyRing, + KeyRing keyRing, MediationDisputeListService mediationDisputeListService, PriceFeedService priceFeedService) { super(p2PService, tradeWalletService, walletService, walletsSetup, tradeManager, closedTradableManager, - openOfferManager, daoFacade, pubKeyRing, mediationDisputeListService, priceFeedService); + openOfferManager, daoFacade, keyRing, mediationDisputeListService, priceFeedService); } /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java b/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java index f00aca5fbd..631218bcc0 100644 --- a/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java +++ b/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java @@ -45,7 +45,7 @@ import bisq.network.p2p.P2PService; import bisq.common.Timer; import bisq.common.UserThread; import bisq.common.app.Version; -import bisq.common.crypto.PubKeyRing; +import bisq.common.crypto.KeyRing; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -76,11 +76,11 @@ public final class RefundManager extends DisputeManager { ClosedTradableManager closedTradableManager, OpenOfferManager openOfferManager, DaoFacade daoFacade, - PubKeyRing pubKeyRing, + KeyRing keyRing, RefundDisputeListService refundDisputeListService, PriceFeedService priceFeedService) { super(p2PService, tradeWalletService, walletService, walletsSetup, tradeManager, closedTradableManager, - openOfferManager, daoFacade, pubKeyRing, refundDisputeListService, priceFeedService); + openOfferManager, daoFacade, keyRing, refundDisputeListService, priceFeedService); } /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index b72799295c..91ebd566f8 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -1007,11 +1007,24 @@ support.tab.legacyArbitration.support=Legacy Arbitration support.tab.ArbitratorsSupportTickets={0}'s tickets support.filter=Search disputes support.filter.prompt=Enter trade ID, date, onion address or account data + +support.sigCheck.button=Verify result +support.sigCheck.popup.info=In case of a reimbursement request to the DAO you need to paste the summary message of the \ + mediation and arbitration process in your reimbursement request on Github. To make this statement verifiable any user can \ + check with this tool if the signature of the mediator or arbitrator matches the summary message. +support.sigCheck.popup.header=Verify dispute result signature +support.sigCheck.popup.msg.label=Summary message +support.sigCheck.popup.msg.prompt=Copy & paste summary message from dispute +support.sigCheck.popup.result=Validation result +support.sigCheck.popup.success=Signature is valid for given message +support.sigCheck.popup.failed=Signature verification failed +support.sigCheck.popup.invalidFormat=Message is not of expected format. Copy & paste summary message from dispute. + support.reOpenByTrader.prompt=Are you sure you want to re-open the dispute? -support.reOpenButton.label=Re-open dispute -support.sendNotificationButton.label=Send private notification -support.reportButton.label=Generate report -support.fullReportButton.label=Get text dump of all disputes +support.reOpenButton.label=Re-open +support.sendNotificationButton.label=Private notification +support.reportButton.label=Report +support.fullReportButton.label=All disputes support.noTickets=There are no open tickets support.sendingMessage=Sending Message... support.receiverNotOnline=Receiver is not online. Message is saved to their mailbox. @@ -2452,9 +2465,14 @@ Payout amount for BTC buyer: {1}\n\ Payout amount for BTC seller: {2}\n\n\ Reason for dispute: {3}\n\n\ Summary notes:\n{4} -disputeSummaryWindow.close.nextStepsForMediation=\n\nNext steps:\n\ + +disputeSummaryWindow.close.msgWithSigAndPubKey={0}{1}\ + Signer node address: {2}\n\ + Signature: {3}{4} + +disputeSummaryWindow.close.nextStepsForMediation=\nNext steps:\n\ Open trade and accept or reject suggestion from mediator -disputeSummaryWindow.close.nextStepsForRefundAgentArbitration=\n\nNext steps:\n\ +disputeSummaryWindow.close.nextStepsForRefundAgentArbitration=\nNext steps:\n\ No further action is required from you. If the arbitrator decided in your favor, you'll see a "Refund from arbitration" transaction in Funds/Transactions disputeSummaryWindow.close.closePeer=You need to close also the trading peers ticket! disputeSummaryWindow.close.txDetails.headline=Publish refund transaction diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java index b374f457b2..80dc5905ba 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java @@ -23,6 +23,7 @@ import bisq.desktop.components.BisqTextArea; import bisq.desktop.components.InputTextField; import bisq.desktop.main.overlays.Overlay; import bisq.desktop.main.overlays.popups.Popup; +import bisq.desktop.main.support.dispute.DisputeSummaryVerification; import bisq.desktop.util.DisplayUtils; import bisq.desktop.util.Layout; @@ -88,8 +89,7 @@ import java.util.Date; import java.util.Optional; import java.util.concurrent.TimeUnit; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; import static bisq.desktop.util.FormBuilder.add2ButtonsWithBox; import static bisq.desktop.util.FormBuilder.addConfirmationLabelLabel; @@ -97,8 +97,9 @@ import static bisq.desktop.util.FormBuilder.addTitledGroupBg; import static bisq.desktop.util.FormBuilder.addTopLabelWithVBox; import static com.google.common.base.Preconditions.checkNotNull; +@Slf4j public class DisputeSummaryWindow extends Overlay { - private static final Logger log = LoggerFactory.getLogger(DisputeSummaryWindow.class); + private final CoinFormatter formatter; private final MediationManager mediationManager; @@ -790,12 +791,19 @@ public class DisputeSummaryWindow extends Overlay { } private void doClose(Button closeTicketButton) { + DisputeManager> disputeManager = getDisputeManager(dispute); + if (disputeManager == null) { + return; + } + + boolean isRefundAgent = disputeManager instanceof RefundManager; disputeResult.setLoserPublisher(isLoserPublisherCheckBox.isSelected()); disputeResult.setCloseDate(new Date()); dispute.setDisputeResult(disputeResult); dispute.setIsClosed(true); DisputeResult.Reason reason = disputeResult.getReason(); - String text = Res.get("disputeSummaryWindow.close.msg", + + String textToSign = Res.get("disputeSummaryWindow.close.msg", DisplayUtils.formatDateTime(disputeResult.getCloseDate()), formatter.formatCoinWithCode(disputeResult.getBuyerPayoutAmount()), formatter.formatCoinWithCode(disputeResult.getSellerPayoutAmount()), @@ -805,16 +813,20 @@ public class DisputeSummaryWindow extends Overlay { if (reason == DisputeResult.Reason.OPTION_TRADE && dispute.getChatMessages().size() > 1 && dispute.getChatMessages().get(1).isSystemMessage()) { - text += "\n\n" + dispute.getChatMessages().get(1).getMessage(); + textToSign += "\n\n" + dispute.getChatMessages().get(1).getMessage(); } - if (dispute.getSupportType() == SupportType.MEDIATION) { - text += Res.get("disputeSummaryWindow.close.nextStepsForMediation"); - } else if (dispute.getSupportType() == SupportType.REFUND) { - text += Res.get("disputeSummaryWindow.close.nextStepsForRefundAgentArbitration"); + summaryNotesTextArea.textProperty().unbindBidirectional(disputeResult.summaryNotesProperty()); + + String summaryText = DisputeSummaryVerification.signAndApply(disputeManager, dispute, disputeResult, textToSign); + + if (isRefundAgent) { + summaryText += Res.get("disputeSummaryWindow.close.nextStepsForRefundAgentArbitration"); + } else { + summaryText += Res.get("disputeSummaryWindow.close.nextStepsForMediation"); } - checkNotNull(getDisputeManager(dispute)).sendDisputeResultMessage(disputeResult, dispute, text); + disputeManager.sendDisputeResultMessage(disputeResult, dispute, summaryText); if (peersDisputeOptional.isPresent() && !peersDisputeOptional.get().isClosed() && !DevEnv.isDevMode()) { UserThread.runAfter(() -> new Popup() @@ -824,7 +836,6 @@ public class DisputeSummaryWindow extends Overlay { } finalizeDisputeHandlerOptional.ifPresent(Runnable::run); - closeTicketButton.disableProperty().unbind(); hide(); diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/VerifyDisputeResultSignatureWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/VerifyDisputeResultSignatureWindow.java new file mode 100644 index 0000000000..c5b25c4e46 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/VerifyDisputeResultSignatureWindow.java @@ -0,0 +1,97 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.desktop.main.overlays.windows; + +import bisq.desktop.main.overlays.Overlay; +import bisq.desktop.main.support.dispute.DisputeSummaryVerification; + +import bisq.core.locale.Res; +import bisq.core.support.dispute.mediation.mediator.MediatorManager; +import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; + +import javafx.scene.control.Label; +import javafx.scene.control.TextArea; +import javafx.scene.control.TextField; +import javafx.scene.layout.ColumnConstraints; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Priority; + +import javafx.geometry.HPos; +import javafx.geometry.Insets; + +import lombok.extern.slf4j.Slf4j; + +import static bisq.desktop.util.FormBuilder.addMultilineLabel; +import static bisq.desktop.util.FormBuilder.addTopLabelTextArea; +import static bisq.desktop.util.FormBuilder.addTopLabelTextField; + +@Slf4j +public class VerifyDisputeResultSignatureWindow extends Overlay { + private TextArea textArea; + private TextField resultTextField; + private final MediatorManager mediatorManager; + private final RefundAgentManager refundAgentManager; + + public VerifyDisputeResultSignatureWindow(MediatorManager mediatorManager, RefundAgentManager refundAgentManager) { + this.mediatorManager = mediatorManager; + this.refundAgentManager = refundAgentManager; + + type = Type.Attention; + } + + public void show() { + if (headLine == null) + headLine = Res.get("support.sigCheck.popup.header"); + + width = 1000; + createGridPane(); + addHeadLine(); + addContent(); + addButtons(); + + applyStyles(); + display(); + + textArea.textProperty().addListener((observable, oldValue, newValue) -> { + resultTextField.setText(DisputeSummaryVerification.verifySignature(newValue, + mediatorManager, + refundAgentManager)); + }); + } + + @Override + protected void createGridPane() { + gridPane = new GridPane(); + gridPane.setHgap(5); + gridPane.setVgap(5); + gridPane.setPadding(new Insets(64, 64, 64, 64)); + gridPane.setPrefWidth(width); + + ColumnConstraints columnConstraints1 = new ColumnConstraints(); + columnConstraints1.setHalignment(HPos.RIGHT); + columnConstraints1.setHgrow(Priority.SOMETIMES); + gridPane.getColumnConstraints().addAll(columnConstraints1); + } + + private void addContent() { + Label label = addMultilineLabel(gridPane, ++rowIndex, Res.get("support.sigCheck.popup.info"), 0, width); + textArea = addTopLabelTextArea(gridPane, ++rowIndex, Res.get("support.sigCheck.popup.msg.label"), + Res.get("support.sigCheck.popup.msg.prompt")).second; + resultTextField = addTopLabelTextField(gridPane, ++rowIndex, Res.get("support.sigCheck.popup.result")).second; + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeSummaryVerification.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeSummaryVerification.java new file mode 100644 index 0000000000..9be98cfc9f --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeSummaryVerification.java @@ -0,0 +1,107 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.desktop.main.support.dispute; + +import bisq.core.locale.Res; +import bisq.core.support.dispute.Dispute; +import bisq.core.support.dispute.DisputeList; +import bisq.core.support.dispute.DisputeManager; +import bisq.core.support.dispute.DisputeResult; +import bisq.core.support.dispute.agent.DisputeAgent; +import bisq.core.support.dispute.mediation.mediator.MediatorManager; +import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; + +import bisq.network.p2p.NodeAddress; + +import bisq.common.crypto.CryptoException; +import bisq.common.crypto.Hash; +import bisq.common.crypto.Sig; +import bisq.common.util.Utilities; + +import java.security.KeyPair; +import java.security.PublicKey; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class DisputeSummaryVerification { + // Must not change as it is used for splitting the text for verifying the signature of the summary message + private static final String SEPARATOR = "\n------------------------------------------------------------------------------------------\n"; + + public static String signAndApply(DisputeManager> disputeManager, + Dispute dispute, + DisputeResult disputeResult, + String textToSign) { + byte[] hash = Hash.getSha256Hash(textToSign); + KeyPair signatureKeyPair = disputeManager.getSignatureKeyPair(); + String sigAsHex; + try { + byte[] signature = Sig.sign(signatureKeyPair.getPrivate(), hash); + sigAsHex = Utilities.encodeToHex(signature); + disputeResult.setArbitratorSignature(signature); + } catch (CryptoException e) { + sigAsHex = "Signing failed"; + } + + disputeResult.setArbitratorPubKey(dispute.getAgentPubKeyRing().getSignaturePubKeyBytes()); + NodeAddress agentNodeAddress = checkNotNull(disputeManager.getAgentNodeAddress(dispute)); + return Res.get("disputeSummaryWindow.close.msgWithSigAndPubKey", + textToSign, + SEPARATOR, + agentNodeAddress.getFullAddress(), + sigAsHex, + SEPARATOR); + } + + public static String verifySignature(String input, + MediatorManager mediatorManager, + RefundAgentManager refundAgentManager) { + try { + String[] tokens = input.split(SEPARATOR); + String textToSign = tokens[0]; + String data = tokens[1]; + String[] dataTokens = data.split("\n"); + String fullAddress = dataTokens[0].split(": ")[1]; + + NodeAddress nodeAddress = new NodeAddress(fullAddress); + DisputeAgent disputeAgent = mediatorManager.getDisputeAgentByNodeAddress(nodeAddress).orElse(null); + if (disputeAgent == null) { + disputeAgent = refundAgentManager.getDisputeAgentByNodeAddress(nodeAddress).orElse(null); + } + checkNotNull(disputeAgent); + PublicKey pubKey = disputeAgent.getPubKeyRing().getSignaturePubKey(); + + String sigString = dataTokens[1].split(": ")[1]; + byte[] sig = Utilities.decodeFromHex(sigString); + + byte[] hash = Hash.getSha256Hash(textToSign); + + try { + boolean result = Sig.verify(pubKey, hash, sig); + if (result) { + return Res.get("support.sigCheck.popup.success"); + } else { + return Res.get("support.sigCheck.popup.failed"); + } + } catch (CryptoException e) { + return Res.get("support.sigCheck.popup.failed"); + } + } catch (Throwable e) { + return Res.get("support.sigCheck.popup.invalidFormat"); + } + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java index fcba3c3b65..d140a9abfc 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java @@ -28,6 +28,7 @@ import bisq.desktop.main.overlays.windows.ContractWindow; import bisq.desktop.main.overlays.windows.DisputeSummaryWindow; import bisq.desktop.main.overlays.windows.SendPrivateNotificationWindow; import bisq.desktop.main.overlays.windows.TradeDetailsWindow; +import bisq.desktop.main.overlays.windows.VerifyDisputeResultSignatureWindow; import bisq.desktop.main.shared.ChatView; import bisq.desktop.util.DisplayUtils; import bisq.desktop.util.GUIUtil; @@ -42,6 +43,8 @@ import bisq.core.support.dispute.DisputeList; import bisq.core.support.dispute.DisputeManager; import bisq.core.support.dispute.DisputeResult; import bisq.core.support.dispute.DisputeSession; +import bisq.core.support.dispute.mediation.mediator.MediatorManager; +import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; import bisq.core.support.messages.ChatMessage; import bisq.core.trade.Contract; import bisq.core.trade.Trade; @@ -121,6 +124,8 @@ public abstract class DisputeView extends ActivatableView { private final TradeDetailsWindow tradeDetailsWindow; private final AccountAgeWitnessService accountAgeWitnessService; + private final MediatorManager mediatorManager; + private final RefundAgentManager refundAgentManager; private final boolean useDevPrivilegeKeys; protected TableView tableView; @@ -136,7 +141,7 @@ public abstract class DisputeView extends ActivatableView { protected FilteredList filteredList; protected InputTextField filterTextField; private ChangeListener filterTextFieldListener; - protected AutoTooltipButton reOpenButton, sendPrivateNotificationButton, reportButton, fullReportButton; + protected AutoTooltipButton sigCheckButton, reOpenButton, sendPrivateNotificationButton, reportButton, fullReportButton; private Map> disputeChatMessagesListeners = new HashMap<>(); @Nullable private ListChangeListener disputesListener; // Only set in mediation cases @@ -157,6 +162,8 @@ public abstract class DisputeView extends ActivatableView { ContractWindow contractWindow, TradeDetailsWindow tradeDetailsWindow, AccountAgeWitnessService accountAgeWitnessService, + MediatorManager mediatorManager, + RefundAgentManager refundAgentManager, boolean useDevPrivilegeKeys) { this.disputeManager = disputeManager; this.keyRing = keyRing; @@ -167,6 +174,8 @@ public abstract class DisputeView extends ActivatableView { this.contractWindow = contractWindow; this.tradeDetailsWindow = tradeDetailsWindow; this.accountAgeWitnessService = accountAgeWitnessService; + this.mediatorManager = mediatorManager; + this.refundAgentManager = refundAgentManager; this.useDevPrivilegeKeys = useDevPrivilegeKeys; } @@ -222,6 +231,12 @@ public abstract class DisputeView extends ActivatableView { showFullReport(); }); + sigCheckButton = new AutoTooltipButton(Res.get("support.sigCheck.button")); + HBox.setHgrow(sigCheckButton, Priority.NEVER); + sigCheckButton.setOnAction(e -> { + new VerifyDisputeResultSignatureWindow(mediatorManager, refundAgentManager).show(); + }); + Pane spacer = new Pane(); HBox.setHgrow(spacer, Priority.ALWAYS); @@ -234,7 +249,8 @@ public abstract class DisputeView extends ActivatableView { reOpenButton, sendPrivateNotificationButton, reportButton, - fullReportButton); + fullReportButton, + sigCheckButton); VBox.setVgrow(filterBox, Priority.NEVER); tableView = new TableView<>(); diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java index f7d059b110..8565c0cab4 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java @@ -34,6 +34,8 @@ import bisq.core.support.dispute.DisputeList; import bisq.core.support.dispute.DisputeManager; import bisq.core.support.dispute.DisputeSession; import bisq.core.support.dispute.agent.MultipleHolderNameDetection; +import bisq.core.support.dispute.mediation.mediator.MediatorManager; +import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; import bisq.core.trade.TradeManager; import bisq.core.user.DontShowAgainLookup; import bisq.core.util.coin.CoinFormatter; @@ -77,6 +79,8 @@ public abstract class DisputeAgentView extends DisputeView implements MultipleHo TradeDetailsWindow tradeDetailsWindow, AccountAgeWitnessService accountAgeWitnessService, DaoFacade daoFacade, + MediatorManager mediatorManager, + RefundAgentManager refundAgentManager, boolean useDevPrivilegeKeys) { super(disputeManager, keyRing, @@ -87,6 +91,8 @@ public abstract class DisputeAgentView extends DisputeView implements MultipleHo contractWindow, tradeDetailsWindow, accountAgeWitnessService, + mediatorManager, + refundAgentManager, useDevPrivilegeKeys); multipleHolderNameDetection = new MultipleHolderNameDetection(disputeManager); diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/arbitration/ArbitratorView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/arbitration/ArbitratorView.java index 7c2278de28..7c8c40b141 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/arbitration/ArbitratorView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/arbitration/ArbitratorView.java @@ -31,6 +31,8 @@ import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.DisputeSession; import bisq.core.support.dispute.arbitration.ArbitrationManager; import bisq.core.support.dispute.arbitration.ArbitrationSession; +import bisq.core.support.dispute.mediation.mediator.MediatorManager; +import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; import bisq.core.trade.TradeManager; import bisq.core.util.FormattingUtils; import bisq.core.util.coin.CoinFormatter; @@ -55,6 +57,8 @@ public class ArbitratorView extends DisputeAgentView { TradeDetailsWindow tradeDetailsWindow, AccountAgeWitnessService accountAgeWitnessService, DaoFacade daoFacade, + MediatorManager mediatorManager, + RefundAgentManager refundAgentManager, @Named(Config.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) { super(arbitrationManager, keyRing, @@ -66,6 +70,8 @@ public class ArbitratorView extends DisputeAgentView { tradeDetailsWindow, accountAgeWitnessService, daoFacade, + mediatorManager, + refundAgentManager, useDevPrivilegeKeys); } diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/mediation/MediatorView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/mediation/MediatorView.java index d96fb9e8f2..4dacc16ed2 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/mediation/MediatorView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/mediation/MediatorView.java @@ -31,6 +31,8 @@ import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.DisputeSession; import bisq.core.support.dispute.mediation.MediationManager; import bisq.core.support.dispute.mediation.MediationSession; +import bisq.core.support.dispute.mediation.mediator.MediatorManager; +import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; import bisq.core.trade.TradeManager; import bisq.core.util.FormattingUtils; import bisq.core.util.coin.CoinFormatter; @@ -55,6 +57,8 @@ public class MediatorView extends DisputeAgentView { TradeDetailsWindow tradeDetailsWindow, AccountAgeWitnessService accountAgeWitnessService, DaoFacade daoFacade, + MediatorManager mediatorManager, + RefundAgentManager refundAgentManager, @Named(Config.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) { super(mediationManager, keyRing, @@ -66,6 +70,8 @@ public class MediatorView extends DisputeAgentView { tradeDetailsWindow, accountAgeWitnessService, daoFacade, + mediatorManager, + refundAgentManager, useDevPrivilegeKeys); } diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/refund/RefundAgentView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/refund/RefundAgentView.java index 71f27f8954..797e4849bd 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/refund/RefundAgentView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/refund/RefundAgentView.java @@ -29,8 +29,10 @@ import bisq.core.dao.DaoFacade; import bisq.core.support.SupportType; import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.DisputeSession; +import bisq.core.support.dispute.mediation.mediator.MediatorManager; import bisq.core.support.dispute.refund.RefundManager; import bisq.core.support.dispute.refund.RefundSession; +import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; import bisq.core.trade.TradeManager; import bisq.core.util.FormattingUtils; import bisq.core.util.coin.CoinFormatter; @@ -55,6 +57,8 @@ public class RefundAgentView extends DisputeAgentView { TradeDetailsWindow tradeDetailsWindow, AccountAgeWitnessService accountAgeWitnessService, DaoFacade daoFacade, + MediatorManager mediatorManager, + RefundAgentManager refundAgentManager, @Named(Config.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) { super(refundManager, keyRing, @@ -66,6 +70,8 @@ public class RefundAgentView extends DisputeAgentView { tradeDetailsWindow, accountAgeWitnessService, daoFacade, + mediatorManager, + refundAgentManager, useDevPrivilegeKeys); } diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/client/DisputeClientView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/client/DisputeClientView.java index f06875202e..76945d5511 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/client/DisputeClientView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/client/DisputeClientView.java @@ -28,6 +28,8 @@ import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.DisputeList; import bisq.core.support.dispute.DisputeManager; import bisq.core.support.dispute.DisputeSession; +import bisq.core.support.dispute.mediation.mediator.MediatorManager; +import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; import bisq.core.trade.TradeManager; import bisq.core.util.coin.CoinFormatter; @@ -43,9 +45,12 @@ public abstract class DisputeClientView extends DisputeView { ContractWindow contractWindow, TradeDetailsWindow tradeDetailsWindow, AccountAgeWitnessService accountAgeWitnessService, + MediatorManager mediatorManager, + RefundAgentManager refundAgentManager, boolean useDevPrivilegeKeys) { super(DisputeManager, keyRing, tradeManager, formatter, disputeSummaryWindow, privateNotificationManager, - contractWindow, tradeDetailsWindow, accountAgeWitnessService, useDevPrivilegeKeys); + contractWindow, tradeDetailsWindow, accountAgeWitnessService, + mediatorManager, refundAgentManager, useDevPrivilegeKeys); } @Override diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/client/arbitration/ArbitrationClientView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/client/arbitration/ArbitrationClientView.java index 0ea4e14435..bd9d0dbd7b 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/client/arbitration/ArbitrationClientView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/client/arbitration/ArbitrationClientView.java @@ -30,6 +30,8 @@ import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.DisputeSession; import bisq.core.support.dispute.arbitration.ArbitrationManager; import bisq.core.support.dispute.arbitration.ArbitrationSession; +import bisq.core.support.dispute.mediation.mediator.MediatorManager; +import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; import bisq.core.trade.TradeManager; import bisq.core.util.FormattingUtils; import bisq.core.util.coin.CoinFormatter; @@ -37,9 +39,8 @@ import bisq.core.util.coin.CoinFormatter; import bisq.common.config.Config; import bisq.common.crypto.KeyRing; -import javax.inject.Named; - import javax.inject.Inject; +import javax.inject.Named; @FxmlView public class ArbitrationClientView extends DisputeClientView { @@ -53,10 +54,12 @@ public class ArbitrationClientView extends DisputeClientView { ContractWindow contractWindow, TradeDetailsWindow tradeDetailsWindow, AccountAgeWitnessService accountAgeWitnessService, + MediatorManager mediatorManager, + RefundAgentManager refundAgentManager, @Named(Config.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) { super(arbitrationManager, keyRing, tradeManager, formatter, disputeSummaryWindow, privateNotificationManager, contractWindow, tradeDetailsWindow, accountAgeWitnessService, - useDevPrivilegeKeys); + mediatorManager, refundAgentManager, useDevPrivilegeKeys); } @Override diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/client/mediation/MediationClientView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/client/mediation/MediationClientView.java index eb60178e5f..520c5a5237 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/client/mediation/MediationClientView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/client/mediation/MediationClientView.java @@ -32,6 +32,8 @@ import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.DisputeSession; import bisq.core.support.dispute.mediation.MediationManager; import bisq.core.support.dispute.mediation.MediationSession; +import bisq.core.support.dispute.mediation.mediator.MediatorManager; +import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; import bisq.core.trade.TradeManager; import bisq.core.util.FormattingUtils; import bisq.core.util.coin.CoinFormatter; @@ -54,10 +56,12 @@ public class MediationClientView extends DisputeClientView { ContractWindow contractWindow, TradeDetailsWindow tradeDetailsWindow, AccountAgeWitnessService accountAgeWitnessService, + MediatorManager mediatorManager, + RefundAgentManager refundAgentManager, @Named(Config.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) { super(mediationManager, keyRing, tradeManager, formatter, disputeSummaryWindow, privateNotificationManager, contractWindow, tradeDetailsWindow, accountAgeWitnessService, - useDevPrivilegeKeys); + mediatorManager, refundAgentManager, useDevPrivilegeKeys); } @Override diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/client/refund/RefundClientView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/client/refund/RefundClientView.java index 4f7b48d4fe..a196b67ef8 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/client/refund/RefundClientView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/client/refund/RefundClientView.java @@ -28,8 +28,10 @@ import bisq.core.alert.PrivateNotificationManager; import bisq.core.support.SupportType; import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.DisputeSession; +import bisq.core.support.dispute.mediation.mediator.MediatorManager; import bisq.core.support.dispute.refund.RefundManager; import bisq.core.support.dispute.refund.RefundSession; +import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; import bisq.core.trade.TradeManager; import bisq.core.util.FormattingUtils; import bisq.core.util.coin.CoinFormatter; @@ -37,9 +39,8 @@ import bisq.core.util.coin.CoinFormatter; import bisq.common.config.Config; import bisq.common.crypto.KeyRing; -import javax.inject.Named; - import javax.inject.Inject; +import javax.inject.Named; @FxmlView public class RefundClientView extends DisputeClientView { @@ -53,10 +54,12 @@ public class RefundClientView extends DisputeClientView { ContractWindow contractWindow, TradeDetailsWindow tradeDetailsWindow, AccountAgeWitnessService accountAgeWitnessService, + MediatorManager mediatorManager, + RefundAgentManager refundAgentManager, @Named(Config.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) { super(refundManager, keyRing, tradeManager, formatter, disputeSummaryWindow, privateNotificationManager, contractWindow, tradeDetailsWindow, accountAgeWitnessService, - useDevPrivilegeKeys); + mediatorManager, refundAgentManager, useDevPrivilegeKeys); } @Override From 559028e5006a14c3e791bcc91a03b0a8992079b5 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sat, 12 Sep 2020 00:46:52 -0500 Subject: [PATCH 09/31] Remove unused var --- .../overlays/windows/VerifyDisputeResultSignatureWindow.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/VerifyDisputeResultSignatureWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/VerifyDisputeResultSignatureWindow.java index c5b25c4e46..f6bfe61ce4 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/VerifyDisputeResultSignatureWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/VerifyDisputeResultSignatureWindow.java @@ -24,7 +24,6 @@ import bisq.core.locale.Res; import bisq.core.support.dispute.mediation.mediator.MediatorManager; import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; -import javafx.scene.control.Label; import javafx.scene.control.TextArea; import javafx.scene.control.TextField; import javafx.scene.layout.ColumnConstraints; @@ -89,7 +88,7 @@ public class VerifyDisputeResultSignatureWindow extends Overlay Date: Sat, 12 Sep 2020 00:49:27 -0500 Subject: [PATCH 10/31] Remove setting of pubKey as it is not needed --- .../desktop/main/support/dispute/DisputeSummaryVerification.java | 1 - 1 file changed, 1 deletion(-) diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeSummaryVerification.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeSummaryVerification.java index 9be98cfc9f..4ca2be2bac 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeSummaryVerification.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeSummaryVerification.java @@ -57,7 +57,6 @@ public class DisputeSummaryVerification { sigAsHex = "Signing failed"; } - disputeResult.setArbitratorPubKey(dispute.getAgentPubKeyRing().getSignaturePubKeyBytes()); NodeAddress agentNodeAddress = checkNotNull(disputeManager.getAgentNodeAddress(dispute)); return Res.get("disputeSummaryWindow.close.msgWithSigAndPubKey", textToSign, From 0c46e7dd529603bd11af5214a3577b8080d74fd0 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sat, 12 Sep 2020 16:19:42 -0500 Subject: [PATCH 11/31] Add more data to summary msg --- .../resources/i18n/displayStrings.properties | 15 ++++++----- .../windows/DisputeSummaryWindow.java | 25 +++++++++++++++++++ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 91ebd566f8..a1c72a3399 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -2459,12 +2459,15 @@ disputeSummaryWindow.reason.TRADE_ALREADY_SETTLED=Trade already settled disputeSummaryWindow.summaryNotes=Summary notes disputeSummaryWindow.addSummaryNotes=Add summary notes disputeSummaryWindow.close.button=Close ticket -disputeSummaryWindow.close.msg=Ticket closed on {0}\n\n\ -Summary:\n\ -Payout amount for BTC buyer: {1}\n\ -Payout amount for BTC seller: {2}\n\n\ -Reason for dispute: {3}\n\n\ -Summary notes:\n{4} +disputeSummaryWindow.close.msg=Ticket for trade {0} closed on {1}\n\ + {2} node address: {3}\n\n\ + Summary:\n\ + Traded currency: {4}\n\ + Trade amount: {5}\n\ + Payout amount for BTC buyer: {6}\n\ + Payout amount for BTC seller: {7}\n\n\ + Reason for dispute: {8}\n\n\ + Summary notes:\n{9} disputeSummaryWindow.close.msgWithSigAndPubKey={0}{1}\ Signer node address: {2}\n\ diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java index 80dc5905ba..eb1f467611 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java @@ -803,8 +803,33 @@ public class DisputeSummaryWindow extends Overlay { dispute.setIsClosed(true); DisputeResult.Reason reason = disputeResult.getReason(); + /* disputeSummaryWindow.close.msg=Ticket for trade {0} closed on {1}\n\n\ + {2} node address: {3}\n\ + Summary:\n\ + Payout amount for BTC buyer: {4}\n\ + Payout amount for BTC seller: {5}\n\n\ + Reason for dispute: {6}\n\n\ + Summary notes:\n{7} + .append("Currency: ") + .append(CurrencyUtil.getNameAndCode(contract.getOfferPayload().getCurrencyCode())) + .append("\n") + .append("Trade amount: ") + .append(contract.getTradeAmount().toFriendlyString()) + + + */ + String role = isRefundAgent ? Res.get("shared.refundAgent") : Res.get("shared.mediator"); + String agentNodeAddress = checkNotNull(disputeManager.getAgentNodeAddress(dispute)).getFullAddress(); + Contract contract = dispute.getContract(); + String currencyCode = contract.getOfferPayload().getCurrencyCode(); + String amount = formatter.formatCoinWithCode(contract.getTradeAmount()); String textToSign = Res.get("disputeSummaryWindow.close.msg", + dispute.getShortTradeId(), DisplayUtils.formatDateTime(disputeResult.getCloseDate()), + role, + agentNodeAddress, + currencyCode, + amount, formatter.formatCoinWithCode(disputeResult.getBuyerPayoutAmount()), formatter.formatCoinWithCode(disputeResult.getSellerPayoutAmount()), Res.get("disputeSummaryWindow.reason." + reason.name()), From de4fb17a19592d5c51910ba4ebe68d472a142dcb Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sat, 12 Sep 2020 20:18:04 -0500 Subject: [PATCH 12/31] Improve summary notes --- .../resources/i18n/displayStrings.properties | 18 ++++++----- .../windows/DisputeSummaryWindow.java | 29 +++++------------- .../VerifyDisputeResultSignatureWindow.java | 2 +- .../dispute/DisputeSummaryVerification.java | 30 ++++++++----------- 4 files changed, 31 insertions(+), 48 deletions(-) diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index a1c72a3399..bd2c6183ae 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -1016,7 +1016,7 @@ support.sigCheck.popup.header=Verify dispute result signature support.sigCheck.popup.msg.label=Summary message support.sigCheck.popup.msg.prompt=Copy & paste summary message from dispute support.sigCheck.popup.result=Validation result -support.sigCheck.popup.success=Signature is valid for given message +support.sigCheck.popup.success=Signature is valid support.sigCheck.popup.failed=Signature verification failed support.sigCheck.popup.invalidFormat=Message is not of expected format. Copy & paste summary message from dispute. @@ -2459,19 +2459,21 @@ disputeSummaryWindow.reason.TRADE_ALREADY_SETTLED=Trade already settled disputeSummaryWindow.summaryNotes=Summary notes disputeSummaryWindow.addSummaryNotes=Add summary notes disputeSummaryWindow.close.button=Close ticket -disputeSummaryWindow.close.msg=Ticket for trade {0} closed on {1}\n\ - {2} node address: {3}\n\n\ + +# Do no change any line break or order of tokens as the structure is used for signature verification +disputeSummaryWindow.close.msg=Ticket closed on {0}\n\ + {1} node address: {2}\n\n\ Summary:\n\ - Traded currency: {4}\n\ + Trade ID: {3}\n\ + Currency: {4}\n\ Trade amount: {5}\n\ Payout amount for BTC buyer: {6}\n\ Payout amount for BTC seller: {7}\n\n\ Reason for dispute: {8}\n\n\ - Summary notes:\n{9} + Summary notes:\n{9}\n -disputeSummaryWindow.close.msgWithSigAndPubKey={0}{1}\ - Signer node address: {2}\n\ - Signature: {3}{4} +# Do no change any line break or order of tokens as the structure is used for signature verification +disputeSummaryWindow.close.msgWithSig={0}{1}{2}{3} disputeSummaryWindow.close.nextStepsForMediation=\nNext steps:\n\ Open trade and accept or reject suggestion from mediator diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java index eb1f467611..4e6751e713 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java @@ -110,7 +110,7 @@ public class DisputeSummaryWindow extends Overlay { private final FeeService feeService; private final DaoFacade daoFacade; private Dispute dispute; - private Optional finalizeDisputeHandlerOptional = Optional.empty(); + private Optional finalizeDisputeHandlerOptional = Optional.empty(); private ToggleGroup tradeAmountToggleGroup, reasonToggleGroup; private DisputeResult disputeResult; private RadioButton buyerGetsTradeAmountRadioButton, sellerGetsTradeAmountRadioButton, @@ -228,7 +228,7 @@ public class DisputeSummaryWindow extends Overlay { else disputeResult = dispute.getDisputeResultProperty().get(); - peersDisputeOptional = getDisputeManager(dispute).getDisputesAsObservableList().stream() + peersDisputeOptional = checkNotNull(getDisputeManager(dispute)).getDisputesAsObservableList().stream() .filter(d -> dispute.getTradeId().equals(d.getTradeId()) && dispute.getTraderId() != d.getTraderId()) .findFirst(); @@ -803,37 +803,24 @@ public class DisputeSummaryWindow extends Overlay { dispute.setIsClosed(true); DisputeResult.Reason reason = disputeResult.getReason(); - /* disputeSummaryWindow.close.msg=Ticket for trade {0} closed on {1}\n\n\ - {2} node address: {3}\n\ - Summary:\n\ - Payout amount for BTC buyer: {4}\n\ - Payout amount for BTC seller: {5}\n\n\ - Reason for dispute: {6}\n\n\ - Summary notes:\n{7} - .append("Currency: ") - .append(CurrencyUtil.getNameAndCode(contract.getOfferPayload().getCurrencyCode())) - .append("\n") - .append("Trade amount: ") - .append(contract.getTradeAmount().toFriendlyString()) - - - */ + summaryNotesTextArea.textProperty().unbindBidirectional(disputeResult.summaryNotesProperty()); String role = isRefundAgent ? Res.get("shared.refundAgent") : Res.get("shared.mediator"); String agentNodeAddress = checkNotNull(disputeManager.getAgentNodeAddress(dispute)).getFullAddress(); Contract contract = dispute.getContract(); String currencyCode = contract.getOfferPayload().getCurrencyCode(); String amount = formatter.formatCoinWithCode(contract.getTradeAmount()); String textToSign = Res.get("disputeSummaryWindow.close.msg", - dispute.getShortTradeId(), DisplayUtils.formatDateTime(disputeResult.getCloseDate()), role, agentNodeAddress, + dispute.getShortTradeId(), currencyCode, amount, formatter.formatCoinWithCode(disputeResult.getBuyerPayoutAmount()), formatter.formatCoinWithCode(disputeResult.getSellerPayoutAmount()), Res.get("disputeSummaryWindow.reason." + reason.name()), - disputeResult.summaryNotesProperty().get()); + disputeResult.summaryNotesProperty().get() + ); if (reason == DisputeResult.Reason.OPTION_TRADE && dispute.getChatMessages().size() > 1 && @@ -841,9 +828,7 @@ public class DisputeSummaryWindow extends Overlay { textToSign += "\n\n" + dispute.getChatMessages().get(1).getMessage(); } - summaryNotesTextArea.textProperty().unbindBidirectional(disputeResult.summaryNotesProperty()); - - String summaryText = DisputeSummaryVerification.signAndApply(disputeManager, dispute, disputeResult, textToSign); + String summaryText = DisputeSummaryVerification.signAndApply(disputeManager, disputeResult, textToSign); if (isRefundAgent) { summaryText += Res.get("disputeSummaryWindow.close.nextStepsForRefundAgentArbitration"); diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/VerifyDisputeResultSignatureWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/VerifyDisputeResultSignatureWindow.java index f6bfe61ce4..9f27499545 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/VerifyDisputeResultSignatureWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/VerifyDisputeResultSignatureWindow.java @@ -57,7 +57,7 @@ public class VerifyDisputeResultSignatureWindow extends Overlay> disputeManager, - Dispute dispute, DisputeResult disputeResult, String textToSign) { + byte[] hash = Hash.getSha256Hash(textToSign); KeyPair signatureKeyPair = disputeManager.getSignatureKeyPair(); String sigAsHex; @@ -57,25 +60,20 @@ public class DisputeSummaryVerification { sigAsHex = "Signing failed"; } - NodeAddress agentNodeAddress = checkNotNull(disputeManager.getAgentNodeAddress(dispute)); - return Res.get("disputeSummaryWindow.close.msgWithSigAndPubKey", + return Res.get("disputeSummaryWindow.close.msgWithSig", textToSign, - SEPARATOR, - agentNodeAddress.getFullAddress(), + SEPARATOR1, sigAsHex, - SEPARATOR); + SEPARATOR2); } public static String verifySignature(String input, MediatorManager mediatorManager, RefundAgentManager refundAgentManager) { try { - String[] tokens = input.split(SEPARATOR); - String textToSign = tokens[0]; - String data = tokens[1]; - String[] dataTokens = data.split("\n"); - String fullAddress = dataTokens[0].split(": ")[1]; - + String[] parts = input.split(SEPARATOR1); + String textToSign = parts[0]; + String fullAddress = textToSign.split("\n")[1].split(": ")[1]; NodeAddress nodeAddress = new NodeAddress(fullAddress); DisputeAgent disputeAgent = mediatorManager.getDisputeAgentByNodeAddress(nodeAddress).orElse(null); if (disputeAgent == null) { @@ -84,11 +82,9 @@ public class DisputeSummaryVerification { checkNotNull(disputeAgent); PublicKey pubKey = disputeAgent.getPubKeyRing().getSignaturePubKey(); - String sigString = dataTokens[1].split(": ")[1]; + String sigString = parts[1].split(SEPARATOR2)[0]; byte[] sig = Utilities.decodeFromHex(sigString); - byte[] hash = Hash.getSha256Hash(textToSign); - try { boolean result = Sig.verify(pubKey, hash, sig); if (result) { From 966b22a69d4c9c02cb50ce705a1f6e25b3afc687 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sat, 12 Sep 2020 20:24:32 -0500 Subject: [PATCH 13/31] Fix line breaks --- .../desktop/main/overlays/windows/DisputeSummaryWindow.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java index 4e6751e713..edd7a54ef6 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java @@ -825,7 +825,7 @@ public class DisputeSummaryWindow extends Overlay { if (reason == DisputeResult.Reason.OPTION_TRADE && dispute.getChatMessages().size() > 1 && dispute.getChatMessages().get(1).isSystemMessage()) { - textToSign += "\n\n" + dispute.getChatMessages().get(1).getMessage(); + textToSign += "\n" + dispute.getChatMessages().get(1).getMessage() + "\n"; } String summaryText = DisputeSummaryVerification.signAndApply(disputeManager, disputeResult, textToSign); From 1c41db4a767e1d70aabe99c9af5d47bd0e3b6112 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Thu, 17 Sep 2020 13:46:00 -0500 Subject: [PATCH 14/31] Fix wrong handling of mainnet RECIPIENT_BTC_ADDRESSes We must not add main net addresses if not on mainnet --- core/src/main/java/bisq/core/dao/DaoFacade.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/bisq/core/dao/DaoFacade.java b/core/src/main/java/bisq/core/dao/DaoFacade.java index bfc542a862..2f20a802ba 100644 --- a/core/src/main/java/bisq/core/dao/DaoFacade.java +++ b/core/src/main/java/bisq/core/dao/DaoFacade.java @@ -73,6 +73,7 @@ import bisq.core.dao.state.model.governance.Vote; import bisq.asset.Asset; +import bisq.common.config.Config; import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ExceptionHandler; import bisq.common.handlers.ResultHandler; @@ -768,10 +769,12 @@ public class DaoFacade implements DaoSetupService { // If Dao is deactivated we need to add the default address as getAllPastParamValues will not return us any. allPastParamValues.add(Param.RECIPIENT_BTC_ADDRESS.getDefaultValue()); - // If Dao is deactivated we need to add the past addresses used as well. - // This list need to be updated once a new address gets defined. - allPastParamValues.add("3EtUWqsGThPtjwUczw27YCo6EWvQdaPUyp"); // burning man 2019 - allPastParamValues.add("3A8Zc1XioE2HRzYfbb5P8iemCS72M6vRJV"); // burningman2 + if (Config.baseCurrencyNetwork().isMainnet()) { + // If Dao is deactivated we need to add the past addresses used as well. + // This list need to be updated once a new address gets defined. + allPastParamValues.add("3EtUWqsGThPtjwUczw27YCo6EWvQdaPUyp"); // burning man 2019 + allPastParamValues.add("3A8Zc1XioE2HRzYfbb5P8iemCS72M6vRJV"); // burningman2 + } return allPastParamValues; } From 3d4427cdfd43edc803cc5a44a9e93b20447bc828 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Thu, 17 Sep 2020 13:54:45 -0500 Subject: [PATCH 15/31] Add result of filter match. Add more filter data (tx ids, json) --- .../main/support/dispute/DisputeView.java | 110 ++++++++++++++++-- .../dispute/agent/DisputeAgentView.java | 18 ++- .../dispute/client/DisputeClientView.java | 14 +-- 3 files changed, 111 insertions(+), 31 deletions(-) diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java index fcba3c3b65..4c4e06f5d9 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java @@ -89,6 +89,7 @@ import javafx.collections.transformation.FilteredList; import javafx.collections.transformation.SortedList; import javafx.util.Callback; +import javafx.util.Duration; import java.text.DateFormat; import java.text.ParseException; @@ -102,6 +103,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import lombok.Getter; @@ -110,6 +112,29 @@ import javax.annotation.Nullable; import static bisq.desktop.util.FormBuilder.getIconForLabel; public abstract class DisputeView extends ActivatableView { + public enum FilterResult { + NO_MATCH("No Match"), + NO_FILTER("No filter text"), + OPEN_DISPUTES("Open disputes"), + TRADE_ID("Trade ID"), + OPENING_DATE("Opening date"), + BUYER_NODE_ADDRESS("Buyer node address"), + SELLER_NODE_ADDRESS("Seller node address"), + BUYER_ACCOUNT_DETAILS("Buyer account details"), + SELLER_ACCOUNT_DETAILS("Seller account details"), + DEPOSIT_TX("Deposit tx ID"), + PAYOUT_TX("Payout tx ID"), + DEL_PAYOUT_TX("Delayed payout tx ID"), + JSON("Contract as json"); + + @Getter + private final String displayString; + + FilterResult(String displayString) { + + this.displayString = displayString; + } + } protected final DisputeManager> disputeManager; protected final KeyRing keyRing; @@ -177,6 +202,10 @@ public abstract class DisputeView extends ActivatableView { HBox.setHgrow(label, Priority.NEVER); filterTextField = new InputTextField(); + Tooltip tooltip = new Tooltip(); + tooltip.setShowDelay(Duration.millis(100)); + tooltip.setShowDuration(Duration.seconds(10)); + filterTextField.setTooltip(tooltip); filterTextFieldListener = (observable, oldValue, newValue) -> applyFilteredListPredicate(filterTextField.getText()); HBox.setHgrow(filterTextField, Priority.NEVER); @@ -385,10 +414,77 @@ public abstract class DisputeView extends ActivatableView { protected abstract DisputeSession getConcreteDisputeChatSession(Dispute dispute); protected void applyFilteredListPredicate(String filterString) { - // If in trader view we must not display arbitrators own disputes as trader (must not happen anyway) - filteredList.setPredicate(dispute -> !dispute.getAgentPubKeyRing().equals(keyRing.getPubKeyRing())); + AtomicReference filterResult = new AtomicReference<>(FilterResult.NO_FILTER); + filteredList.setPredicate(dispute -> { + FilterResult filterResult1 = getFilterResult(dispute, filterString); + filterResult.set(filterResult1); + boolean b = filterResult.get() != FilterResult.NO_MATCH; + log.error("filterResult1 {} {} {}, {}", filterResult1, dispute.getTraderId(), b, filterResult); + return b; + }); + + if (filterResult.get() == FilterResult.NO_MATCH) { + filterTextField.getTooltip().setText("No matches found"); + } else if (filterResult.get() == FilterResult.NO_FILTER) { + filterTextField.getTooltip().setText("No filter applied"); + } else if (filterResult.get() == FilterResult.OPEN_DISPUTES) { + filterTextField.getTooltip().setText("Show all open disputes"); + } else { + filterTextField.getTooltip().setText("Data matching filter string: " + filterResult.get().getDisplayString()); + } } + protected FilterResult getFilterResult(Dispute dispute, String filterString) { + if (filterString.isEmpty()) { + return FilterResult.NO_FILTER; + } + if (!dispute.isClosed() && filterString.toLowerCase().equals("open")) { + return FilterResult.OPEN_DISPUTES; + } + + if (dispute.getTradeId().contains(filterString)) { + return FilterResult.TRADE_ID; + } + + if (DisplayUtils.formatDate(dispute.getOpeningDate()).contains(filterString)) { + return FilterResult.OPENING_DATE; + } + + if (dispute.getContract().getBuyerNodeAddress().getFullAddress().contains(filterString)) { + return FilterResult.BUYER_NODE_ADDRESS; + } + + if (dispute.getContract().getSellerNodeAddress().getFullAddress().contains(filterString)) { + return FilterResult.SELLER_NODE_ADDRESS; + } + + if (dispute.getContract().getBuyerPaymentAccountPayload().getPaymentDetails().contains(filterString)) { + return FilterResult.BUYER_ACCOUNT_DETAILS; + } + + if (dispute.getContract().getSellerPaymentAccountPayload().getPaymentDetails().contains(filterString)) { + return FilterResult.SELLER_ACCOUNT_DETAILS; + } + + if (dispute.getDepositTxId() != null && dispute.getDepositTxId().contains(filterString)) { + return FilterResult.DEPOSIT_TX; + } + if (dispute.getPayoutTxId() != null && dispute.getPayoutTxId().contains(filterString)) { + return FilterResult.PAYOUT_TX; + } + + if (dispute.getDelayedPayoutTxId() != null && dispute.getDelayedPayoutTxId().contains(filterString)) { + return FilterResult.DEL_PAYOUT_TX; + } + + if (dispute.getContractAsJson().contains(filterString)) { + return FilterResult.JSON; + } + + return FilterResult.NO_MATCH; + } + + protected void reOpenDisputeFromButton() { reOpenDispute(); } @@ -412,16 +508,6 @@ public abstract class DisputeView extends ActivatableView { } } - protected boolean anyMatchOfFilterString(Dispute dispute, String filterString) { - boolean matchesTradeId = dispute.getTradeId().contains(filterString); - boolean matchesDate = DisplayUtils.formatDate(dispute.getOpeningDate()).contains(filterString); - boolean isBuyerOnion = dispute.getContract().getBuyerNodeAddress().getFullAddress().contains(filterString); - boolean isSellerOnion = dispute.getContract().getSellerNodeAddress().getFullAddress().contains(filterString); - boolean matchesBuyersPaymentAccountData = dispute.getContract().getBuyerPaymentAccountPayload().getPaymentDetails().contains(filterString); - boolean matchesSellersPaymentAccountData = dispute.getContract().getSellerPaymentAccountPayload().getPaymentDetails().contains(filterString); - return matchesTradeId || matchesDate || isBuyerOnion || isSellerOnion || - matchesBuyersPaymentAccountData || matchesSellersPaymentAccountData; - } /////////////////////////////////////////////////////////////////////////////////////////// // UI actions diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java index f7d059b110..c8661f2bb1 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java @@ -174,17 +174,13 @@ public abstract class DisputeAgentView extends DisputeView implements MultipleHo /////////////////////////////////////////////////////////////////////////////////////////// @Override - protected void applyFilteredListPredicate(String filterString) { - filteredList.setPredicate(dispute -> { - // If in arbitrator view we must only display disputes where we are selected as arbitrator (must not receive others anyway) - if (!dispute.getAgentPubKeyRing().equals(keyRing.getPubKeyRing())) { - return false; - } - boolean isOpen = !dispute.isClosed() && filterString.toLowerCase().equals("open"); - return filterString.isEmpty() || - isOpen || - anyMatchOfFilterString(dispute, filterString); - }); + protected DisputeView.FilterResult getFilterResult(Dispute dispute, String filterString) { + // If in arbitrator view we must only display disputes where we are selected as arbitrator (must not receive others anyway) + if (!dispute.getAgentPubKeyRing().equals(keyRing.getPubKeyRing())) { + return FilterResult.NO_MATCH; + } + + return super.getFilterResult(dispute, filterString); } @Override diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/client/DisputeClientView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/client/DisputeClientView.java index f06875202e..265f5838ec 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/client/DisputeClientView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/client/DisputeClientView.java @@ -55,14 +55,12 @@ public abstract class DisputeClientView extends DisputeView { } @Override - protected void applyFilteredListPredicate(String filterString) { - filteredList.setPredicate(dispute -> { - // As we are in the client view we hide disputes where we are the agent - if (dispute.getAgentPubKeyRing().equals(keyRing.getPubKeyRing())) { - return false; - } + protected DisputeView.FilterResult getFilterResult(Dispute dispute, String filterString) { + // As we are in the client view we hide disputes where we are the agent + if (dispute.getAgentPubKeyRing().equals(keyRing.getPubKeyRing())) { + return FilterResult.NO_MATCH; + } - return filterString.isEmpty() || anyMatchOfFilterString(dispute, filterString); - }); + return super.getFilterResult(dispute, filterString); } } From 45cee2a2729c91dc4a627a5ba2747c45b4673b26 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Thu, 17 Sep 2020 19:05:55 -0500 Subject: [PATCH 16/31] Add check for disputes with duplicated trade ID or payout tx ids --- .../main/java/bisq/core/dao/DaoFacade.java | 4 +- .../bisq/core/support/dispute/Dispute.java | 9 +++ .../core/support/dispute/DisputeManager.java | 32 ++++++-- .../dispute/mediation/MediationManager.java | 1 + .../support/dispute/refund/RefundManager.java | 1 + .../core/trade/DelayedPayoutTxValidation.java | 81 ++++++++++++++++++- .../windows/DisputeSummaryWindow.java | 16 ++++ .../dispute/agent/DisputeAgentView.java | 40 +++++---- proto/src/main/proto/pb.proto | 1 + 9 files changed, 159 insertions(+), 26 deletions(-) diff --git a/core/src/main/java/bisq/core/dao/DaoFacade.java b/core/src/main/java/bisq/core/dao/DaoFacade.java index 2f20a802ba..7d9d39aaa0 100644 --- a/core/src/main/java/bisq/core/dao/DaoFacade.java +++ b/core/src/main/java/bisq/core/dao/DaoFacade.java @@ -767,7 +767,9 @@ public class DaoFacade implements DaoSetupService { Set allPastParamValues = getAllPastParamValues(Param.RECIPIENT_BTC_ADDRESS); // If Dao is deactivated we need to add the default address as getAllPastParamValues will not return us any. - allPastParamValues.add(Param.RECIPIENT_BTC_ADDRESS.getDefaultValue()); + if (allPastParamValues.isEmpty()) { + allPastParamValues.add(Param.RECIPIENT_BTC_ADDRESS.getDefaultValue()); + } if (Config.baseCurrencyNetwork().isMainnet()) { // If Dao is deactivated we need to add the past addresses used as well. diff --git a/core/src/main/java/bisq/core/support/dispute/Dispute.java b/core/src/main/java/bisq/core/support/dispute/Dispute.java index 6a79a9f740..df80aa8c3f 100644 --- a/core/src/main/java/bisq/core/support/dispute/Dispute.java +++ b/core/src/main/java/bisq/core/support/dispute/Dispute.java @@ -107,6 +107,9 @@ public final class Dispute implements NetworkPayload { @Setter @Nullable private String donationAddressOfDelayedPayoutTx; + @Setter + @Nullable + private String agentsUid; /////////////////////////////////////////////////////////////////////////////////////////// @@ -234,6 +237,7 @@ public final class Dispute implements NetworkPayload { Optional.ofNullable(mediatorsDisputeResult).ifPresent(result -> builder.setMediatorsDisputeResult(mediatorsDisputeResult)); Optional.ofNullable(delayedPayoutTxId).ifPresent(result -> builder.setDelayedPayoutTxId(delayedPayoutTxId)); Optional.ofNullable(donationAddressOfDelayedPayoutTx).ifPresent(result -> builder.setDonationAddressOfDelayedPayoutTx(donationAddressOfDelayedPayoutTx)); + Optional.ofNullable(agentsUid).ifPresent(result -> builder.setAgentsUid(agentsUid)); return builder.build(); } @@ -282,6 +286,11 @@ public final class Dispute implements NetworkPayload { dispute.setDonationAddressOfDelayedPayoutTx(donationAddressOfDelayedPayoutTx); } + String agentsUid = proto.getAgentsUid(); + if (!agentsUid.isEmpty()) { + dispute.setAgentsUid(agentsUid); + } + return dispute; } diff --git a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java index 3fc8538e15..ea1ff28c54 100644 --- a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java +++ b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java @@ -86,10 +86,11 @@ public abstract class DisputeManager disputeListService; private final PriceFeedService priceFeedService; - private final DaoFacade daoFacade; + protected final DaoFacade daoFacade; @Getter - protected final ObservableList disputesWithInvalidDonationAddress = FXCollections.observableArrayList(); + protected final ObservableList validationExceptions = + FXCollections.observableArrayList(); /////////////////////////////////////////////////////////////////////////////////////////// @@ -219,7 +220,7 @@ public abstract class DisputeManager { + if (dispute.getAgentsUid() == null) { + dispute.setAgentsUid(UUID.randomUUID().toString()); + } + + try { + DelayedPayoutTxValidation.validateDonationAddress(dispute, dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade); + DelayedPayoutTxValidation.testIfDisputeTriesReplay(dispute, getDisputeList().getList()); + } catch (DelayedPayoutTxValidation.AddressException | DelayedPayoutTxValidation.DisputeReplayException e) { + log.error(e.toString()); + validationExceptions.add(e); + } + }); } public boolean isTrader(Dispute dispute) { @@ -282,6 +297,8 @@ public abstract class DisputeManager openOfferManager, daoFacade, pubKeyRing, mediationDisputeListService, priceFeedService); } + /////////////////////////////////////////////////////////////////////////////////////////// // Implement template methods /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java b/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java index f00aca5fbd..ab3008d8b1 100644 --- a/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java +++ b/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java @@ -83,6 +83,7 @@ public final class RefundManager extends DisputeManager { openOfferManager, daoFacade, pubKeyRing, refundDisputeListService, priceFeedService); } + /////////////////////////////////////////////////////////////////////////////////////////// // Implement template methods /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java b/core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java index 023f9d608d..25eb077120 100644 --- a/core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java +++ b/core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java @@ -30,9 +30,14 @@ import org.bitcoinj.core.TransactionInput; import org.bitcoinj.core.TransactionOutPoint; import org.bitcoinj.core.TransactionOutput; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Set; import java.util.function.Consumer; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import javax.annotation.Nullable; @@ -45,6 +50,11 @@ public class DelayedPayoutTxValidation { public static void validateDonationAddress(String addressAsString, DaoFacade daoFacade) throws AddressException { + validateDonationAddress(null, addressAsString, daoFacade); + } + + public static void validateDonationAddress(@Nullable Dispute dispute, String addressAsString, DaoFacade daoFacade) + throws AddressException { if (addressAsString == null) { log.warn("address is null at validateDonationAddress. This is expected in case of an not updated trader."); @@ -57,7 +67,55 @@ public class DelayedPayoutTxValidation { "\nAddress used in the dispute: " + addressAsString + "\nAll DAO param donation addresses:" + allPastParamValues; log.error(errorMsg); - throw new AddressException(errorMsg); + throw new AddressException(dispute, errorMsg); + } + } + + public static void testIfDisputeTriesReplay(Dispute disputeToTest, List disputeList) + throws DisputeReplayException { + try { + String disputeToTestDelayedPayoutTxId = disputeToTest.getDelayedPayoutTxId(); + checkNotNull(disputeToTestDelayedPayoutTxId, + "delayedPayoutTxId must not be null. Trade ID: " + disputeToTest.getTradeId()); + String disputeToTestAgentsUid = checkNotNull(disputeToTest.getAgentsUid(), + "agentsUid must not be null. Trade ID: " + disputeToTest.getTradeId()); + // This method can be called with the existing list and a new dispute (at opening a new dispute) or with the + // dispute already added (at close dispute). So we will consider that in the for loop. + // We have 2 disputes per trade (one per trader). + + Map> disputesPerTradeId = new HashMap<>(); + Map> disputesPerDelayedPayoutTxId = new HashMap<>(); + disputeList.forEach(dispute -> { + String tradeId = dispute.getTradeId(); + String agentsUid = dispute.getAgentsUid(); + + // We use an uid we have created not data delivered by the trader to protect against replay attacks + // If our dispute got already added to the list we ignore it. We will check once we build our maps + + disputesPerTradeId.putIfAbsent(tradeId, new HashSet<>()); + Set set = disputesPerTradeId.get(tradeId); + if (!disputeToTestAgentsUid.equals(agentsUid)) { + set.add(agentsUid); + } + + String delayedPayoutTxId = dispute.getDelayedPayoutTxId(); + disputesPerDelayedPayoutTxId.putIfAbsent(delayedPayoutTxId, new HashSet<>()); + set = disputesPerDelayedPayoutTxId.get(delayedPayoutTxId); + if (!disputeToTestAgentsUid.equals(agentsUid)) { + set.add(agentsUid); + } + }); + + String disputeToTestTradeId = disputeToTest.getTradeId(); + checkArgument(disputesPerTradeId.get(disputeToTestTradeId).size() <= 1, + "We found more then 2 disputes with the same trade ID. " + + "Trade ID: " + disputeToTest.getTradeId()); + checkArgument(disputesPerDelayedPayoutTxId.get(disputeToTestDelayedPayoutTxId).size() <= 1, + "We found more then 2 disputes with the same delayedPayoutTxId. " + + "Trade ID: " + disputeToTest.getTradeId()); + + } catch (IllegalArgumentException | NullPointerException e) { + throw new DisputeReplayException(disputeToTest, e.getMessage()); } } @@ -177,7 +235,7 @@ public class DelayedPayoutTxValidation { errorMsg = "Donation address cannot be resolved (not of type P2PKHScript or P2SH). Output: " + output; log.error(errorMsg); log.error(delayedPayoutTx.toString()); - throw new AddressException(errorMsg); + throw new AddressException(dispute, errorMsg); } } @@ -220,14 +278,23 @@ public class DelayedPayoutTxValidation { /////////////////////////////////////////////////////////////////////////////////////////// public static class ValidationException extends Exception { + @Nullable + @Getter + private final Dispute dispute; + ValidationException(String msg) { + this(null, msg); + } + + ValidationException(@Nullable Dispute dispute, String msg) { super(msg); + this.dispute = dispute; } } public static class AddressException extends ValidationException { - AddressException(String msg) { - super(msg); + AddressException(@Nullable Dispute dispute, String msg) { + super(dispute, msg); } } @@ -260,4 +327,10 @@ public class DelayedPayoutTxValidation { super(msg); } } + + public static class DisputeReplayException extends ValidationException { + DisputeReplayException(Dispute dispute, String msg) { + super(dispute, msg); + } + } } diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java index 3e349efc4d..2fd0b33e8e 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java @@ -746,6 +746,7 @@ public class DisputeSummaryWindow extends Overlay { var disputeManager = checkNotNull(getDisputeManager(dispute)); try { DelayedPayoutTxValidation.validateDonationAddress(dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade); + DelayedPayoutTxValidation.testIfDisputeTriesReplay(dispute, disputeManager.getDisputesAsObservableList()); doClose(closeTicketButton); } catch (DelayedPayoutTxValidation.AddressException exception) { String addressAsString = dispute.getDonationAddressOfDelayedPayoutTx(); @@ -775,6 +776,21 @@ public class DisputeSummaryWindow extends Overlay { Res.get("support.warning.disputesWithInvalidDonationAddress.refundAgent"))) .show(); } + } catch (DelayedPayoutTxValidation.DisputeReplayException exception) { + if (disputeManager instanceof MediationManager) { + new Popup().width(900) + .warning(exception.getMessage()) + .onAction(() -> { + doClose(closeTicketButton); + }) + .actionButtonText(Res.get("shared.yes")) + .closeButtonText(Res.get("shared.no")) + .show(); + } else { + new Popup().width(900) + .warning(exception.getMessage()) + .show(); + } } } diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java index c8661f2bb1..b2802c28b4 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java @@ -34,6 +34,7 @@ import bisq.core.support.dispute.DisputeList; import bisq.core.support.dispute.DisputeManager; import bisq.core.support.dispute.DisputeSession; import bisq.core.support.dispute.agent.MultipleHolderNameDetection; +import bisq.core.trade.DelayedPayoutTxValidation; import bisq.core.trade.TradeManager; import bisq.core.user.DontShowAgainLookup; import bisq.core.util.coin.CoinFormatter; @@ -59,13 +60,14 @@ import javafx.collections.ListChangeListener; import java.util.List; +import static bisq.core.trade.DelayedPayoutTxValidation.ValidationException; import static bisq.desktop.util.FormBuilder.getIconForLabel; public abstract class DisputeAgentView extends DisputeView implements MultipleHolderNameDetection.Listener { private final MultipleHolderNameDetection multipleHolderNameDetection; private final DaoFacade daoFacade; - private ListChangeListener disputesWithInvalidDonationAddressListener; + private ListChangeListener validationExceptionListener; public DisputeAgentView(DisputeManager> disputeManager, KeyRing keyRing, @@ -115,24 +117,30 @@ public abstract class DisputeAgentView extends DisputeView implements MultipleHo multipleHolderNameDetection.detectMultipleHolderNames(); - disputesWithInvalidDonationAddressListener = c -> { + validationExceptionListener = c -> { c.next(); if (c.wasAdded()) { - showWarningForInvalidDonationAddress(c.getAddedSubList()); + showWarningForValidationExceptions(c.getAddedSubList()); } }; } - protected void showWarningForInvalidDonationAddress(List disputes) { - disputes.stream() - .filter(dispute -> !dispute.isClosed()) - .forEach(dispute -> { - new Popup().warning(Res.get("support.warning.disputesWithInvalidDonationAddress", - dispute.getDonationAddressOfDelayedPayoutTx(), - daoFacade.getAllDonationAddresses(), - dispute.getTradeId(), - "")) - .show(); + protected void showWarningForValidationExceptions(List exceptions) { + exceptions.stream() + .filter(ex -> ex.getDispute() != null) + .filter(ex -> !ex.getDispute().isClosed()) + .forEach(ex -> { + Dispute dispute = ex.getDispute(); + if (ex instanceof DelayedPayoutTxValidation.AddressException) { + new Popup().width(900).warning(Res.get("support.warning.disputesWithInvalidDonationAddress", + dispute.getDonationAddressOfDelayedPayoutTx(), + daoFacade.getAllDonationAddresses(), + dispute.getTradeId(), + "")) + .show(); + } else { + new Popup().width(900).warning(ex.getMessage()).show(); + } }); } @@ -145,8 +153,8 @@ public abstract class DisputeAgentView extends DisputeView implements MultipleHo suspiciousDisputeDetected(); } - disputeManager.getDisputesWithInvalidDonationAddress().addListener(disputesWithInvalidDonationAddressListener); - showWarningForInvalidDonationAddress(disputeManager.getDisputesWithInvalidDonationAddress()); + disputeManager.getValidationExceptions().addListener(validationExceptionListener); + showWarningForValidationExceptions(disputeManager.getValidationExceptions()); } @Override @@ -155,7 +163,7 @@ public abstract class DisputeAgentView extends DisputeView implements MultipleHo multipleHolderNameDetection.removeListener(this); - disputeManager.getDisputesWithInvalidDonationAddress().removeListener(disputesWithInvalidDonationAddressListener); + disputeManager.getValidationExceptions().removeListener(validationExceptionListener); } diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index b10a4a02da..29efbd465b 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -793,6 +793,7 @@ message Dispute { string mediators_dispute_result = 25; string delayed_payout_tx_id = 26; string donation_address_of_delayed_payout_tx = 27; + string agents_uid = 28; } message Attachment { From 32930477ab74dd0ae86e2cc8993549c95a57818e Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 20 Sep 2020 14:49:32 -0500 Subject: [PATCH 17/31] Set agentsUid to new uuid in case it is null from persisted data --- core/src/main/java/bisq/core/support/dispute/Dispute.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/main/java/bisq/core/support/dispute/Dispute.java b/core/src/main/java/bisq/core/support/dispute/Dispute.java index df80aa8c3f..8087e9a1b3 100644 --- a/core/src/main/java/bisq/core/support/dispute/Dispute.java +++ b/core/src/main/java/bisq/core/support/dispute/Dispute.java @@ -44,6 +44,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Optional; +import java.util.UUID; import java.util.stream.Collectors; import lombok.EqualsAndHashCode; @@ -289,6 +290,8 @@ public final class Dispute implements NetworkPayload { String agentsUid = proto.getAgentsUid(); if (!agentsUid.isEmpty()) { dispute.setAgentsUid(agentsUid); + } else { + dispute.setAgentsUid(UUID.randomUUID().toString()); } return dispute; From d31deff9c4c96cca36792ddc989d88206da9fe1b Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 20 Sep 2020 14:50:02 -0500 Subject: [PATCH 18/31] Remove dev log --- .../bisq/desktop/main/support/dispute/DisputeView.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java index 7acd1beb29..f87b45eefb 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java @@ -432,11 +432,8 @@ public abstract class DisputeView extends ActivatableView { protected void applyFilteredListPredicate(String filterString) { AtomicReference filterResult = new AtomicReference<>(FilterResult.NO_FILTER); filteredList.setPredicate(dispute -> { - FilterResult filterResult1 = getFilterResult(dispute, filterString); - filterResult.set(filterResult1); - boolean b = filterResult.get() != FilterResult.NO_MATCH; - log.error("filterResult1 {} {} {}, {}", filterResult1, dispute.getTraderId(), b, filterResult); - return b; + filterResult.set(getFilterResult(dispute, filterString)); + return filterResult.get() != FilterResult.NO_MATCH; }); if (filterResult.get() == FilterResult.NO_MATCH) { From 25bc616db49a17c4b9f129caa075b6f482fbe16b Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 20 Sep 2020 14:51:09 -0500 Subject: [PATCH 19/31] Add check for multiple deposit txs --- .../core/trade/DelayedPayoutTxValidation.java | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java b/core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java index 7711780393..6b90089582 100644 --- a/core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java +++ b/core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java @@ -74,24 +74,35 @@ public class DelayedPayoutTxValidation { public static void testIfDisputeTriesReplay(Dispute disputeToTest, List disputeList) throws DisputeReplayException { try { + String disputeToTestTradeId = disputeToTest.getTradeId(); + String disputeToTestDelayedPayoutTxId = disputeToTest.getDelayedPayoutTxId(); + String disputeToTestDepositTxId = disputeToTest.getDepositTxId(); + String disputeToTestAgentsUid = disputeToTest.getAgentsUid(); + checkNotNull(disputeToTestDelayedPayoutTxId, - "delayedPayoutTxId must not be null. Trade ID: " + disputeToTest.getTradeId()); - String disputeToTestAgentsUid = checkNotNull(disputeToTest.getAgentsUid(), - "agentsUid must not be null. Trade ID: " + disputeToTest.getTradeId()); + "delayedPayoutTxId must not be null. Trade ID: " + disputeToTestTradeId); + checkNotNull(disputeToTestDepositTxId, + "depositTxId must not be null. Trade ID: " + disputeToTestTradeId); + checkNotNull(disputeToTestAgentsUid, + "agentsUid must not be null. Trade ID: " + disputeToTestTradeId); + // This method can be called with the existing list and a new dispute (at opening a new dispute) or with the // dispute already added (at close dispute). So we will consider that in the for loop. // We have 2 disputes per trade (one per trader). Map> disputesPerTradeId = new HashMap<>(); Map> disputesPerDelayedPayoutTxId = new HashMap<>(); + Map> disputesPerDepositTxId = new HashMap<>(); disputeList.forEach(dispute -> { - String tradeId = dispute.getTradeId(); String agentsUid = dispute.getAgentsUid(); + checkNotNull(agentsUid, + "agentsUid must not be null. Trade ID: " + disputeToTestTradeId); // We use an uid we have created not data delivered by the trader to protect against replay attacks // If our dispute got already added to the list we ignore it. We will check once we build our maps + String tradeId = dispute.getTradeId(); disputesPerTradeId.putIfAbsent(tradeId, new HashSet<>()); Set set = disputesPerTradeId.get(tradeId); if (!disputeToTestAgentsUid.equals(agentsUid)) { @@ -104,15 +115,24 @@ public class DelayedPayoutTxValidation { if (!disputeToTestAgentsUid.equals(agentsUid)) { set.add(agentsUid); } + + String depositTxId = dispute.getDepositTxId(); + disputesPerDepositTxId.putIfAbsent(depositTxId, new HashSet<>()); + set = disputesPerDepositTxId.get(depositTxId); + if (!disputeToTestAgentsUid.equals(agentsUid)) { + set.add(agentsUid); + } }); - String disputeToTestTradeId = disputeToTest.getTradeId(); checkArgument(disputesPerTradeId.get(disputeToTestTradeId).size() <= 1, "We found more then 2 disputes with the same trade ID. " + - "Trade ID: " + disputeToTest.getTradeId()); + "Trade ID: " + disputeToTestTradeId); checkArgument(disputesPerDelayedPayoutTxId.get(disputeToTestDelayedPayoutTxId).size() <= 1, "We found more then 2 disputes with the same delayedPayoutTxId. " + - "Trade ID: " + disputeToTest.getTradeId()); + "Trade ID: " + disputeToTestTradeId); + checkArgument(disputesPerDepositTxId.get(disputeToTestDepositTxId).size() <= 1, + "We found more then 2 disputes with the same depositTxId. " + + "Trade ID: " + disputeToTestTradeId); } catch (IllegalArgumentException | NullPointerException e) { throw new DisputeReplayException(disputeToTest, e.getMessage()); From 4878a101d407094bb99a047428fc4cdb3d01e0d6 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 20 Sep 2020 15:32:19 -0500 Subject: [PATCH 20/31] Optimize testIfDisputeTriesReplay methods to avoid that maps get created at each iteration --- .../core/support/dispute/DisputeManager.java | 14 ++- .../core/trade/DelayedPayoutTxValidation.java | 116 +++++++++++------- 2 files changed, 81 insertions(+), 49 deletions(-) diff --git a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java index 822ba7656e..33e6a0e38f 100644 --- a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java +++ b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java @@ -260,18 +260,20 @@ public abstract class DisputeManager { - if (dispute.getAgentsUid() == null) { - dispute.setAgentsUid(UUID.randomUUID().toString()); - } - try { DelayedPayoutTxValidation.validateDonationAddress(dispute, dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade); - DelayedPayoutTxValidation.testIfDisputeTriesReplay(dispute, getDisputeList().getList()); - } catch (DelayedPayoutTxValidation.AddressException | DelayedPayoutTxValidation.DisputeReplayException e) { + } catch (DelayedPayoutTxValidation.AddressException e) { log.error(e.toString()); validationExceptions.add(e); } + }); + + DelayedPayoutTxValidation.testIfAnyDisputeTriedReplay(getDisputeList().getList(), + disputeReplayException -> { + log.error(disputeReplayException.toString()); + validationExceptions.add(disputeReplayException); + }); } public boolean isTrader(Dispute dispute) { diff --git a/core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java b/core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java index 6b90089582..3cc1d3455f 100644 --- a/core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java +++ b/core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java @@ -22,6 +22,8 @@ import bisq.core.dao.DaoFacade; import bisq.core.offer.Offer; import bisq.core.support.dispute.Dispute; +import bisq.common.util.Tuple3; + import org.bitcoinj.core.Address; import org.bitcoinj.core.Coin; import org.bitcoinj.core.NetworkParameters; @@ -57,7 +59,7 @@ public class DelayedPayoutTxValidation { throws AddressException { if (addressAsString == null) { - log.warn("address is null at validateDonationAddress. This is expected in case of an not updated trader."); + log.debug("address is null at validateDonationAddress. This is expected in case of an not updated trader."); return; } @@ -71,11 +73,76 @@ public class DelayedPayoutTxValidation { } } - public static void testIfDisputeTriesReplay(Dispute disputeToTest, List disputeList) + public static void testIfAnyDisputeTriedReplay(List disputeList, + Consumer exceptionHandler) { + var tuple = getTestReplayHashMaps(disputeList); + Map> disputesPerTradeId = tuple.first; + Map> disputesPerDelayedPayoutTxId = tuple.second; + Map> disputesPerDepositTxId = tuple.third; + + disputeList.forEach(disputeToTest -> { + try { + testIfDisputeTriesReplay(disputeToTest, + disputesPerTradeId, + disputesPerDelayedPayoutTxId, + disputesPerDepositTxId); + + } catch (DisputeReplayException e) { + exceptionHandler.accept(e); + } + }); + } + + + public static void testIfDisputeTriesReplay(Dispute dispute, + List disputeList) throws DisputeReplayException { + var tuple = DelayedPayoutTxValidation.getTestReplayHashMaps(disputeList); + Map> disputesPerTradeId = tuple.first; + Map> disputesPerDelayedPayoutTxId = tuple.second; + Map> disputesPerDepositTxId = tuple.third; + + testIfDisputeTriesReplay(dispute, + disputesPerTradeId, + disputesPerDelayedPayoutTxId, + disputesPerDepositTxId); + } + + + private static Tuple3>, Map>, Map>> getTestReplayHashMaps( + List disputeList) { + Map> disputesPerTradeId = new HashMap<>(); + Map> disputesPerDelayedPayoutTxId = new HashMap<>(); + Map> disputesPerDepositTxId = new HashMap<>(); + disputeList.forEach(dispute -> { + String agentsUid = dispute.getAgentsUid(); + + String tradeId = dispute.getTradeId(); + disputesPerTradeId.putIfAbsent(tradeId, new HashSet<>()); + Set set = disputesPerTradeId.get(tradeId); + set.add(agentsUid); + + String delayedPayoutTxId = dispute.getDelayedPayoutTxId(); + disputesPerDelayedPayoutTxId.putIfAbsent(delayedPayoutTxId, new HashSet<>()); + set = disputesPerDelayedPayoutTxId.get(delayedPayoutTxId); + set.add(agentsUid); + + String depositTxId = dispute.getDepositTxId(); + disputesPerDepositTxId.putIfAbsent(depositTxId, new HashSet<>()); + set = disputesPerDepositTxId.get(depositTxId); + set.add(agentsUid); + }); + + return new Tuple3<>(disputesPerTradeId, disputesPerDelayedPayoutTxId, disputesPerDepositTxId); + } + + private static void testIfDisputeTriesReplay(Dispute disputeToTest, + Map> disputesPerTradeId, + Map> disputesPerDelayedPayoutTxId, + Map> disputesPerDepositTxId) throws DisputeReplayException { + try { String disputeToTestTradeId = disputeToTest.getTradeId(); - String disputeToTestDelayedPayoutTxId = disputeToTest.getDelayedPayoutTxId(); String disputeToTestDepositTxId = disputeToTest.getDepositTxId(); String disputeToTestAgentsUid = disputeToTest.getAgentsUid(); @@ -87,50 +154,13 @@ public class DelayedPayoutTxValidation { checkNotNull(disputeToTestAgentsUid, "agentsUid must not be null. Trade ID: " + disputeToTestTradeId); - // This method can be called with the existing list and a new dispute (at opening a new dispute) or with the - // dispute already added (at close dispute). So we will consider that in the for loop. - // We have 2 disputes per trade (one per trader). - - Map> disputesPerTradeId = new HashMap<>(); - Map> disputesPerDelayedPayoutTxId = new HashMap<>(); - Map> disputesPerDepositTxId = new HashMap<>(); - disputeList.forEach(dispute -> { - String agentsUid = dispute.getAgentsUid(); - checkNotNull(agentsUid, - "agentsUid must not be null. Trade ID: " + disputeToTestTradeId); - - // We use an uid we have created not data delivered by the trader to protect against replay attacks - // If our dispute got already added to the list we ignore it. We will check once we build our maps - - String tradeId = dispute.getTradeId(); - disputesPerTradeId.putIfAbsent(tradeId, new HashSet<>()); - Set set = disputesPerTradeId.get(tradeId); - if (!disputeToTestAgentsUid.equals(agentsUid)) { - set.add(agentsUid); - } - - String delayedPayoutTxId = dispute.getDelayedPayoutTxId(); - disputesPerDelayedPayoutTxId.putIfAbsent(delayedPayoutTxId, new HashSet<>()); - set = disputesPerDelayedPayoutTxId.get(delayedPayoutTxId); - if (!disputeToTestAgentsUid.equals(agentsUid)) { - set.add(agentsUid); - } - - String depositTxId = dispute.getDepositTxId(); - disputesPerDepositTxId.putIfAbsent(depositTxId, new HashSet<>()); - set = disputesPerDepositTxId.get(depositTxId); - if (!disputeToTestAgentsUid.equals(agentsUid)) { - set.add(agentsUid); - } - }); - - checkArgument(disputesPerTradeId.get(disputeToTestTradeId).size() <= 1, + checkArgument(disputesPerTradeId.get(disputeToTestTradeId).size() <= 2, "We found more then 2 disputes with the same trade ID. " + "Trade ID: " + disputeToTestTradeId); - checkArgument(disputesPerDelayedPayoutTxId.get(disputeToTestDelayedPayoutTxId).size() <= 1, + checkArgument(disputesPerDelayedPayoutTxId.get(disputeToTestDelayedPayoutTxId).size() <= 2, "We found more then 2 disputes with the same delayedPayoutTxId. " + "Trade ID: " + disputeToTestTradeId); - checkArgument(disputesPerDepositTxId.get(disputeToTestDepositTxId).size() <= 1, + checkArgument(disputesPerDepositTxId.get(disputeToTestDepositTxId).size() <= 2, "We found more then 2 disputes with the same depositTxId. " + "Trade ID: " + disputeToTestTradeId); From c6778d6b2dc6e84f8f9e207f452dd020250372d2 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 20 Sep 2020 20:20:39 -0500 Subject: [PATCH 21/31] Add copy to csv data button to report screen --- .../core/support/dispute/DisputeManager.java | 5 +- .../main/support/dispute/DisputeView.java | 209 +++++++++++------- 2 files changed, 130 insertions(+), 84 deletions(-) diff --git a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java index 33e6a0e38f..fc74453bdb 100644 --- a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java +++ b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java @@ -259,7 +259,8 @@ public abstract class DisputeManager { + ObservableList disputes = getDisputeList().getList(); + disputes.forEach(dispute -> { try { DelayedPayoutTxValidation.validateDonationAddress(dispute, dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade); } catch (DelayedPayoutTxValidation.AddressException e) { @@ -269,7 +270,7 @@ public abstract class DisputeManager { log.error(disputeReplayException.toString()); validationExceptions.add(disputeReplayException); diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java index f87b45eefb..cc2f317db6 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java @@ -61,8 +61,6 @@ import bisq.common.util.Utilities; import org.bitcoinj.core.Coin; -import com.google.common.collect.Lists; - import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon; import javafx.scene.control.Button; @@ -94,10 +92,6 @@ import javafx.collections.transformation.SortedList; import javafx.util.Callback; import javafx.util.Duration; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; - import java.util.ArrayList; import java.util.Comparator; import java.util.Date; @@ -128,6 +122,8 @@ public abstract class DisputeView extends ActivatableView { DEPOSIT_TX("Deposit tx ID"), PAYOUT_TX("Payout tx ID"), DEL_PAYOUT_TX("Delayed payout tx ID"), + RESULT_MESSAGE("Result message"), + REASON("Reason"), JSON("Contract as json"); @Getter @@ -300,7 +296,8 @@ public abstract class DisputeView extends ActivatableView { protected void activate() { filterTextField.textProperty().addListener(filterTextFieldListener); - filteredList = new FilteredList<>(disputeManager.getDisputesAsObservableList()); + ObservableList disputesAsObservableList = disputeManager.getDisputesAsObservableList(); + filteredList = new FilteredList<>(disputesAsObservableList); applyFilteredListPredicate(filterTextField.getText()); sortedList = new SortedList<>(filteredList); @@ -321,54 +318,6 @@ public abstract class DisputeView extends ActivatableView { chatView.scrollToBottom(); } - - // If doPrint=true we print out a html page which opens tabs with all deposit txs - // (firefox needs about:config change to allow > 20 tabs) - // Useful to check if there any funds in not finished trades (no payout tx done). - // Last check 10.02.2017 found 8 trades and we contacted all traders as far as possible (email if available - // otherwise in-app private notification) - boolean doPrint = false; - //noinspection ConstantConditions - if (doPrint) { - try { - DateFormat formatter = new SimpleDateFormat("dd/MM/yy"); - //noinspection UnusedAssignment - Date startDate = formatter.parse("10/02/17"); - startDate = new Date(0); // print all from start - - HashMap map = new HashMap<>(); - disputeManager.getDisputesAsObservableList().forEach(dispute -> map.put(dispute.getDepositTxId(), dispute)); - - final Date finalStartDate = startDate; - List disputes = new ArrayList<>(map.values()); - disputes.sort(Comparator.comparing(Dispute::getOpeningDate)); - List> subLists = Lists.partition(disputes, 1000); - StringBuilder sb = new StringBuilder(); - // We don't translate that as it is not intended for the public - subLists.forEach(list -> { - StringBuilder sb1 = new StringBuilder("\n\n"); - list.forEach(dispute -> { - if (dispute.getOpeningDate().after(finalStartDate)) { - String txId = dispute.getDepositTxId(); - sb1.append("window.open(\"https://blockchain.info/tx/").append(txId).append("\", '_blank');\n"); - - sb2.append("Dispute ID: ").append(dispute.getId()). - append(" Tx ID: "). - append(""). - append(txId).append(" "). - append("Opening date: ").append(formatter.format(dispute.getOpeningDate())).append("
\n"); - } - }); - sb2.append(""); - String res = sb1.toString() + sb2.toString(); - - sb.append(res).append("\n\n\n"); - }); - log.info(sb.toString()); - } catch (ParseException ignore) { - } - } GUIUtil.requestFocus(filterTextField); } @@ -448,18 +397,21 @@ public abstract class DisputeView extends ActivatableView { } protected FilterResult getFilterResult(Dispute dispute, String filterString) { + filterString = filterString.toLowerCase(); if (filterString.isEmpty()) { return FilterResult.NO_FILTER; } - if (!dispute.isClosed() && filterString.toLowerCase().equals("open")) { - return FilterResult.OPEN_DISPUTES; + + // For open filter we do not want to continue further as json data would cause a match + if (filterString.equalsIgnoreCase("open")) { + return !dispute.isClosed() ? FilterResult.OPEN_DISPUTES : FilterResult.NO_MATCH; } - if (dispute.getTradeId().contains(filterString)) { + if (dispute.getTradeId().toLowerCase().contains(filterString)) { return FilterResult.TRADE_ID; } - if (DisplayUtils.formatDate(dispute.getOpeningDate()).contains(filterString)) { + if (DisplayUtils.formatDate(dispute.getOpeningDate()).toLowerCase().contains(filterString)) { return FilterResult.OPENING_DATE; } @@ -471,11 +423,11 @@ public abstract class DisputeView extends ActivatableView { return FilterResult.SELLER_NODE_ADDRESS; } - if (dispute.getContract().getBuyerPaymentAccountPayload().getPaymentDetails().contains(filterString)) { + if (dispute.getContract().getBuyerPaymentAccountPayload().getPaymentDetails().toLowerCase().contains(filterString)) { return FilterResult.BUYER_ACCOUNT_DETAILS; } - if (dispute.getContract().getSellerPaymentAccountPayload().getPaymentDetails().contains(filterString)) { + if (dispute.getContract().getSellerPaymentAccountPayload().getPaymentDetails().toLowerCase().contains(filterString)) { return FilterResult.SELLER_ACCOUNT_DETAILS; } @@ -490,7 +442,19 @@ public abstract class DisputeView extends ActivatableView { return FilterResult.DEL_PAYOUT_TX; } - if (dispute.getContractAsJson().contains(filterString)) { + DisputeResult disputeResult = dispute.getDisputeResultProperty().get(); + if (disputeResult != null) { + ChatMessage chatMessage = disputeResult.getChatMessage(); + if (chatMessage != null && chatMessage.getMessage().toLowerCase().contains(filterString)) { + return FilterResult.RESULT_MESSAGE; + } + + if (disputeResult.getReason().name().toLowerCase().contains(filterString)) { + return FilterResult.REASON; + } + } + + if (dispute.getContractAsJson().toLowerCase().contains(filterString)) { return FilterResult.JSON; } @@ -646,6 +610,32 @@ public abstract class DisputeView extends ActivatableView { map.forEach((key, value) -> allDisputes.add(value)); allDisputes.sort(Comparator.comparing(o -> !o.isEmpty() ? o.get(0).getOpeningDate() : new Date(0))); StringBuilder stringBuilder = new StringBuilder(); + StringBuilder csvStringBuilder = new StringBuilder(); + csvStringBuilder.append("Dispute nr").append(";") + .append("Status").append(";") + .append("Trade date").append(";") + .append("Trade ID").append(";") + .append("Offer version").append(";") + .append("Opening date").append(";") + .append("Close date").append(";") + .append("Duration").append(";") + .append("Currency").append(";") + .append("Trade amount").append(";") + .append("Payment method").append(";") + .append("Buyer account details").append(";") + .append("Seller account details").append(";") + .append("Buyer address").append(";") + .append("Seller address").append(";") + .append("Buyer security deposit").append(";") + .append("Seller security deposit").append(";") + .append("Dispute opened by").append(";") + .append("Payout to buyer").append(";") + .append("Payout to seller").append(";") + .append("Winner").append(";") + .append("Reason").append(";") + .append("Summary notes").append(";") + .append("Summary notes (other trader)"); + AtomicInteger disputeIndex = new AtomicInteger(); allDisputes.forEach(disputesPerTrade -> { if (disputesPerTrade.size() > 0) { @@ -660,38 +650,58 @@ public abstract class DisputeView extends ActivatableView { disputeResult.getWinner() == DisputeResult.Winner.BUYER ? "Buyer" : "Seller"; String buyerPayoutAmount = disputeResult != null ? disputeResult.getBuyerPayoutAmount().toFriendlyString() : ""; String sellerPayoutAmount = disputeResult != null ? disputeResult.getSellerPayoutAmount().toFriendlyString() : ""; + + int index = disputeIndex.incrementAndGet(); + String tradeDateString = DisplayUtils.formatDateTime(firstDispute.getTradeDate()); + String openingDateString = DisplayUtils.formatDateTime(openingDate); stringBuilder.append("\n") - .append("Dispute nr. ") - .append(disputeIndex.incrementAndGet()) + .append("Dispute nr. ").append(index) .append("\n") - .append("Opening date: ") - .append(DisplayUtils.formatDateTime(openingDate)) + .append("Trade date: ").append(tradeDateString) + .append("\n") + .append("Opening date: ").append(openingDateString) .append("\n"); - String summaryNotes0 = ""; + String tradeId = firstDispute.getTradeId(); + csvStringBuilder.append("\n").append(index).append(";") + .append(firstDispute.isClosed() ? "Closed" : "Open").append(";") + .append(tradeDateString).append(";") + .append(firstDispute.getShortTradeId()).append(";") + .append(tradeId, tradeId.length() - 3, tradeId.length()).append(";") + .append(openingDateString).append(";"); + + String summaryNotes = ""; if (disputeResult != null) { Date closeDate = disputeResult.getCloseDate(); long duration = closeDate.getTime() - openingDate.getTime(); - stringBuilder.append("Close date: ") - .append(DisplayUtils.formatDateTime(closeDate)) - .append("\n") - .append("Dispute duration: ") - .append(FormattingUtils.formatDurationAsWords(duration)) - .append("\n"); + + String closeDateString = DisplayUtils.formatDateTime(closeDate); + String durationAsWords = FormattingUtils.formatDurationAsWords(duration); + stringBuilder.append("Close date: ").append(closeDateString).append("\n") + .append("Dispute duration: ").append(durationAsWords).append("\n"); + csvStringBuilder.append(closeDateString).append(";") + .append(durationAsWords).append(";"); + } else { + csvStringBuilder.append(";").append(";"); } + String paymentMethod = Res.get(contract.getPaymentMethodId()); + String currency = CurrencyUtil.getNameAndCode(contract.getOfferPayload().getCurrencyCode()); + String tradeAmount = contract.getTradeAmount().toFriendlyString(); + String buyerDeposit = Coin.valueOf(contract.getOfferPayload().getBuyerSecurityDeposit()).toFriendlyString(); + String sellerDeposit = Coin.valueOf(contract.getOfferPayload().getSellerSecurityDeposit()).toFriendlyString(); stringBuilder.append("Payment method: ") - .append(Res.get(contract.getPaymentMethodId())) + .append(paymentMethod) .append("\n") .append("Currency: ") - .append(CurrencyUtil.getNameAndCode(contract.getOfferPayload().getCurrencyCode())) + .append(currency) .append("\n") .append("Trade amount: ") - .append(contract.getTradeAmount().toFriendlyString()) + .append(tradeAmount) .append("\n") .append("Buyer/seller security deposit: ") - .append(Coin.valueOf(contract.getOfferPayload().getBuyerSecurityDeposit()).toFriendlyString()) + .append(buyerDeposit) .append("/") - .append(Coin.valueOf(contract.getOfferPayload().getSellerSecurityDeposit()).toFriendlyString()) + .append(sellerDeposit) .append("\n") .append("Dispute opened by: ") .append(opener) @@ -702,6 +712,28 @@ public abstract class DisputeView extends ActivatableView { .append(winner) .append(")\n"); + String buyerPaymentAccountPayload = Utilities.toTruncatedString( + contract.getBuyerPaymentAccountPayload().getPaymentDetails(). + replace("\n", " ").replace(";", "."), 100); + String sellerPaymentAccountPayload = Utilities.toTruncatedString( + contract.getSellerPaymentAccountPayload().getPaymentDetails() + .replace("\n", " ").replace(";", "."), 100); + String buyerNodeAddress = contract.getBuyerNodeAddress().getFullAddress(); + String sellerNodeAddress = contract.getSellerNodeAddress().getFullAddress(); + csvStringBuilder.append(currency).append(";") + .append(tradeAmount.replace(" BTC", "")).append(";") + .append(paymentMethod).append(";") + .append(buyerPaymentAccountPayload).append(";") + .append(sellerPaymentAccountPayload).append(";") + .append(buyerNodeAddress.replace(".onion:9999", "")).append(";") + .append(sellerNodeAddress.replace(".onion:9999", "")).append(";") + .append(buyerDeposit.replace(" BTC", "")).append(";") + .append(sellerDeposit.replace(" BTC", "")).append(";") + .append(opener).append(";") + .append(buyerPayoutAmount.replace(" BTC", "")).append(";") + .append(sellerPayoutAmount.replace(" BTC", "")).append(";") + .append(winner).append(";"); + if (disputeResult != null) { DisputeResult.Reason reason = disputeResult.getReason(); if (firstDispute.disputeResultProperty().get().getReason() != null) { @@ -710,10 +742,18 @@ public abstract class DisputeView extends ActivatableView { stringBuilder.append("Reason: ") .append(reason.name()) .append("\n"); + + csvStringBuilder.append(reason.name()).append(";"); + } else { + csvStringBuilder.append(";"); } - summaryNotes0 = disputeResult.getSummaryNotesProperty().get(); - stringBuilder.append("Summary notes: ").append(summaryNotes0).append("\n"); + summaryNotes = disputeResult.getSummaryNotesProperty().get(); + stringBuilder.append("Summary notes: ").append(summaryNotes).append("\n"); + + csvStringBuilder.append(summaryNotes).append(";"); + } else { + csvStringBuilder.append(";"); } // We might have a different summary notes at second trader. Only if it @@ -723,8 +763,12 @@ public abstract class DisputeView extends ActivatableView { DisputeResult disputeResult1 = dispute1.getDisputeResultProperty().get(); if (disputeResult1 != null) { String summaryNotes1 = disputeResult1.getSummaryNotesProperty().get(); - if (!summaryNotes1.equals(summaryNotes0)) { + if (!summaryNotes1.equals(summaryNotes)) { stringBuilder.append("Summary notes (different message to other trader was used): ").append(summaryNotes1).append("\n"); + + csvStringBuilder.append(summaryNotes1).append(";"); + } else { + csvStringBuilder.append(";"); } } } @@ -742,8 +786,9 @@ public abstract class DisputeView extends ActivatableView { .width(1200) .actionButtonText("Copy to clipboard") .onAction(() -> Utilities.copyToClipboard(message)) + .secondaryActionButtonText("Copy as csv data") + .onSecondaryAction(() -> Utilities.copyToClipboard(csvStringBuilder.toString())) .show(); - } private void showFullReport() { From 72dca0b55a94f4eba3ed6cc6ad8b9ebd121361fe Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 20 Sep 2020 21:25:12 -0500 Subject: [PATCH 22/31] Add cylce index --- .../main/java/bisq/core/dao/DaoFacade.java | 14 +++++- .../main/support/dispute/DisputeView.java | 46 +++++++++++++++---- .../dispute/agent/DisputeAgentView.java | 3 +- .../dispute/client/DisputeClientView.java | 4 +- .../arbitration/ArbitrationClientView.java | 4 +- .../client/mediation/MediationClientView.java | 4 +- .../client/refund/RefundClientView.java | 4 +- 7 files changed, 63 insertions(+), 16 deletions(-) diff --git a/core/src/main/java/bisq/core/dao/DaoFacade.java b/core/src/main/java/bisq/core/dao/DaoFacade.java index 7d9d39aaa0..ce52e74e67 100644 --- a/core/src/main/java/bisq/core/dao/DaoFacade.java +++ b/core/src/main/java/bisq/core/dao/DaoFacade.java @@ -96,10 +96,14 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import lombok.extern.slf4j.Slf4j; @@ -425,10 +429,18 @@ public class DaoFacade implements DaoSetupService { case RESULT: break; } - return firstBlock; } + public Map getBlockStartDateByCycleIndex() { + AtomicInteger index = new AtomicInteger(); + Map map = new HashMap<>(); + periodService.getCycles() + .forEach(cycle -> daoStateService.getBlockAtHeight(cycle.getHeightOfFirstBlock()) + .ifPresent(block -> map.put(index.getAndIncrement(), new Date(block.getTime())))); + return map; + } + // Because last block in request and voting phases must not be used for making a tx as it will get confirmed in the // next block which would be already the next phase we hide that last block to the user and add it to the break. public int getLastBlockOfPhaseForDisplay(int height, DaoPhase.Phase phase) { diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java index cc2f317db6..badfe8c339 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java @@ -35,6 +35,7 @@ import bisq.desktop.util.GUIUtil; import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.alert.PrivateNotificationManager; +import bisq.core.dao.DaoFacade; import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; import bisq.core.support.SupportType; @@ -92,6 +93,8 @@ import javafx.collections.transformation.SortedList; import javafx.util.Callback; import javafx.util.Duration; +import java.text.SimpleDateFormat; + import java.util.ArrayList; import java.util.Comparator; import java.util.Date; @@ -147,6 +150,7 @@ public abstract class DisputeView extends ActivatableView { private final AccountAgeWitnessService accountAgeWitnessService; private final MediatorManager mediatorManager; private final RefundAgentManager refundAgentManager; + protected final DaoFacade daoFacade; private final boolean useDevPrivilegeKeys; protected TableView tableView; @@ -185,6 +189,7 @@ public abstract class DisputeView extends ActivatableView { AccountAgeWitnessService accountAgeWitnessService, MediatorManager mediatorManager, RefundAgentManager refundAgentManager, + DaoFacade daoFacade, boolean useDevPrivilegeKeys) { this.disputeManager = disputeManager; this.keyRing = keyRing; @@ -197,6 +202,7 @@ public abstract class DisputeView extends ActivatableView { this.accountAgeWitnessService = accountAgeWitnessService; this.mediatorManager = mediatorManager; this.refundAgentManager = refundAgentManager; + this.daoFacade = daoFacade; this.useDevPrivilegeKeys = useDevPrivilegeKeys; } @@ -612,6 +618,7 @@ public abstract class DisputeView extends ActivatableView { StringBuilder stringBuilder = new StringBuilder(); StringBuilder csvStringBuilder = new StringBuilder(); csvStringBuilder.append("Dispute nr").append(";") + .append("Closed during cycle").append(";") .append("Status").append(";") .append("Trade date").append(";") .append("Trade ID").append(";") @@ -636,6 +643,9 @@ public abstract class DisputeView extends ActivatableView { .append("Summary notes").append(";") .append("Summary notes (other trader)"); + Map blockStartDateByCycleIndex = daoFacade.getBlockStartDateByCycleIndex(); + + SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy MM dd HH:mm:ss"); AtomicInteger disputeIndex = new AtomicInteger(); allDisputes.forEach(disputesPerTrade -> { if (disputesPerTrade.size() > 0) { @@ -652,18 +662,36 @@ public abstract class DisputeView extends ActivatableView { String sellerPayoutAmount = disputeResult != null ? disputeResult.getSellerPayoutAmount().toFriendlyString() : ""; int index = disputeIndex.incrementAndGet(); - String tradeDateString = DisplayUtils.formatDateTime(firstDispute.getTradeDate()); - String openingDateString = DisplayUtils.formatDateTime(openingDate); - stringBuilder.append("\n") - .append("Dispute nr. ").append(index) - .append("\n") - .append("Trade date: ").append(tradeDateString) + String tradeDateString = dateFormatter.format(firstDispute.getTradeDate()); + String openingDateString = dateFormatter.format(openingDate); + + // Index we display starts with 1 not with 0 + int cycleIndex = 0; + if (disputeResult != null) { + Date closeDate = disputeResult.getCloseDate(); + cycleIndex = blockStartDateByCycleIndex.entrySet().stream() + .filter(e -> e.getValue().after(closeDate)) + .findFirst() + .map(Map.Entry::getKey) + .orElse(0); + } + stringBuilder.append("\n").append("Dispute nr.: ").append(index).append("\n"); + + if (cycleIndex > 0) { + stringBuilder.append("Closed during cycle: ").append(cycleIndex).append("\n"); + } + stringBuilder.append("Trade date: ").append(tradeDateString) .append("\n") .append("Opening date: ").append(openingDateString) .append("\n"); String tradeId = firstDispute.getTradeId(); - csvStringBuilder.append("\n").append(index).append(";") - .append(firstDispute.isClosed() ? "Closed" : "Open").append(";") + csvStringBuilder.append("\n").append(index).append(";"); + if (cycleIndex > 0) { + csvStringBuilder.append(cycleIndex).append(";"); + } else { + csvStringBuilder.append(";"); + } + csvStringBuilder.append(firstDispute.isClosed() ? "Closed" : "Open").append(";") .append(tradeDateString).append(";") .append(firstDispute.getShortTradeId()).append(";") .append(tradeId, tradeId.length() - 3, tradeId.length()).append(";") @@ -674,7 +702,7 @@ public abstract class DisputeView extends ActivatableView { Date closeDate = disputeResult.getCloseDate(); long duration = closeDate.getTime() - openingDate.getTime(); - String closeDateString = DisplayUtils.formatDateTime(closeDate); + String closeDateString = dateFormatter.format(closeDate); String durationAsWords = FormattingUtils.formatDurationAsWords(duration); stringBuilder.append("Close date: ").append(closeDateString).append("\n") .append("Dispute duration: ").append(durationAsWords).append("\n"); diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java index dbfdc1db3b..b8241353ad 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java @@ -68,7 +68,6 @@ import static bisq.desktop.util.FormBuilder.getIconForLabel; public abstract class DisputeAgentView extends DisputeView implements MultipleHolderNameDetection.Listener { private final MultipleHolderNameDetection multipleHolderNameDetection; - private final DaoFacade daoFacade; private ListChangeListener validationExceptionListener; public DisputeAgentView(DisputeManager> disputeManager, @@ -95,10 +94,10 @@ public abstract class DisputeAgentView extends DisputeView implements MultipleHo accountAgeWitnessService, mediatorManager, refundAgentManager, + daoFacade, useDevPrivilegeKeys); multipleHolderNameDetection = new MultipleHolderNameDetection(disputeManager); - this.daoFacade = daoFacade; } diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/client/DisputeClientView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/client/DisputeClientView.java index 93a8a2642f..33e31ce3bb 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/client/DisputeClientView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/client/DisputeClientView.java @@ -24,6 +24,7 @@ import bisq.desktop.main.support.dispute.DisputeView; import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.alert.PrivateNotificationManager; +import bisq.core.dao.DaoFacade; import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.DisputeList; import bisq.core.support.dispute.DisputeManager; @@ -47,10 +48,11 @@ public abstract class DisputeClientView extends DisputeView { AccountAgeWitnessService accountAgeWitnessService, MediatorManager mediatorManager, RefundAgentManager refundAgentManager, + DaoFacade daoFacade, boolean useDevPrivilegeKeys) { super(DisputeManager, keyRing, tradeManager, formatter, disputeSummaryWindow, privateNotificationManager, contractWindow, tradeDetailsWindow, accountAgeWitnessService, - mediatorManager, refundAgentManager, useDevPrivilegeKeys); + mediatorManager, refundAgentManager, daoFacade, useDevPrivilegeKeys); } @Override diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/client/arbitration/ArbitrationClientView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/client/arbitration/ArbitrationClientView.java index bd9d0dbd7b..699463b84f 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/client/arbitration/ArbitrationClientView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/client/arbitration/ArbitrationClientView.java @@ -25,6 +25,7 @@ import bisq.desktop.main.support.dispute.client.DisputeClientView; import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.alert.PrivateNotificationManager; +import bisq.core.dao.DaoFacade; import bisq.core.support.SupportType; import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.DisputeSession; @@ -56,10 +57,11 @@ public class ArbitrationClientView extends DisputeClientView { AccountAgeWitnessService accountAgeWitnessService, MediatorManager mediatorManager, RefundAgentManager refundAgentManager, + DaoFacade daoFacade, @Named(Config.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) { super(arbitrationManager, keyRing, tradeManager, formatter, disputeSummaryWindow, privateNotificationManager, contractWindow, tradeDetailsWindow, accountAgeWitnessService, - mediatorManager, refundAgentManager, useDevPrivilegeKeys); + mediatorManager, refundAgentManager, daoFacade, useDevPrivilegeKeys); } @Override diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/client/mediation/MediationClientView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/client/mediation/MediationClientView.java index 520c5a5237..02170e3835 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/client/mediation/MediationClientView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/client/mediation/MediationClientView.java @@ -26,6 +26,7 @@ import bisq.desktop.main.support.dispute.client.DisputeClientView; import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.alert.PrivateNotificationManager; +import bisq.core.dao.DaoFacade; import bisq.core.locale.Res; import bisq.core.support.SupportType; import bisq.core.support.dispute.Dispute; @@ -58,10 +59,11 @@ public class MediationClientView extends DisputeClientView { AccountAgeWitnessService accountAgeWitnessService, MediatorManager mediatorManager, RefundAgentManager refundAgentManager, + DaoFacade daoFacade, @Named(Config.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) { super(mediationManager, keyRing, tradeManager, formatter, disputeSummaryWindow, privateNotificationManager, contractWindow, tradeDetailsWindow, accountAgeWitnessService, - mediatorManager, refundAgentManager, useDevPrivilegeKeys); + mediatorManager, refundAgentManager, daoFacade, useDevPrivilegeKeys); } @Override diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/client/refund/RefundClientView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/client/refund/RefundClientView.java index a196b67ef8..94cde1f8fe 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/client/refund/RefundClientView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/client/refund/RefundClientView.java @@ -25,6 +25,7 @@ import bisq.desktop.main.support.dispute.client.DisputeClientView; import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.alert.PrivateNotificationManager; +import bisq.core.dao.DaoFacade; import bisq.core.support.SupportType; import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.DisputeSession; @@ -56,10 +57,11 @@ public class RefundClientView extends DisputeClientView { AccountAgeWitnessService accountAgeWitnessService, MediatorManager mediatorManager, RefundAgentManager refundAgentManager, + DaoFacade daoFacade, @Named(Config.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) { super(refundManager, keyRing, tradeManager, formatter, disputeSummaryWindow, privateNotificationManager, contractWindow, tradeDetailsWindow, accountAgeWitnessService, - mediatorManager, refundAgentManager, useDevPrivilegeKeys); + mediatorManager, refundAgentManager, daoFacade, useDevPrivilegeKeys); } @Override From 2943316f90ac17c5dd6dcb599bfaa48423db427c Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 20 Sep 2020 21:31:51 -0500 Subject: [PATCH 23/31] Remove agentsUid from protobuf, rename to uid --- .../main/java/bisq/core/support/dispute/Dispute.java | 12 +++--------- .../bisq/core/support/dispute/DisputeManager.java | 5 ----- .../bisq/core/trade/DelayedPayoutTxValidation.java | 12 ++++++------ proto/src/main/proto/pb.proto | 1 - 4 files changed, 9 insertions(+), 21 deletions(-) diff --git a/core/src/main/java/bisq/core/support/dispute/Dispute.java b/core/src/main/java/bisq/core/support/dispute/Dispute.java index 8087e9a1b3..0bc342bbf5 100644 --- a/core/src/main/java/bisq/core/support/dispute/Dispute.java +++ b/core/src/main/java/bisq/core/support/dispute/Dispute.java @@ -108,9 +108,10 @@ public final class Dispute implements NetworkPayload { @Setter @Nullable private String donationAddressOfDelayedPayoutTx; + // We do not persist uid, it is only used by dispute agents to guarantee an uid. @Setter @Nullable - private String agentsUid; + private transient String uid; /////////////////////////////////////////////////////////////////////////////////////////// @@ -201,6 +202,7 @@ public final class Dispute implements NetworkPayload { this.supportType = supportType; id = tradeId + "_" + traderId; + uid = UUID.randomUUID().toString(); } @Override @@ -238,7 +240,6 @@ public final class Dispute implements NetworkPayload { Optional.ofNullable(mediatorsDisputeResult).ifPresent(result -> builder.setMediatorsDisputeResult(mediatorsDisputeResult)); Optional.ofNullable(delayedPayoutTxId).ifPresent(result -> builder.setDelayedPayoutTxId(delayedPayoutTxId)); Optional.ofNullable(donationAddressOfDelayedPayoutTx).ifPresent(result -> builder.setDonationAddressOfDelayedPayoutTx(donationAddressOfDelayedPayoutTx)); - Optional.ofNullable(agentsUid).ifPresent(result -> builder.setAgentsUid(agentsUid)); return builder.build(); } @@ -287,13 +288,6 @@ public final class Dispute implements NetworkPayload { dispute.setDonationAddressOfDelayedPayoutTx(donationAddressOfDelayedPayoutTx); } - String agentsUid = proto.getAgentsUid(); - if (!agentsUid.isEmpty()) { - dispute.setAgentsUid(agentsUid); - } else { - dispute.setAgentsUid(UUID.randomUUID().toString()); - } - return dispute; } diff --git a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java index fc74453bdb..59cc848ff1 100644 --- a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java +++ b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java @@ -305,8 +305,6 @@ public abstract class DisputeManager> disputesPerDelayedPayoutTxId = new HashMap<>(); Map> disputesPerDepositTxId = new HashMap<>(); disputeList.forEach(dispute -> { - String agentsUid = dispute.getAgentsUid(); + String uid = dispute.getUid(); String tradeId = dispute.getTradeId(); disputesPerTradeId.putIfAbsent(tradeId, new HashSet<>()); Set set = disputesPerTradeId.get(tradeId); - set.add(agentsUid); + set.add(uid); String delayedPayoutTxId = dispute.getDelayedPayoutTxId(); disputesPerDelayedPayoutTxId.putIfAbsent(delayedPayoutTxId, new HashSet<>()); set = disputesPerDelayedPayoutTxId.get(delayedPayoutTxId); - set.add(agentsUid); + set.add(uid); String depositTxId = dispute.getDepositTxId(); disputesPerDepositTxId.putIfAbsent(depositTxId, new HashSet<>()); set = disputesPerDepositTxId.get(depositTxId); - set.add(agentsUid); + set.add(uid); }); return new Tuple3<>(disputesPerTradeId, disputesPerDelayedPayoutTxId, disputesPerDepositTxId); @@ -145,13 +145,13 @@ public class DelayedPayoutTxValidation { String disputeToTestTradeId = disputeToTest.getTradeId(); String disputeToTestDelayedPayoutTxId = disputeToTest.getDelayedPayoutTxId(); String disputeToTestDepositTxId = disputeToTest.getDepositTxId(); - String disputeToTestAgentsUid = disputeToTest.getAgentsUid(); + String disputeToTestUid = disputeToTest.getUid(); checkNotNull(disputeToTestDelayedPayoutTxId, "delayedPayoutTxId must not be null. Trade ID: " + disputeToTestTradeId); checkNotNull(disputeToTestDepositTxId, "depositTxId must not be null. Trade ID: " + disputeToTestTradeId); - checkNotNull(disputeToTestAgentsUid, + checkNotNull(disputeToTestUid, "agentsUid must not be null. Trade ID: " + disputeToTestTradeId); checkArgument(disputesPerTradeId.get(disputeToTestTradeId).size() <= 2, diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 29efbd465b..b10a4a02da 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -793,7 +793,6 @@ message Dispute { string mediators_dispute_result = 25; string delayed_payout_tx_id = 26; string donation_address_of_delayed_payout_tx = 27; - string agents_uid = 28; } message Attachment { From 30e9add4dc641c82dab7499e6f2f05c700912620 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 20 Sep 2020 21:40:21 -0500 Subject: [PATCH 24/31] Refactor: rename DelayedPayoutTxValidation to TradeDataValidation --- .../core/support/dispute/DisputeManager.java | 20 +++++++++---------- ...lidation.java => TradeDataValidation.java} | 4 ++-- .../java/bisq/core/trade/TradeManager.java | 4 ++-- .../BuyerVerifiesFinalDelayedPayoutTx.java | 8 ++++---- .../BuyerVerifiesPreparedDelayedPayoutTx.java | 6 +++--- .../windows/DisputeSummaryWindow.java | 10 +++++----- .../pendingtrades/PendingTradesDataModel.java | 6 +++--- .../steps/buyer/BuyerStep1View.java | 8 ++++---- .../steps/buyer/BuyerStep2View.java | 8 ++++---- .../dispute/agent/DisputeAgentView.java | 6 +++--- 10 files changed, 40 insertions(+), 40 deletions(-) rename core/src/main/java/bisq/core/trade/{DelayedPayoutTxValidation.java => TradeDataValidation.java} (99%) diff --git a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java index 59cc848ff1..3e47353102 100644 --- a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java +++ b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java @@ -36,8 +36,8 @@ import bisq.core.support.dispute.messages.OpenNewDisputeMessage; import bisq.core.support.dispute.messages.PeerOpenedDisputeMessage; import bisq.core.support.messages.ChatMessage; import bisq.core.trade.Contract; -import bisq.core.trade.DelayedPayoutTxValidation; import bisq.core.trade.Trade; +import bisq.core.trade.TradeDataValidation; import bisq.core.trade.TradeManager; import bisq.core.trade.closed.ClosedTradableManager; @@ -92,7 +92,7 @@ public abstract class DisputeManager validationExceptions = + protected final ObservableList validationExceptions = FXCollections.observableArrayList(); @Getter private final KeyPair signatureKeyPair; @@ -262,15 +262,15 @@ public abstract class DisputeManager disputes = getDisputeList().getList(); disputes.forEach(dispute -> { try { - DelayedPayoutTxValidation.validateDonationAddress(dispute, dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade); - } catch (DelayedPayoutTxValidation.AddressException e) { + TradeDataValidation.validateDonationAddress(dispute, dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade); + } catch (TradeDataValidation.AddressException e) { log.error(e.toString()); validationExceptions.add(e); } }); - DelayedPayoutTxValidation.testIfAnyDisputeTriedReplay(disputes, + TradeDataValidation.testIfAnyDisputeTriedReplay(disputes, disputeReplayException -> { log.error(disputeReplayException.toString()); validationExceptions.add(disputeReplayException); @@ -313,9 +313,9 @@ public abstract class DisputeManager disputeList) throws DisputeReplayException { - var tuple = DelayedPayoutTxValidation.getTestReplayHashMaps(disputeList); + var tuple = TradeDataValidation.getTestReplayHashMaps(disputeList); Map> disputesPerTradeId = tuple.first; Map> disputesPerDelayedPayoutTxId = tuple.second; Map> disputesPerDepositTxId = tuple.third; diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index eae1556340..411e31bdf0 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -306,11 +306,11 @@ public class TradeManager implements PersistedDataHost { } try { - DelayedPayoutTxValidation.validatePayoutTx(trade, + TradeDataValidation.validatePayoutTx(trade, trade.getDelayedPayoutTx(), daoFacade, btcWalletService); - } catch (DelayedPayoutTxValidation.ValidationException e) { + } catch (TradeDataValidation.ValidationException e) { log.warn("Delayed payout tx exception, trade {}, exception {}", trade.getId(), e.getMessage()); if (!allowFaultyDelayedTxs) { // We move it to failed trades so it cannot be continued. diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesFinalDelayedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesFinalDelayedPayoutTx.java index 3aaf36a39d..99c1221620 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesFinalDelayedPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesFinalDelayedPayoutTx.java @@ -17,8 +17,8 @@ package bisq.core.trade.protocol.tasks.buyer; -import bisq.core.trade.DelayedPayoutTxValidation; import bisq.core.trade.Trade; +import bisq.core.trade.TradeDataValidation; import bisq.core.trade.protocol.tasks.TradeTask; import bisq.common.taskrunner.TaskRunner; @@ -44,7 +44,7 @@ public class BuyerVerifiesFinalDelayedPayoutTx extends TradeTask { Transaction delayedPayoutTx = trade.getDelayedPayoutTx(); checkNotNull(delayedPayoutTx, "trade.getDelayedPayoutTx() must not be null"); // Check again tx - DelayedPayoutTxValidation.validatePayoutTx(trade, + TradeDataValidation.validatePayoutTx(trade, delayedPayoutTx, processModel.getDaoFacade(), processModel.getBtcWalletService()); @@ -52,10 +52,10 @@ public class BuyerVerifiesFinalDelayedPayoutTx extends TradeTask { // Now as we know the deposit tx we can also verify the input Transaction depositTx = trade.getDepositTx(); checkNotNull(depositTx, "trade.getDepositTx() must not be null"); - DelayedPayoutTxValidation.validatePayoutTxInput(depositTx, delayedPayoutTx); + TradeDataValidation.validatePayoutTxInput(depositTx, delayedPayoutTx); complete(); - } catch (DelayedPayoutTxValidation.ValidationException e) { + } catch (TradeDataValidation.ValidationException e) { failed(e.getMessage()); } catch (Throwable t) { failed(t); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesPreparedDelayedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesPreparedDelayedPayoutTx.java index 7853767d27..a57be35a15 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesPreparedDelayedPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesPreparedDelayedPayoutTx.java @@ -17,8 +17,8 @@ package bisq.core.trade.protocol.tasks.buyer; -import bisq.core.trade.DelayedPayoutTxValidation; import bisq.core.trade.Trade; +import bisq.core.trade.TradeDataValidation; import bisq.core.trade.protocol.tasks.TradeTask; import bisq.common.taskrunner.TaskRunner; @@ -37,13 +37,13 @@ public class BuyerVerifiesPreparedDelayedPayoutTx extends TradeTask { try { runInterceptHook(); - DelayedPayoutTxValidation.validatePayoutTx(trade, + TradeDataValidation.validatePayoutTx(trade, processModel.getPreparedDelayedPayoutTx(), processModel.getDaoFacade(), processModel.getBtcWalletService()); complete(); - } catch (DelayedPayoutTxValidation.ValidationException e) { + } catch (TradeDataValidation.ValidationException e) { failed(e.getMessage()); } catch (Throwable t) { failed(t); diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java index da12af2908..9cf8613cc2 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java @@ -47,7 +47,7 @@ import bisq.core.support.dispute.DisputeResult; import bisq.core.support.dispute.mediation.MediationManager; import bisq.core.support.dispute.refund.RefundManager; import bisq.core.trade.Contract; -import bisq.core.trade.DelayedPayoutTxValidation; +import bisq.core.trade.TradeDataValidation; import bisq.core.util.FormattingUtils; import bisq.core.util.ParsingUtils; import bisq.core.util.coin.CoinFormatter; @@ -757,10 +757,10 @@ public class DisputeSummaryWindow extends Overlay { private void doCloseIfValid(Button closeTicketButton) { var disputeManager = checkNotNull(getDisputeManager(dispute)); try { - DelayedPayoutTxValidation.validateDonationAddress(dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade); - DelayedPayoutTxValidation.testIfDisputeTriesReplay(dispute, disputeManager.getDisputesAsObservableList()); + TradeDataValidation.validateDonationAddress(dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade); + TradeDataValidation.testIfDisputeTriesReplay(dispute, disputeManager.getDisputesAsObservableList()); doClose(closeTicketButton); - } catch (DelayedPayoutTxValidation.AddressException exception) { + } catch (TradeDataValidation.AddressException exception) { String addressAsString = dispute.getDonationAddressOfDelayedPayoutTx(); String tradeId = dispute.getTradeId(); @@ -788,7 +788,7 @@ public class DisputeSummaryWindow extends Overlay { Res.get("support.warning.disputesWithInvalidDonationAddress.refundAgent"))) .show(); } - } catch (DelayedPayoutTxValidation.DisputeReplayException exception) { + } catch (TradeDataValidation.DisputeReplayException exception) { if (disputeManager instanceof MediationManager) { new Popup().width(900) .warning(exception.getMessage()) diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java index 04709f358c..ae7f141542 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java @@ -47,9 +47,9 @@ import bisq.core.support.dispute.refund.RefundManager; import bisq.core.support.messages.ChatMessage; import bisq.core.support.traderchat.TraderChatManager; import bisq.core.trade.BuyerTrade; -import bisq.core.trade.DelayedPayoutTxValidation; import bisq.core.trade.SellerTrade; import bisq.core.trade.Trade; +import bisq.core.trade.TradeDataValidation; import bisq.core.trade.TradeManager; import bisq.core.trade.messages.RefreshTradeStateRequest; import bisq.core.user.Preferences; @@ -541,12 +541,12 @@ public class PendingTradesDataModel extends ActivatableDataModel { AtomicReference donationAddressString = new AtomicReference<>(""); Transaction delayedPayoutTx = trade.getDelayedPayoutTx(); try { - DelayedPayoutTxValidation.validatePayoutTx(trade, + TradeDataValidation.validatePayoutTx(trade, delayedPayoutTx, daoFacade, btcWalletService, donationAddressString::set); - } catch (DelayedPayoutTxValidation.ValidationException e) { + } catch (TradeDataValidation.ValidationException e) { // The peer sent us an invalid donation address. We do not return here as we don't want to break // mediation/arbitration and log only the issue. The dispute agent will run validation as well and will get // a popup displayed to react. diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep1View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep1View.java index 716d6bae04..e094ac9ccd 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep1View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep1View.java @@ -22,7 +22,7 @@ import bisq.desktop.main.portfolio.pendingtrades.PendingTradesViewModel; import bisq.desktop.main.portfolio.pendingtrades.steps.TradeStepView; import bisq.core.locale.Res; -import bisq.core.trade.DelayedPayoutTxValidation; +import bisq.core.trade.TradeDataValidation; import bisq.common.UserThread; @@ -99,15 +99,15 @@ public class BuyerStep1View extends TradeStepView { private void validatePayoutTx() { try { - DelayedPayoutTxValidation.validatePayoutTx(trade, + TradeDataValidation.validatePayoutTx(trade, trade.getDelayedPayoutTx(), model.dataModel.daoFacade, model.dataModel.btcWalletService); - } catch (DelayedPayoutTxValidation.MissingTxException ignore) { + } catch (TradeDataValidation.MissingTxException ignore) { // We don't react on those errors as a failed trade might get listed initially but getting removed from the // trade manager after initPendingTrades which happens after activate might be called. log.error(""); - } catch (DelayedPayoutTxValidation.ValidationException e) { + } catch (TradeDataValidation.ValidationException e) { if (!model.dataModel.tradeManager.isAllowFaultyDelayedTxs()) { new Popup().warning(Res.get("portfolio.pending.invalidDelayedPayoutTx", e.getMessage())).show(); } 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 6096e0cf3d..13fb709d7d 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 @@ -70,8 +70,8 @@ import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.payment.payload.PaymentMethod; import bisq.core.payment.payload.USPostalMoneyOrderAccountPayload; import bisq.core.payment.payload.WesternUnionAccountPayload; -import bisq.core.trade.DelayedPayoutTxValidation; import bisq.core.trade.Trade; +import bisq.core.trade.TradeDataValidation; import bisq.core.user.DontShowAgainLookup; import bisq.common.Timer; @@ -635,15 +635,15 @@ public class BuyerStep2View extends TradeStepView { private void validatePayoutTx() { try { - DelayedPayoutTxValidation.validatePayoutTx(trade, + TradeDataValidation.validatePayoutTx(trade, trade.getDelayedPayoutTx(), model.dataModel.daoFacade, model.dataModel.btcWalletService); - } catch (DelayedPayoutTxValidation.MissingTxException ignore) { + } catch (TradeDataValidation.MissingTxException ignore) { // We don't react on those errors as a failed trade might get listed initially but getting removed from the // trade manager after initPendingTrades which happens after activate might be called. log.error(""); - } catch (DelayedPayoutTxValidation.ValidationException e) { + } catch (TradeDataValidation.ValidationException e) { if (!model.dataModel.tradeManager.isAllowFaultyDelayedTxs()) { new Popup().warning(Res.get("portfolio.pending.invalidDelayedPayoutTx", e.getMessage())).show(); } diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java index b8241353ad..7866e2260e 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java @@ -36,7 +36,7 @@ import bisq.core.support.dispute.DisputeSession; import bisq.core.support.dispute.agent.MultipleHolderNameDetection; import bisq.core.support.dispute.mediation.mediator.MediatorManager; import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; -import bisq.core.trade.DelayedPayoutTxValidation; +import bisq.core.trade.TradeDataValidation; import bisq.core.trade.TradeManager; import bisq.core.user.DontShowAgainLookup; import bisq.core.util.coin.CoinFormatter; @@ -62,7 +62,7 @@ import javafx.collections.ListChangeListener; import java.util.List; -import static bisq.core.trade.DelayedPayoutTxValidation.ValidationException; +import static bisq.core.trade.TradeDataValidation.ValidationException; import static bisq.desktop.util.FormBuilder.getIconForLabel; public abstract class DisputeAgentView extends DisputeView implements MultipleHolderNameDetection.Listener { @@ -136,7 +136,7 @@ public abstract class DisputeAgentView extends DisputeView implements MultipleHo .filter(ex -> !ex.getDispute().isClosed()) .forEach(ex -> { Dispute dispute = ex.getDispute(); - if (ex instanceof DelayedPayoutTxValidation.AddressException) { + if (ex instanceof TradeDataValidation.AddressException) { new Popup().width(900).warning(Res.get("support.warning.disputesWithInvalidDonationAddress", dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade.getAllDonationAddresses(), From 3206c6215153b413666a8d1da6366a83978d43a8 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 20 Sep 2020 21:58:32 -0500 Subject: [PATCH 25/31] Refactor: Move RegexValidator from bisq.desktop.util.validation to bisq.core.util.validation Add RegexValidatorFactory Move regex tests from GUIUtilTest to new RegexValidatorTest class --- .../core}/util/validation/RegexValidator.java | 4 +- .../validation/RegexValidatorFactory.java | 174 ++++++++++ .../bisq/core/util/RegexValidatorTest.java | 312 ++++++++++++++++++ .../paymentmethods/JapanBankTransferForm.java | 2 +- .../main/dao/governance/ProposalDisplay.java | 4 +- .../overlays/windows/SetXmrTxKeyWindow.java | 4 +- .../settings/network/NetworkSettingsView.java | 5 +- .../settings/preferences/PreferencesView.java | 13 +- .../main/java/bisq/desktop/util/GUIUtil.java | 155 --------- .../validation/AdvancedCashValidator.java | 1 + .../InteracETransferAnswerValidator.java | 1 + .../InteracETransferQuestionValidator.java | 1 + .../JapanBankAccountNameValidator.java | 5 +- .../JapanBankBranchNameValidator.java | 5 +- .../java/bisq/desktop/util/GUIUtilTest.java | 275 --------------- .../validation/AdvancedCashValidatorTest.java | 1 + .../InteracETransferAnswerValidatorTest.java | 1 + ...InteracETransferQuestionValidatorTest.java | 1 + .../InteracETransferValidatorTest.java | 1 + .../util/validation/RegexValidatorTest.java | 1 + 20 files changed, 519 insertions(+), 447 deletions(-) rename {desktop/src/main/java/bisq/desktop => core/src/main/java/bisq/core}/util/validation/RegexValidator.java (91%) create mode 100644 core/src/main/java/bisq/core/util/validation/RegexValidatorFactory.java create mode 100644 core/src/test/java/bisq/core/util/RegexValidatorTest.java diff --git a/desktop/src/main/java/bisq/desktop/util/validation/RegexValidator.java b/core/src/main/java/bisq/core/util/validation/RegexValidator.java similarity index 91% rename from desktop/src/main/java/bisq/desktop/util/validation/RegexValidator.java rename to core/src/main/java/bisq/core/util/validation/RegexValidator.java index 72ed1450bf..f9096ec2ce 100644 --- a/desktop/src/main/java/bisq/desktop/util/validation/RegexValidator.java +++ b/core/src/main/java/bisq/core/util/validation/RegexValidator.java @@ -1,9 +1,9 @@ -package bisq.desktop.util.validation; +package bisq.core.util.validation; import bisq.core.locale.Res; -import bisq.core.util.validation.InputValidator; public class RegexValidator extends InputValidator { + private String pattern; private String errorMessage; diff --git a/core/src/main/java/bisq/core/util/validation/RegexValidatorFactory.java b/core/src/main/java/bisq/core/util/validation/RegexValidatorFactory.java new file mode 100644 index 0000000000..2eab99db9a --- /dev/null +++ b/core/src/main/java/bisq/core/util/validation/RegexValidatorFactory.java @@ -0,0 +1,174 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.util.validation; + +public class RegexValidatorFactory { + public static RegexValidator addressRegexValidator() { + RegexValidator regexValidator = new RegexValidator(); + String portRegexPattern = "(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])"; + String onionV2RegexPattern = String.format("[a-zA-Z2-7]{16}\\.onion(?:\\:%1$s)?", portRegexPattern); + String onionV3RegexPattern = String.format("[a-zA-Z2-7]{56}\\.onion(?:\\:%1$s)?", portRegexPattern); + String ipv4RegexPattern = String.format("(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}" + + "(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" + + "(?:\\:%1$s)?", portRegexPattern); + String ipv6RegexPattern = "(" + + "([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|" + // 1:2:3:4:5:6:7:8 + "([0-9a-fA-F]{1,4}:){1,7}:|" + // 1:: 1:2:3:4:5:6:7:: + "([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|" + // 1::8 1:2:3:4:5:6::8 1:2:3:4:5:6::8 + "([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|" + // 1::7:8 1:2:3:4:5::7:8 1:2:3:4:5::8 + "([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|" + // 1::6:7:8 1:2:3:4::6:7:8 1:2:3:4::8 + "([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|" + // 1::5:6:7:8 1:2:3::5:6:7:8 1:2:3::8 + "([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|" + // 1::4:5:6:7:8 1:2::4:5:6:7:8 1:2::8 + "[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|" + // 1::3:4:5:6:7:8 1::3:4:5:6:7:8 1::8 + ":((:[0-9a-fA-F]{1,4}){1,7}|:)|" + // ::2:3:4:5:6:7:8 ::2:3:4:5:6:7:8 ::8 :: + "fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|" + // fe80::7:8%eth0 fe80::7:8%1 + "::(ffff(:0{1,4}){0,1}:){0,1}" + // (link-local IPv6 addresses with zone index) + "((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}" + + "(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|" + // ::255.255.255.255 ::ffff:255.255.255.255 ::ffff:0:255.255.255.255 + "([0-9a-fA-F]{1,4}:){1,4}:" + // (IPv4-mapped IPv6 addresses and IPv4-translated addresses) + "((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}" + + "(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])" + // 2001:db8:3:4::192.0.2.33 64:ff9b::192.0.2.33 + ")"; // (IPv4-Embedded IPv6 Address) + ipv6RegexPattern = String.format("(?:%1$s)|(?:\\[%1$s\\]\\:%2$s)", ipv6RegexPattern, portRegexPattern); + String fqdnRegexPattern = String.format("(((?!-)[a-zA-Z0-9-]{1,63}(?. + */ + +package bisq.core.util; + +import bisq.core.locale.GlobalSettings; +import bisq.core.locale.Res; +import bisq.core.util.validation.RegexValidator; +import bisq.core.util.validation.RegexValidatorFactory; + +import java.util.Locale; + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class RegexValidatorTest { + @Before + public void setup() { + Locale.setDefault(new Locale("en", "US")); + GlobalSettings.setLocale(new Locale("en", "US")); + Res.setBaseCurrencyCode("BTC"); + Res.setBaseCurrencyName("Bitcoin"); + } + + @Test + public void testAddressRegexValidator() { + RegexValidator regexValidator = RegexValidatorFactory.addressRegexValidator(); + + assertTrue(regexValidator.validate("").isValid); + assertFalse(regexValidator.validate(" ").isValid); + + // onion V2 addresses + assertTrue(regexValidator.validate("abcdefghij234567.onion").isValid); + assertTrue(regexValidator.validate("abcdefghijklmnop.onion,abcdefghijklmnop.onion").isValid); + assertTrue(regexValidator.validate("abcdefghijklmnop.onion, abcdefghijklmnop.onion").isValid); + assertTrue(regexValidator.validate("qrstuvwxyzABCDEF.onion,qrstuvwxyzABCDEF.onion,aaaaaaaaaaaaaaaa.onion").isValid); + assertTrue(regexValidator.validate("GHIJKLMNOPQRSTUV.onion:9999").isValid); + assertTrue(regexValidator.validate("WXYZ234567abcdef.onion,GHIJKLMNOPQRSTUV.onion:9999").isValid); + assertTrue(regexValidator.validate("aaaaaaaaaaaaaaaa.onion:9999,WXYZ234567abcdef.onion:9999,2222222222222222.onion:9999").isValid); + assertFalse(regexValidator.validate("abcd.onion").isValid); + assertFalse(regexValidator.validate("abcdefghijklmnop,abcdefghijklmnop.onion").isValid); + assertFalse(regexValidator.validate("abcdefghi2345689.onion:9999").isValid); + assertFalse(regexValidator.validate("onion:9999,abcdefghijklmnop.onion:9999").isValid); + assertFalse(regexValidator.validate("abcdefghijklmnop.onion:").isValid); + + // onion v3 addresses + assertFalse(regexValidator.validate("32zzibxmqi2ybxpqyggwwuwz7a3lbvtzoloti7cxoevyvijexvgsfei.onion:8333").isValid); // 1 missing char + assertTrue(regexValidator.validate("wizseedscybbttk4bmb2lzvbuk2jtect37lcpva4l3twktmkzemwbead.onion:8000").isValid); + + // ipv4 addresses + assertTrue(regexValidator.validate("12.34.56.78").isValid); + assertTrue(regexValidator.validate("12.34.56.78,87.65.43.21").isValid); + assertTrue(regexValidator.validate("12.34.56.78:8888").isValid); + assertFalse(regexValidator.validate("12.34.56.788").isValid); + assertFalse(regexValidator.validate("12.34.56.78:").isValid); + + // ipv6 addresses + assertTrue(regexValidator.validate("FE80:0000:0000:0000:0202:B3FF:FE1E:8329").isValid); + assertTrue(regexValidator.validate("FE80::0202:B3FF:FE1E:8329").isValid); + assertTrue(regexValidator.validate("FE80::0202:B3FF:FE1E:8329,FE80:0000:0000:0000:0202:B3FF:FE1E:8329").isValid); + assertTrue(regexValidator.validate("::1").isValid); + assertTrue(regexValidator.validate("fe80::").isValid); + assertTrue(regexValidator.validate("2001::").isValid); + assertTrue(regexValidator.validate("[::1]:8333").isValid); + assertTrue(regexValidator.validate("[FE80::0202:B3FF:FE1E:8329]:8333").isValid); + assertTrue(regexValidator.validate("[2001:db8::1]:80").isValid); + assertTrue(regexValidator.validate("[aaaa::bbbb]:8333").isValid); + assertFalse(regexValidator.validate("1200:0000:AB00:1234:O000:2552:7777:1313").isValid); + + // fqdn addresses + assertTrue(regexValidator.validate("example.com").isValid); + assertTrue(regexValidator.validate("mynode.local:8333").isValid); + assertTrue(regexValidator.validate("foo.example.com,bar.example.com").isValid); + assertTrue(regexValidator.validate("foo.example.com:8333,bar.example.com:8333").isValid); + + assertFalse(regexValidator.validate("mynode.local:65536").isValid); + assertFalse(regexValidator.validate("-example.com").isValid); + assertFalse(regexValidator.validate("example-.com").isValid); + } + + @Test + public void testOnionAddressRegexValidator() { + RegexValidator regexValidator = RegexValidatorFactory.onionAddressRegexValidator(); + + assertTrue(regexValidator.validate("").isValid); + assertFalse(regexValidator.validate(" ").isValid); + + // onion V2 addresses + assertTrue(regexValidator.validate("abcdefghij234567.onion").isValid); + assertTrue(regexValidator.validate("abcdefghijklmnop.onion,abcdefghijklmnop.onion").isValid); + assertTrue(regexValidator.validate("abcdefghijklmnop.onion, abcdefghijklmnop.onion").isValid); + assertTrue(regexValidator.validate("qrstuvwxyzABCDEF.onion,qrstuvwxyzABCDEF.onion,aaaaaaaaaaaaaaaa.onion").isValid); + assertTrue(regexValidator.validate("GHIJKLMNOPQRSTUV.onion:9999").isValid); + assertTrue(regexValidator.validate("WXYZ234567abcdef.onion,GHIJKLMNOPQRSTUV.onion:9999").isValid); + assertTrue(regexValidator.validate("aaaaaaaaaaaaaaaa.onion:9999,WXYZ234567abcdef.onion:9999,2222222222222222.onion:9999").isValid); + assertFalse(regexValidator.validate("abcd.onion").isValid); + assertFalse(regexValidator.validate("abcdefghijklmnop,abcdefghijklmnop.onion").isValid); + assertFalse(regexValidator.validate("abcdefghi2345689.onion:9999").isValid); + assertFalse(regexValidator.validate("onion:9999,abcdefghijklmnop.onion:9999").isValid); + assertFalse(regexValidator.validate("abcdefghijklmnop.onion:").isValid); + + // onion v3 addresses + assertFalse(regexValidator.validate("32zzibxmqi2ybxpqyggwwuwz7a3lbvtzoloti7cxoevyvijexvgsfei.onion:8333").isValid); // 1 missing char + assertTrue(regexValidator.validate("wizseedscybbttk4bmb2lzvbuk2jtect37lcpva4l3twktmkzemwbead.onion:8000").isValid); + + } + + @Test + public void testLocalnetAddressRegexValidator() { + RegexValidator regexValidator = RegexValidatorFactory.localnetAddressRegexValidator(); + + assertTrue(regexValidator.validate("").isValid); + assertFalse(regexValidator.validate(" ").isValid); + + // onion V2 addresses + assertFalse(regexValidator.validate("abcdefghij234567.onion").isValid); + assertFalse(regexValidator.validate("abcdefghijklmnop.onion,abcdefghijklmnop.onion").isValid); + assertFalse(regexValidator.validate("abcdefghijklmnop.onion, abcdefghijklmnop.onion").isValid); + assertFalse(regexValidator.validate("qrstuvwxyzABCDEF.onion,qrstuvwxyzABCDEF.onion,aaaaaaaaaaaaaaaa.onion").isValid); + assertFalse(regexValidator.validate("GHIJKLMNOPQRSTUV.onion:9999").isValid); + assertFalse(regexValidator.validate("WXYZ234567abcdef.onion,GHIJKLMNOPQRSTUV.onion:9999").isValid); + assertFalse(regexValidator.validate("aaaaaaaaaaaaaaaa.onion:9999,WXYZ234567abcdef.onion:9999,2222222222222222.onion:9999").isValid); + assertFalse(regexValidator.validate("abcd.onion").isValid); + assertFalse(regexValidator.validate("abcdefghijklmnop,abcdefghijklmnop.onion").isValid); + assertFalse(regexValidator.validate("abcdefghi2345689.onion:9999").isValid); + assertFalse(regexValidator.validate("onion:9999,abcdefghijklmnop.onion:9999").isValid); + assertFalse(regexValidator.validate("abcdefghijklmnop.onion:").isValid); + + // onion v3 addresses + assertFalse(regexValidator.validate("32zzibxmqi2ybxpqyggwwuwz7a3lbvtzoloti7cxoevyvijexvgsfei.onion:8333").isValid); // 1 missing char + assertFalse(regexValidator.validate("wizseedscybbttk4bmb2lzvbuk2jtect37lcpva4l3twktmkzemwbead.onion:8000").isValid); + + // ipv4 addresses + assertFalse(regexValidator.validate("12.34.56.78").isValid); + assertFalse(regexValidator.validate("12.34.56.78,87.65.43.21").isValid); + assertFalse(regexValidator.validate("12.34.56.78:8888").isValid); + assertFalse(regexValidator.validate("12.34.56.788").isValid); + assertFalse(regexValidator.validate("12.34.56.78:").isValid); + + // ipv4 local addresses + assertTrue(regexValidator.validate("10.10.10.10").isValid); + assertTrue(regexValidator.validate("172.19.1.1").isValid); + assertTrue(regexValidator.validate("172.19.1.1").isValid); + assertTrue(regexValidator.validate("192.168.1.1").isValid); + assertTrue(regexValidator.validate("192.168.1.1,172.16.1.1").isValid); + assertTrue(regexValidator.validate("192.168.1.1:8888,192.168.1.2:8888").isValid); + assertFalse(regexValidator.validate("192.168.1.888").isValid); + assertFalse(regexValidator.validate("192.168.1.1:").isValid); + + // ipv4 autolocal addresses + assertTrue(regexValidator.validate("169.254.123.232").isValid); + + // ipv6 local addresses + assertTrue(regexValidator.validate("fe80:2:3:4:5:6:7:8").isValid); + assertTrue(regexValidator.validate("fe80::").isValid); + assertTrue(regexValidator.validate("fc00::").isValid); + assertTrue(regexValidator.validate("fd00::,fe80::1").isValid); + assertTrue(regexValidator.validate("fd00::8").isValid); + assertTrue(regexValidator.validate("fd00::7:8").isValid); + assertTrue(regexValidator.validate("fd00::6:7:8").isValid); + assertTrue(regexValidator.validate("fd00::5:6:7:8").isValid); + assertTrue(regexValidator.validate("fd00::4:5:6:7:8").isValid); + assertTrue(regexValidator.validate("fd00::3:4:5:6:7:8").isValid); + assertTrue(regexValidator.validate("fd00:2:3:4:5:6:7:8").isValid); + assertTrue(regexValidator.validate("fd00::0202:B3FF:FE1E:8329").isValid); + assertTrue(regexValidator.validate("fd00::0202:B3FF:FE1E:8329,FE80::0202:B3FF:FE1E:8329").isValid); + // ipv6 local with optional port at the end + assertTrue(regexValidator.validate("[fd00::1]:8081").isValid); + assertTrue(regexValidator.validate("[fd00::1]:8081,[fc00::1]:8081").isValid); + assertTrue(regexValidator.validate("[FE80::0202:B3FF:FE1E:8329]:8333").isValid); + + // ipv6 loopback + assertFalse(regexValidator.validate("::1").isValid); + + // ipv6 unicast + assertFalse(regexValidator.validate("2001::").isValid); + assertFalse(regexValidator.validate("[::1]:8333").isValid); + assertFalse(regexValidator.validate("[2001:db8::1]:80").isValid); + assertFalse(regexValidator.validate("[aaaa::bbbb]:8333").isValid); + assertFalse(regexValidator.validate("1200:0000:AB00:1234:O000:2552:7777:1313").isValid); + + // *.local fqdn hostnames + assertTrue(regexValidator.validate("mynode.local").isValid); + assertTrue(regexValidator.validate("mynode.local:8081").isValid); + + // non-local fqdn hostnames + assertFalse(regexValidator.validate("example.com").isValid); + assertFalse(regexValidator.validate("foo.example.com,bar.example.com").isValid); + assertFalse(regexValidator.validate("foo.example.com:8333,bar.example.com:8333").isValid); + + // invalid fqdn hostnames + assertFalse(regexValidator.validate("mynode.local:65536").isValid); + assertFalse(regexValidator.validate("-example.com").isValid); + assertFalse(regexValidator.validate("example-.com").isValid); + } + + @Test + public void testLocalhostAddressRegexValidator() { + RegexValidator regexValidator = RegexValidatorFactory.localhostAddressRegexValidator(); + + assertTrue(regexValidator.validate("").isValid); + assertFalse(regexValidator.validate(" ").isValid); + + // onion V2 addresses + assertFalse(regexValidator.validate("abcdefghij234567.onion").isValid); + assertFalse(regexValidator.validate("abcdefghijklmnop.onion,abcdefghijklmnop.onion").isValid); + assertFalse(regexValidator.validate("abcdefghijklmnop.onion, abcdefghijklmnop.onion").isValid); + assertFalse(regexValidator.validate("qrstuvwxyzABCDEF.onion,qrstuvwxyzABCDEF.onion,aaaaaaaaaaaaaaaa.onion").isValid); + assertFalse(regexValidator.validate("GHIJKLMNOPQRSTUV.onion:9999").isValid); + assertFalse(regexValidator.validate("WXYZ234567abcdef.onion,GHIJKLMNOPQRSTUV.onion:9999").isValid); + assertFalse(regexValidator.validate("aaaaaaaaaaaaaaaa.onion:9999,WXYZ234567abcdef.onion:9999,2222222222222222.onion:9999").isValid); + assertFalse(regexValidator.validate("abcd.onion").isValid); + assertFalse(regexValidator.validate("abcdefghijklmnop,abcdefghijklmnop.onion").isValid); + assertFalse(regexValidator.validate("abcdefghi2345689.onion:9999").isValid); + assertFalse(regexValidator.validate("onion:9999,abcdefghijklmnop.onion:9999").isValid); + assertFalse(regexValidator.validate("abcdefghijklmnop.onion:").isValid); + + // onion v3 addresses + assertFalse(regexValidator.validate("32zzibxmqi2ybxpqyggwwuwz7a3lbvtzoloti7cxoevyvijexvgsfei.onion:8333").isValid); // 1 missing char + assertFalse(regexValidator.validate("wizseedscybbttk4bmb2lzvbuk2jtect37lcpva4l3twktmkzemwbead.onion:8000").isValid); + + // ipv4 addresses + assertFalse(regexValidator.validate("12.34.56.78").isValid); + assertFalse(regexValidator.validate("12.34.56.78,87.65.43.21").isValid); + assertFalse(regexValidator.validate("12.34.56.78:8888").isValid); + assertFalse(regexValidator.validate("12.34.56.788").isValid); + assertFalse(regexValidator.validate("12.34.56.78:").isValid); + + // ipv4 loopback addresses + assertTrue(regexValidator.validate("127.0.0.1").isValid); + assertTrue(regexValidator.validate("127.0.1.1").isValid); + + // ipv4 local addresses + assertFalse(regexValidator.validate("10.10.10.10").isValid); + assertFalse(regexValidator.validate("172.19.1.1").isValid); + assertFalse(regexValidator.validate("172.19.1.1").isValid); + assertFalse(regexValidator.validate("192.168.1.1").isValid); + assertFalse(regexValidator.validate("192.168.1.1,172.16.1.1").isValid); + assertFalse(regexValidator.validate("192.168.1.1:8888,192.168.1.2:8888").isValid); + assertFalse(regexValidator.validate("192.168.1.888").isValid); + assertFalse(regexValidator.validate("192.168.1.1:").isValid); + + // ipv4 autolocal addresses + assertFalse(regexValidator.validate("169.254.123.232").isValid); + + // ipv6 local addresses + assertFalse(regexValidator.validate("fe80::").isValid); + assertFalse(regexValidator.validate("fc00::").isValid); + assertFalse(regexValidator.validate("fd00::8").isValid); + assertFalse(regexValidator.validate("fd00::7:8").isValid); + assertFalse(regexValidator.validate("fd00::6:7:8").isValid); + assertFalse(regexValidator.validate("fd00::5:6:7:8").isValid); + assertFalse(regexValidator.validate("fd00::3:4:5:6:7:8").isValid); + assertFalse(regexValidator.validate("fd00::4:5:6:7:8").isValid); + assertFalse(regexValidator.validate("fd00:2:3:4:5:6:7:8").isValid); + assertFalse(regexValidator.validate("fd00::0202:B3FF:FE1E:8329").isValid); + + assertFalse(regexValidator.validate("FE80:0000:0000:0000:0202:B3FF:FE1E:8329").isValid); + assertFalse(regexValidator.validate("FE80::0202:B3FF:FE1E:8329").isValid); + assertFalse(regexValidator.validate("FE80::0202:B3FF:FE1E:8329,FE80:0000:0000:0000:0202:B3FF:FE1E:8329").isValid); + // ipv6 local with optional port at the end + assertFalse(regexValidator.validate("[fd00::1]:8081").isValid); + assertFalse(regexValidator.validate("[fd00::1]:8081,[fc00::1]:8081").isValid); + + // ipv6 loopback + assertTrue(regexValidator.validate("::1").isValid); + assertTrue(regexValidator.validate("::2").isValid); + assertTrue(regexValidator.validate("[::1]:8333").isValid); + + // ipv6 unicast + assertFalse(regexValidator.validate("2001::").isValid); + assertFalse(regexValidator.validate("[FE80::0202:B3FF:FE1E:8329]:8333").isValid); + assertFalse(regexValidator.validate("[2001:db8::1]:80").isValid); + assertFalse(regexValidator.validate("[aaaa::bbbb]:8333").isValid); + assertFalse(regexValidator.validate("1200:0000:AB00:1234:O000:2552:7777:1313").isValid); + + // localhost fqdn hostnames + assertTrue(regexValidator.validate("localhost").isValid); + assertTrue(regexValidator.validate("localhost:8081").isValid); + + // local fqdn hostnames + assertFalse(regexValidator.validate("mynode.local:8081").isValid); + + // non-local fqdn hostnames + assertFalse(regexValidator.validate("example.com").isValid); + assertFalse(regexValidator.validate("foo.example.com,bar.example.com").isValid); + assertFalse(regexValidator.validate("foo.example.com:8333,bar.example.com:8333").isValid); + + // invalid fqdn hostnames + assertFalse(regexValidator.validate("mynode.local:65536").isValid); + assertFalse(regexValidator.validate("-example.com").isValid); + assertFalse(regexValidator.validate("example-.com").isValid); + } + +} diff --git a/desktop/src/main/java/bisq/desktop/components/paymentmethods/JapanBankTransferForm.java b/desktop/src/main/java/bisq/desktop/components/paymentmethods/JapanBankTransferForm.java index ff22c42153..22eac439a5 100644 --- a/desktop/src/main/java/bisq/desktop/components/paymentmethods/JapanBankTransferForm.java +++ b/desktop/src/main/java/bisq/desktop/components/paymentmethods/JapanBankTransferForm.java @@ -27,7 +27,6 @@ import bisq.desktop.util.validation.JapanBankBranchCodeValidator; import bisq.desktop.util.validation.JapanBankBranchNameValidator; import bisq.desktop.util.validation.JapanBankTransferValidator; import bisq.desktop.util.validation.LengthValidator; -import bisq.desktop.util.validation.RegexValidator; import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.locale.Res; @@ -38,6 +37,7 @@ import bisq.core.payment.payload.JapanBankAccountPayload; import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.util.coin.CoinFormatter; import bisq.core.util.validation.InputValidator; +import bisq.core.util.validation.RegexValidator; import bisq.common.util.Tuple2; import bisq.common.util.Tuple3; diff --git a/desktop/src/main/java/bisq/desktop/main/dao/governance/ProposalDisplay.java b/desktop/src/main/java/bisq/desktop/main/dao/governance/ProposalDisplay.java index f2b925081e..edf96413c8 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/governance/ProposalDisplay.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/governance/ProposalDisplay.java @@ -29,9 +29,7 @@ import bisq.desktop.util.FormBuilder; import bisq.desktop.util.GUIUtil; import bisq.desktop.util.Layout; import bisq.desktop.util.validation.BsqValidator; -import bisq.desktop.util.validation.RegexValidator; -import bisq.common.config.BaseCurrencyNetwork; import bisq.core.dao.DaoFacade; import bisq.core.dao.governance.bond.Bond; import bisq.core.dao.governance.bond.role.BondedRole; @@ -60,9 +58,11 @@ import bisq.core.locale.Res; import bisq.core.user.Preferences; import bisq.core.util.coin.BsqFormatter; import bisq.core.util.validation.InputValidator; +import bisq.core.util.validation.RegexValidator; import bisq.asset.Asset; +import bisq.common.config.BaseCurrencyNetwork; import bisq.common.util.Tuple3; import org.bitcoinj.core.Coin; diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java index 517f57be08..5aab52269a 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java @@ -19,10 +19,10 @@ package bisq.desktop.main.overlays.windows; import bisq.desktop.components.InputTextField; import bisq.desktop.main.overlays.Overlay; -import bisq.desktop.util.validation.RegexValidator; import bisq.core.locale.Res; import bisq.core.trade.txproof.xmr.XmrTxProofModel; +import bisq.core.util.validation.RegexValidator; import javafx.scene.layout.ColumnConstraints; import javafx.scene.layout.GridPane; @@ -36,8 +36,8 @@ import lombok.Getter; import javax.annotation.Nullable; import static bisq.common.app.DevEnv.isDevMode; -import static bisq.desktop.util.FormBuilder.addMultilineLabel; import static bisq.desktop.util.FormBuilder.addInputTextField; +import static bisq.desktop.util.FormBuilder.addMultilineLabel; import static javafx.beans.binding.Bindings.createBooleanBinding; public class SetXmrTxKeyWindow extends Overlay { diff --git a/desktop/src/main/java/bisq/desktop/main/settings/network/NetworkSettingsView.java b/desktop/src/main/java/bisq/desktop/main/settings/network/NetworkSettingsView.java index 750cfe72fc..dd1d53be10 100644 --- a/desktop/src/main/java/bisq/desktop/main/settings/network/NetworkSettingsView.java +++ b/desktop/src/main/java/bisq/desktop/main/settings/network/NetworkSettingsView.java @@ -27,7 +27,6 @@ import bisq.desktop.components.TitledGroupBg; import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.main.overlays.windows.TorNetworkSettingsWindow; import bisq.desktop.util.GUIUtil; -import bisq.desktop.util.validation.RegexValidator; import bisq.core.btc.nodes.BtcNodes; import bisq.core.btc.nodes.LocalBitcoinNode; @@ -37,6 +36,8 @@ import bisq.core.filter.FilterManager; import bisq.core.locale.Res; import bisq.core.user.Preferences; import bisq.core.util.FormattingUtils; +import bisq.core.util.validation.RegexValidator; +import bisq.core.util.validation.RegexValidatorFactory; import bisq.network.p2p.P2PService; import bisq.network.p2p.network.Statistic; @@ -237,7 +238,7 @@ public class NetworkSettingsView extends ActivatableView { }; btcNodesInputTextField.setPromptText(Res.get("settings.net.ips")); - RegexValidator regexValidator = GUIUtil.addressRegexValidator(); + RegexValidator regexValidator = RegexValidatorFactory.addressRegexValidator(); btcNodesInputTextField.setValidator(regexValidator); btcNodesInputTextField.setErrorMessage(Res.get("validation.invalidAddressList")); btcNodesInputTextFieldFocusListener = (observable, oldValue, newValue) -> { diff --git a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java index f9c803be1e..ef8c695109 100644 --- a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java +++ b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java @@ -30,7 +30,6 @@ import bisq.desktop.util.GUIUtil; import bisq.desktop.util.ImageUtil; import bisq.desktop.util.Layout; import bisq.desktop.util.validation.BtcValidator; -import bisq.desktop.util.validation.RegexValidator; import bisq.core.btc.wallet.Restrictions; import bisq.core.dao.DaoFacade; @@ -52,6 +51,8 @@ import bisq.core.util.FormattingUtils; import bisq.core.util.ParsingUtils; import bisq.core.util.coin.CoinFormatter; import bisq.core.util.validation.IntegerValidator; +import bisq.core.util.validation.RegexValidator; +import bisq.core.util.validation.RegexValidatorFactory; import bisq.common.UserThread; import bisq.common.app.DevEnv; @@ -94,8 +95,8 @@ import javafx.util.StringConverter; import java.io.File; -import java.util.Arrays; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; @@ -341,7 +342,7 @@ public class PreferencesView extends ActivatableViewAndModel { @@ -674,9 +675,9 @@ public class PreferencesView extends ActivatableViewAndModel { if (!newValue.equals(oldValue)) { - RegexValidator onionRegex = GUIUtil.onionAddressRegexValidator(); - RegexValidator localhostRegex = GUIUtil.localhostAddressRegexValidator(); - RegexValidator localnetRegex = GUIUtil.localnetAddressRegexValidator(); + RegexValidator onionRegex = RegexValidatorFactory.onionAddressRegexValidator(); + RegexValidator localhostRegex = RegexValidatorFactory.localhostAddressRegexValidator(); + RegexValidator localnetRegex = RegexValidatorFactory.localnetAddressRegexValidator(); List serviceAddressesRaw = Arrays.asList(StringUtils.deleteWhitespace(newValue).split(",")); diff --git a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java index 9ee9da3654..b7b6b8a209 100644 --- a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java +++ b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java @@ -27,7 +27,6 @@ import bisq.desktop.main.MainView; import bisq.desktop.main.account.AccountView; import bisq.desktop.main.account.content.fiataccounts.FiatAccountsView; import bisq.desktop.main.overlays.popups.Popup; -import bisq.desktop.util.validation.RegexValidator; import bisq.core.account.witness.AccountAgeWitness; import bisq.core.account.witness.AccountAgeWitnessService; @@ -1105,160 +1104,6 @@ public class GUIUtil { MaterialDesignIcon.APPROVAL : MaterialDesignIcon.ALERT_CIRCLE_OUTLINE; } - public static RegexValidator addressRegexValidator() { - RegexValidator regexValidator = new RegexValidator(); - String portRegexPattern = "(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])"; - String onionV2RegexPattern = String.format("[a-zA-Z2-7]{16}\\.onion(?:\\:%1$s)?", portRegexPattern); - String onionV3RegexPattern = String.format("[a-zA-Z2-7]{56}\\.onion(?:\\:%1$s)?", portRegexPattern); - String ipv4RegexPattern = String.format("(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}" + - "(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" + - "(?:\\:%1$s)?", portRegexPattern); - String ipv6RegexPattern = "(" + - "([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|" + // 1:2:3:4:5:6:7:8 - "([0-9a-fA-F]{1,4}:){1,7}:|" + // 1:: 1:2:3:4:5:6:7:: - "([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|" + // 1::8 1:2:3:4:5:6::8 1:2:3:4:5:6::8 - "([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|" + // 1::7:8 1:2:3:4:5::7:8 1:2:3:4:5::8 - "([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|" + // 1::6:7:8 1:2:3:4::6:7:8 1:2:3:4::8 - "([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|" + // 1::5:6:7:8 1:2:3::5:6:7:8 1:2:3::8 - "([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|" + // 1::4:5:6:7:8 1:2::4:5:6:7:8 1:2::8 - "[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|" + // 1::3:4:5:6:7:8 1::3:4:5:6:7:8 1::8 - ":((:[0-9a-fA-F]{1,4}){1,7}|:)|" + // ::2:3:4:5:6:7:8 ::2:3:4:5:6:7:8 ::8 :: - "fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|" + // fe80::7:8%eth0 fe80::7:8%1 - "::(ffff(:0{1,4}){0,1}:){0,1}" + // (link-local IPv6 addresses with zone index) - "((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}" + - "(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|" + // ::255.255.255.255 ::ffff:255.255.255.255 ::ffff:0:255.255.255.255 - "([0-9a-fA-F]{1,4}:){1,4}:" + // (IPv4-mapped IPv6 addresses and IPv4-translated addresses) - "((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}" + - "(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])" + // 2001:db8:3:4::192.0.2.33 64:ff9b::192.0.2.33 - ")"; // (IPv4-Embedded IPv6 Address) - ipv6RegexPattern = String.format("(?:%1$s)|(?:\\[%1$s\\]\\:%2$s)", ipv6RegexPattern, portRegexPattern); - String fqdnRegexPattern = String.format("(((?!-)[a-zA-Z0-9-]{1,63}(? Date: Sun, 20 Sep 2020 22:10:48 -0500 Subject: [PATCH 26/31] Add node address validation --- .../core/support/dispute/DisputeManager.java | 5 +++-- .../bisq/core/trade/TradeDataValidation.java | 19 +++++++++++++++++++ .../dispute/agent/DisputeAgentView.java | 2 +- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java index 3e47353102..f08ca12453 100644 --- a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java +++ b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java @@ -263,11 +263,12 @@ public abstract class DisputeManager { try { TradeDataValidation.validateDonationAddress(dispute, dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade); - } catch (TradeDataValidation.AddressException e) { + TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getBuyerNodeAddress()); + TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getSellerNodeAddress()); + } catch (TradeDataValidation.AddressException | TradeDataValidation.NodeAddressException e) { log.error(e.toString()); validationExceptions.add(e); } - }); TradeDataValidation.testIfAnyDisputeTriedReplay(disputes, diff --git a/core/src/main/java/bisq/core/trade/TradeDataValidation.java b/core/src/main/java/bisq/core/trade/TradeDataValidation.java index b4ff1628eb..ae23c32394 100644 --- a/core/src/main/java/bisq/core/trade/TradeDataValidation.java +++ b/core/src/main/java/bisq/core/trade/TradeDataValidation.java @@ -21,6 +21,9 @@ import bisq.core.btc.wallet.BtcWalletService; import bisq.core.dao.DaoFacade; import bisq.core.offer.Offer; import bisq.core.support.dispute.Dispute; +import bisq.core.util.validation.RegexValidatorFactory; + +import bisq.network.p2p.NodeAddress; import bisq.common.util.Tuple3; @@ -55,6 +58,16 @@ public class TradeDataValidation { validateDonationAddress(null, addressAsString, daoFacade); } + public static void validateNodeAddress(Dispute dispute, NodeAddress nodeAddress) + throws NodeAddressException { + if (!RegexValidatorFactory.onionAddressRegexValidator().validate(nodeAddress.getFullAddress()).isValid) { + String msg = "Node address " + nodeAddress.getFullAddress() + " at dispute with trade ID " + + dispute.getShortTradeId() + " is not a valid address"; + log.error(msg); + throw new NodeAddressException(dispute, msg); + } + } + public static void validateDonationAddress(@Nullable Dispute dispute, String addressAsString, DaoFacade daoFacade) throws AddressException { @@ -383,4 +396,10 @@ public class TradeDataValidation { super(dispute, msg); } } + + public static class NodeAddressException extends ValidationException { + NodeAddressException(Dispute dispute, String msg) { + super(dispute, msg); + } + } } diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java index 7866e2260e..41a1f2a3b6 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java @@ -133,7 +133,7 @@ public abstract class DisputeAgentView extends DisputeView implements MultipleHo protected void showWarningForValidationExceptions(List exceptions) { exceptions.stream() .filter(ex -> ex.getDispute() != null) - .filter(ex -> !ex.getDispute().isClosed()) + .filter(ex -> !ex.getDispute().isClosed()) // we show warnings only for open cases .forEach(ex -> { Dispute dispute = ex.getDispute(); if (ex instanceof TradeDataValidation.AddressException) { From baa915f5deaf97788902bd77aa6142f483de96fc Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 20 Sep 2020 22:32:53 -0500 Subject: [PATCH 27/31] Add validateNodeAddress at onOpenNewDisputeMessage - Cleanups --- core/src/main/java/bisq/core/support/dispute/Dispute.java | 1 + .../main/java/bisq/core/support/dispute/DisputeManager.java | 6 +++++- .../main/java/bisq/core/util/validation/RegexValidator.java | 1 - core/src/test/java/bisq/core/util/RegexValidatorTest.java | 3 ++- .../desktop/main/overlays/windows/DisputeSummaryWindow.java | 3 +-- .../portfolio/pendingtrades/PendingTradesDataModel.java | 5 ++--- .../portfolio/pendingtrades/steps/buyer/BuyerStep1View.java | 1 - .../portfolio/pendingtrades/steps/buyer/BuyerStep2View.java | 1 - .../java/bisq/desktop/main/support/dispute/DisputeView.java | 1 + 9 files changed, 12 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/bisq/core/support/dispute/Dispute.java b/core/src/main/java/bisq/core/support/dispute/Dispute.java index 0bc342bbf5..2cfd0850d7 100644 --- a/core/src/main/java/bisq/core/support/dispute/Dispute.java +++ b/core/src/main/java/bisq/core/support/dispute/Dispute.java @@ -374,6 +374,7 @@ public final class Dispute implements NetworkPayload { return "Dispute{" + "\n tradeId='" + tradeId + '\'' + ",\n id='" + id + '\'' + + ",\n uid='" + uid + '\'' + ",\n traderId=" + traderId + ",\n disputeOpenerIsBuyer=" + disputeOpenerIsBuyer + ",\n disputeOpenerIsMaker=" + disputeOpenerIsMaker + diff --git a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java index f08ca12453..354414a8fe 100644 --- a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java +++ b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java @@ -316,7 +316,11 @@ public abstract class DisputeManager { - - private final CoinFormatter formatter; private final MediationManager mediationManager; private final RefundManager refundManager; @@ -862,6 +860,7 @@ public class DisputeSummaryWindow extends Overlay { } finalizeDisputeHandlerOptional.ifPresent(Runnable::run); + closeTicketButton.disableProperty().unbind(); hide(); diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java index ae7f141542..fe94474fe7 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java @@ -590,7 +590,7 @@ public class PendingTradesDataModel extends ActivatableDataModel { dispute.setDonationAddressOfDelayedPayoutTx(donationAddressString.get()); if (delayedPayoutTx != null) { - dispute.setDelayedPayoutTxId(delayedPayoutTx.getHashAsString()); + dispute.setDelayedPayoutTxId(delayedPayoutTx.getTxId().toString()); } trade.setDisputeState(Trade.DisputeState.MEDIATION_REQUESTED); @@ -667,7 +667,6 @@ public class PendingTradesDataModel extends ActivatableDataModel { isSupportTicket, SupportType.REFUND); - String tradeId = dispute.getTradeId(); mediationManager.findDispute(tradeId) .ifPresent(mediatorsDispute -> { @@ -682,7 +681,7 @@ public class PendingTradesDataModel extends ActivatableDataModel { }); dispute.setDonationAddressOfDelayedPayoutTx(donationAddressString.get()); - dispute.setDelayedPayoutTxId(trade.getDelayedPayoutTx().getTxId().toString()); + dispute.setDelayedPayoutTxId(delayedPayoutTx.getTxId().toString()); trade.setDisputeState(Trade.DisputeState.REFUND_REQUESTED); diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep1View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep1View.java index e094ac9ccd..93f2c20ed0 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep1View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep1View.java @@ -106,7 +106,6 @@ public class BuyerStep1View extends TradeStepView { } catch (TradeDataValidation.MissingTxException ignore) { // We don't react on those errors as a failed trade might get listed initially but getting removed from the // trade manager after initPendingTrades which happens after activate might be called. - log.error(""); } catch (TradeDataValidation.ValidationException e) { if (!model.dataModel.tradeManager.isAllowFaultyDelayedTxs()) { new Popup().warning(Res.get("portfolio.pending.invalidDelayedPayoutTx", e.getMessage())).show(); 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 13fb709d7d..7002de2b9f 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 @@ -642,7 +642,6 @@ public class BuyerStep2View extends TradeStepView { } catch (TradeDataValidation.MissingTxException ignore) { // We don't react on those errors as a failed trade might get listed initially but getting removed from the // trade manager after initPendingTrades which happens after activate might be called. - log.error(""); } catch (TradeDataValidation.ValidationException e) { if (!model.dataModel.tradeManager.isAllowFaultyDelayedTxs()) { new Popup().warning(Res.get("portfolio.pending.invalidDelayedPayoutTx", e.getMessage())).show(); diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java index badfe8c339..1f8bfb57d2 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java @@ -129,6 +129,7 @@ public abstract class DisputeView extends ActivatableView { REASON("Reason"), JSON("Contract as json"); + // Used in tooltip at search string to show where the match was found @Getter private final String displayString; From c7a3f9592530361fc3413bd19fac13deddf44498 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 20 Sep 2020 23:15:20 -0500 Subject: [PATCH 28/31] Rename filterString to filterTerm --- .../main/support/dispute/DisputeView.java | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java index 1f8bfb57d2..e23df1f2eb 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java @@ -403,65 +403,65 @@ public abstract class DisputeView extends ActivatableView { } } - protected FilterResult getFilterResult(Dispute dispute, String filterString) { - filterString = filterString.toLowerCase(); - if (filterString.isEmpty()) { + protected FilterResult getFilterResult(Dispute dispute, String filterTerm) { + String filter = filterTerm.toLowerCase(); + if (filter.isEmpty()) { return FilterResult.NO_FILTER; } // For open filter we do not want to continue further as json data would cause a match - if (filterString.equalsIgnoreCase("open")) { + if (filter.equalsIgnoreCase("open")) { return !dispute.isClosed() ? FilterResult.OPEN_DISPUTES : FilterResult.NO_MATCH; } - if (dispute.getTradeId().toLowerCase().contains(filterString)) { + if (dispute.getTradeId().toLowerCase().contains(filter)) { return FilterResult.TRADE_ID; } - if (DisplayUtils.formatDate(dispute.getOpeningDate()).toLowerCase().contains(filterString)) { + if (DisplayUtils.formatDate(dispute.getOpeningDate()).toLowerCase().contains(filter)) { return FilterResult.OPENING_DATE; } - if (dispute.getContract().getBuyerNodeAddress().getFullAddress().contains(filterString)) { + if (dispute.getContract().getBuyerNodeAddress().getFullAddress().contains(filter)) { return FilterResult.BUYER_NODE_ADDRESS; } - if (dispute.getContract().getSellerNodeAddress().getFullAddress().contains(filterString)) { + if (dispute.getContract().getSellerNodeAddress().getFullAddress().contains(filter)) { return FilterResult.SELLER_NODE_ADDRESS; } - if (dispute.getContract().getBuyerPaymentAccountPayload().getPaymentDetails().toLowerCase().contains(filterString)) { + if (dispute.getContract().getBuyerPaymentAccountPayload().getPaymentDetails().toLowerCase().contains(filter)) { return FilterResult.BUYER_ACCOUNT_DETAILS; } - if (dispute.getContract().getSellerPaymentAccountPayload().getPaymentDetails().toLowerCase().contains(filterString)) { + if (dispute.getContract().getSellerPaymentAccountPayload().getPaymentDetails().toLowerCase().contains(filter)) { return FilterResult.SELLER_ACCOUNT_DETAILS; } - if (dispute.getDepositTxId() != null && dispute.getDepositTxId().contains(filterString)) { + if (dispute.getDepositTxId() != null && dispute.getDepositTxId().contains(filter)) { return FilterResult.DEPOSIT_TX; } - if (dispute.getPayoutTxId() != null && dispute.getPayoutTxId().contains(filterString)) { + if (dispute.getPayoutTxId() != null && dispute.getPayoutTxId().contains(filter)) { return FilterResult.PAYOUT_TX; } - if (dispute.getDelayedPayoutTxId() != null && dispute.getDelayedPayoutTxId().contains(filterString)) { + if (dispute.getDelayedPayoutTxId() != null && dispute.getDelayedPayoutTxId().contains(filter)) { return FilterResult.DEL_PAYOUT_TX; } DisputeResult disputeResult = dispute.getDisputeResultProperty().get(); if (disputeResult != null) { ChatMessage chatMessage = disputeResult.getChatMessage(); - if (chatMessage != null && chatMessage.getMessage().toLowerCase().contains(filterString)) { + if (chatMessage != null && chatMessage.getMessage().toLowerCase().contains(filter)) { return FilterResult.RESULT_MESSAGE; } - if (disputeResult.getReason().name().toLowerCase().contains(filterString)) { + if (disputeResult.getReason().name().toLowerCase().contains(filter)) { return FilterResult.REASON; } } - if (dispute.getContractAsJson().toLowerCase().contains(filterString)) { + if (dispute.getContractAsJson().toLowerCase().contains(filter)) { return FilterResult.JSON; } From 76c82631de404b0fc954243480174143d32b39c9 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Mon, 21 Sep 2020 00:04:05 -0500 Subject: [PATCH 29/31] Ignore onion address validation for localhost --- .../bisq/core/support/dispute/DisputeManager.java | 12 ++++++++---- .../dispute/arbitration/ArbitrationManager.java | 4 +++- .../support/dispute/mediation/MediationManager.java | 4 +++- .../core/support/dispute/refund/RefundManager.java | 4 +++- .../java/bisq/core/trade/TradeDataValidation.java | 5 +++-- 5 files changed, 20 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java index 354414a8fe..c6533c2b8f 100644 --- a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java +++ b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java @@ -48,6 +48,7 @@ import bisq.network.p2p.SendMailboxMessageListener; import bisq.common.UserThread; import bisq.common.app.Version; +import bisq.common.config.Config; import bisq.common.crypto.KeyRing; import bisq.common.crypto.PubKeyRing; import bisq.common.handlers.FaultHandler; @@ -88,6 +89,7 @@ public abstract class DisputeManager disputeListService; + private final Config config; private final PriceFeedService priceFeedService; protected final DaoFacade daoFacade; @@ -112,6 +114,7 @@ public abstract class DisputeManager disputeListService, + Config config, PriceFeedService priceFeedService) { super(p2PService, walletsSetup); @@ -124,6 +127,7 @@ public abstract class DisputeManager { try { TradeDataValidation.validateDonationAddress(dispute, dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade); - TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getBuyerNodeAddress()); - TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getSellerNodeAddress()); + TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getBuyerNodeAddress(), config); + TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getSellerNodeAddress(), config); } catch (TradeDataValidation.AddressException | TradeDataValidation.NodeAddressException e) { log.error(e.toString()); validationExceptions.add(e); @@ -316,8 +320,8 @@ public abstract class DisputeManager DaoFacade daoFacade, KeyRing keyRing, MediationDisputeListService mediationDisputeListService, + Config config, PriceFeedService priceFeedService) { super(p2PService, tradeWalletService, walletService, walletsSetup, tradeManager, closedTradableManager, - openOfferManager, daoFacade, keyRing, mediationDisputeListService, priceFeedService); + openOfferManager, daoFacade, keyRing, mediationDisputeListService, config, priceFeedService); } diff --git a/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java b/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java index fc616a2475..25e6b18275 100644 --- a/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java +++ b/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java @@ -45,6 +45,7 @@ import bisq.network.p2p.P2PService; import bisq.common.Timer; import bisq.common.UserThread; import bisq.common.app.Version; +import bisq.common.config.Config; import bisq.common.crypto.KeyRing; import com.google.inject.Inject; @@ -78,9 +79,10 @@ public final class RefundManager extends DisputeManager { DaoFacade daoFacade, KeyRing keyRing, RefundDisputeListService refundDisputeListService, + Config config, PriceFeedService priceFeedService) { super(p2PService, tradeWalletService, walletService, walletsSetup, tradeManager, closedTradableManager, - openOfferManager, daoFacade, keyRing, refundDisputeListService, priceFeedService); + openOfferManager, daoFacade, keyRing, refundDisputeListService, config, priceFeedService); } diff --git a/core/src/main/java/bisq/core/trade/TradeDataValidation.java b/core/src/main/java/bisq/core/trade/TradeDataValidation.java index ae23c32394..eea5799225 100644 --- a/core/src/main/java/bisq/core/trade/TradeDataValidation.java +++ b/core/src/main/java/bisq/core/trade/TradeDataValidation.java @@ -25,6 +25,7 @@ import bisq.core.util.validation.RegexValidatorFactory; import bisq.network.p2p.NodeAddress; +import bisq.common.config.Config; import bisq.common.util.Tuple3; import org.bitcoinj.core.Address; @@ -58,9 +59,9 @@ public class TradeDataValidation { validateDonationAddress(null, addressAsString, daoFacade); } - public static void validateNodeAddress(Dispute dispute, NodeAddress nodeAddress) + public static void validateNodeAddress(Dispute dispute, NodeAddress nodeAddress, Config config) throws NodeAddressException { - if (!RegexValidatorFactory.onionAddressRegexValidator().validate(nodeAddress.getFullAddress()).isValid) { + if (!config.useLocalhostForP2P && !RegexValidatorFactory.onionAddressRegexValidator().validate(nodeAddress.getFullAddress()).isValid) { String msg = "Node address " + nodeAddress.getFullAddress() + " at dispute with trade ID " + dispute.getShortTradeId() + " is not a valid address"; log.error(msg); From a9f10624c2615e82996999e1c43e41c84c26e2c5 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Mon, 21 Sep 2020 00:20:12 -0500 Subject: [PATCH 30/31] Move validation after adding dispute to list --- .../core/support/dispute/DisputeManager.java | 24 +++++++++---------- .../windows/DisputeSummaryWindow.java | 1 - 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java index c6533c2b8f..bd1a33d0cc 100644 --- a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java +++ b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java @@ -317,18 +317,6 @@ public abstract class DisputeManager { fee, buyerPayoutAddressString, sellerPayoutAddressString); - log.error("transaction " + tx); tradeWalletService.broadcastTx(tx, new TxBroadcaster.Callback() { @Override public void onSuccess(Transaction transaction) { From 81bea14af2145c39473a2cd3aa929b9afcbb357c Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Mon, 21 Sep 2020 01:16:47 -0500 Subject: [PATCH 31/31] Show popup to peer who accepted mediators suggestion once locktime is over --- .../resources/i18n/displayStrings.properties | 6 ++ .../pendingtrades/steps/TradeStepView.java | 95 +++++++++++++++++-- .../steps/buyer/BuyerStep1View.java | 25 +---- .../steps/buyer/BuyerStep2View.java | 25 ++--- 4 files changed, 105 insertions(+), 46 deletions(-) diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index b49ede2786..06f892731c 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -894,6 +894,12 @@ portfolio.pending.mediationResult.popup.info=The mediator has suggested the foll (or if the other peer is unresponsive).\n\n\ More details about the new arbitration model:\n\ https://docs.bisq.network/trading-rules.html#arbitration +portfolio.pending.mediationResult.popup.selfAccepted.lockTimeOver=You have accepted the mediator''s suggested payout \ + but it seems that your trading peer has not accepted it.\n\n\ + The lock time is since {0} (block {1}) over and you can open a second-round dispute with an arbitrator who will \ + investigate the case again and do a payout based on their findings.\n\n\ + You can find more details about the arbitration model at:\n\ + https://docs.bisq.network/trading-rules.html#arbitration portfolio.pending.mediationResult.popup.openArbitration=Reject and request arbitration portfolio.pending.mediationResult.popup.alreadyAccepted=You've already accepted 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 cfc35e8968..4c91de8614 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 @@ -30,6 +30,7 @@ import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.DisputeResult; +import bisq.core.support.dispute.mediation.MediationResultState; import bisq.core.trade.Contract; import bisq.core.trade.Trade; import bisq.core.user.Preferences; @@ -41,6 +42,9 @@ import bisq.common.ClockWatcher; import bisq.common.UserThread; import bisq.common.util.Tuple3; +import org.bitcoinj.core.Transaction; +import org.bitcoinj.core.listeners.NewBestBlockListener; + import de.jensd.fx.fontawesome.AwesomeDude; import de.jensd.fx.fontawesome.AwesomeIcon; @@ -62,6 +66,7 @@ import javafx.geometry.Insets; import org.fxmisc.easybind.EasyBind; import org.fxmisc.easybind.Subscription; +import javafx.beans.property.BooleanProperty; import javafx.beans.value.ChangeListener; import java.util.Optional; @@ -97,6 +102,8 @@ public abstract class TradeStepView extends AnchorPane { private Popup acceptMediationResultPopup; private BootstrapListener bootstrapListener; private TradeSubView.ChatCallback chatCallback; + private final NewBestBlockListener newBestBlockListener; + private ChangeListener pendingTradesInitializedListener; /////////////////////////////////////////////////////////////////////////////////////////// @@ -158,6 +165,10 @@ public abstract class TradeStepView extends AnchorPane { updateTimeLeft(); } }; + + newBestBlockListener = block -> { + checkIfLockTimeIsOver(); + }; } public void activate() { @@ -200,14 +211,34 @@ public abstract class TradeStepView extends AnchorPane { } tradePeriodStateSubscription = EasyBind.subscribe(trade.tradePeriodStateProperty(), newValue -> { - if (newValue != null) + if (newValue != null) { updateTradePeriodState(newValue); + } }); model.clockWatcher.addListener(clockListener); - if (infoLabel != null) + if (infoLabel != null) { infoLabel.setText(getInfoText()); + } + + BooleanProperty pendingTradesInitialized = model.dataModel.tradeManager.getPendingTradesInitialized(); + if (pendingTradesInitialized.get()) { + onPendingTradesInitialized(); + } else { + pendingTradesInitializedListener = (observable, oldValue, newValue) -> { + if (newValue) { + onPendingTradesInitialized(); + UserThread.execute(() -> pendingTradesInitialized.removeListener(pendingTradesInitializedListener)); + } + }; + pendingTradesInitialized.addListener(pendingTradesInitializedListener); + } + } + + protected void onPendingTradesInitialized() { + model.dataModel.btcWalletService.addNewBestBlockListener(newBestBlockListener); + checkIfLockTimeIsOver(); } private void registerSubscriptions() { @@ -262,6 +293,15 @@ public abstract class TradeStepView extends AnchorPane { if (tradeStepInfo != null) tradeStepInfo.setOnAction(null); + + if (newBestBlockListener != null) { + model.dataModel.btcWalletService.removeNewBestBlockListener(newBestBlockListener); + } + + if (acceptMediationResultPopup != null) { + acceptMediationResultPopup.hide(); + acceptMediationResultPopup = null; + } } /////////////////////////////////////////////////////////////////////////////////////////// @@ -445,6 +485,11 @@ public abstract class TradeStepView extends AnchorPane { tradeStepInfo.setState(TradeStepInfo.State.IN_REFUND_REQUEST_SELF_REQUESTED); }); + if (acceptMediationResultPopup != null) { + acceptMediationResultPopup.hide(); + acceptMediationResultPopup = null; + } + break; case REFUND_REQUEST_STARTED_BY_PEER: if (tradeStepInfo != null) { @@ -457,6 +502,11 @@ public abstract class TradeStepView extends AnchorPane { if (tradeStepInfo != null) tradeStepInfo.setState(TradeStepInfo.State.IN_REFUND_REQUEST_PEER_REQUESTED); }); + + if (acceptMediationResultPopup != null) { + acceptMediationResultPopup.hide(); + acceptMediationResultPopup = null; + } break; case REFUND_REQUEST_CLOSED: break; @@ -563,13 +613,34 @@ public abstract class TradeStepView extends AnchorPane { String actionButtonText = hasSelfAccepted() ? Res.get("portfolio.pending.mediationResult.popup.alreadyAccepted") : Res.get("shared.accept"); - acceptMediationResultPopup = new Popup().width(900) - .headLine(headLine) - .instruction(Res.get("portfolio.pending.mediationResult.popup.info", + String message; + MediationResultState mediationResultState = checkNotNull(trade).getMediationResultState(); + if (mediationResultState == null) { + return; + } + + switch (mediationResultState) { + case MEDIATION_RESULT_ACCEPTED: + case SIG_MSG_SENT: + case SIG_MSG_ARRIVED: + case SIG_MSG_IN_MAILBOX: + case SIG_MSG_SEND_FAILED: + message = Res.get("portfolio.pending.mediationResult.popup.selfAccepted.lockTimeOver", + FormattingUtils.getDateFromBlockHeight(remaining), + lockTime); + break; + default: + message = Res.get("portfolio.pending.mediationResult.popup.info", myPayoutAmount, peersPayoutAmount, FormattingUtils.getDateFromBlockHeight(remaining), - lockTime)) + lockTime); + break; + } + + acceptMediationResultPopup = new Popup().width(900) + .headLine(headLine) + .instruction(message) .actionButtonText(actionButtonText) .onAction(() -> { model.dataModel.mediationManager.acceptMediationResult(trade, @@ -656,6 +727,18 @@ public abstract class TradeStepView extends AnchorPane { return trade.getDisputeState() != Trade.DisputeState.NO_DISPUTE; } + private void checkIfLockTimeIsOver() { + Transaction delayedPayoutTx = trade.getDelayedPayoutTx(); + if (delayedPayoutTx != null) { + long lockTime = delayedPayoutTx.getLockTime(); + int bestChainHeight = model.dataModel.btcWalletService.getBestChainHeight(); + long remaining = lockTime - bestChainHeight; + if (remaining <= 0) { + openMediationResultPopup(Res.get("portfolio.pending.mediationResult.popup.headline", trade.getShortId())); + } + } + } + /////////////////////////////////////////////////////////////////////////////////////////// // TradeDurationLimitInfo diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep1View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep1View.java index 93f2c20ed0..cc916cf23d 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep1View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep1View.java @@ -24,13 +24,7 @@ import bisq.desktop.main.portfolio.pendingtrades.steps.TradeStepView; import bisq.core.locale.Res; import bisq.core.trade.TradeDataValidation; -import bisq.common.UserThread; - -import javafx.beans.property.BooleanProperty; -import javafx.beans.value.ChangeListener; - public class BuyerStep1View extends TradeStepView { - private ChangeListener pendingTradesInitializedListener; /////////////////////////////////////////////////////////////////////////////////////////// // Constructor, Initialisation @@ -41,22 +35,9 @@ public class BuyerStep1View extends TradeStepView { } @Override - public void activate() { - super.activate(); - - // We need to have the trades initialized before we can call validatePayoutTx. - BooleanProperty pendingTradesInitialized = model.dataModel.tradeManager.getPendingTradesInitialized(); - if (pendingTradesInitialized.get()) { - validatePayoutTx(); - } else { - pendingTradesInitializedListener = (observable, oldValue, newValue) -> { - if (newValue) { - validatePayoutTx(); - UserThread.execute(() -> pendingTradesInitialized.removeListener(pendingTradesInitializedListener)); - } - }; - pendingTradesInitialized.addListener(pendingTradesInitializedListener); - } + protected void onPendingTradesInitialized() { + super.onPendingTradesInitialized(); + validatePayoutTx(); } 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 7002de2b9f..6ee580f815 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 @@ -88,9 +88,6 @@ import javafx.scene.layout.Priority; import org.fxmisc.easybind.EasyBind; import org.fxmisc.easybind.Subscription; -import javafx.beans.property.BooleanProperty; -import javafx.beans.value.ChangeListener; - import java.util.List; import java.util.concurrent.TimeUnit; @@ -107,7 +104,6 @@ public class BuyerStep2View extends TradeStepView { private BusyAnimation busyAnimation; private Subscription tradeStatePropertySubscription; private Timer timeoutTimer; - private ChangeListener pendingTradesInitializedListener; /////////////////////////////////////////////////////////////////////////////////////////// // Constructor, Initialisation @@ -121,20 +117,6 @@ public class BuyerStep2View extends TradeStepView { public void activate() { super.activate(); - // We need to have the trades initialized before we can call validatePayoutTx. - BooleanProperty pendingTradesInitialized = model.dataModel.tradeManager.getPendingTradesInitialized(); - if (pendingTradesInitialized.get()) { - validatePayoutTx(); - } else { - pendingTradesInitializedListener = (observable, oldValue, newValue) -> { - if (newValue) { - validatePayoutTx(); - UserThread.execute(() -> pendingTradesInitialized.removeListener(pendingTradesInitializedListener)); - } - }; - pendingTradesInitialized.addListener(pendingTradesInitializedListener); - } - if (timeoutTimer != null) timeoutTimer.stop(); @@ -212,6 +194,13 @@ public class BuyerStep2View extends TradeStepView { } } + @Override + protected void onPendingTradesInitialized() { + super.onPendingTradesInitialized(); + validatePayoutTx(); + } + + /////////////////////////////////////////////////////////////////////////////////////////// // Content ///////////////////////////////////////////////////////////////////////////////////////////