From 4f1cbbd00ec77ee6d8dc18bd07ea3552cb359037 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Fri, 11 Sep 2020 15:35:47 -0500 Subject: [PATCH 01/34] 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 1485ae25465..50b6402c5a7 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.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 long getRequiredBond(BondedRoleType bondedRoleType) { 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 d5f45a7a185..6a79a9f740d 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 protobuf.Dispute toProtoMessage() { 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 static Dispute fromProto(protobuf.Dispute proto, CoreProtoResolver corePr dispute.setDelayedPayoutTxId(delayedPayoutTxId); } + String donationAddressOfDelayedPayoutTx = proto.getDonationAddressOfDelayedPayoutTx(); + if (!donationAddressOfDelayedPayoutTx.isEmpty()) { + dispute.setDonationAddressOfDelayedPayoutTx(donationAddressOfDelayedPayoutTx); + } + return dispute; } @@ -382,6 +393,7 @@ public String toString() { ",\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 9998e1f2eba..5a01fdba56e 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.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.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 javafx.beans.property.IntegerProperty; +import javafx.collections.FXCollections; import javafx.collections.ObservableList; import java.util.List; @@ -66,10 +69,12 @@ 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 DisputeManager(P2PService p2PService, TradeManager tradeManager, ClosedTradableManager closedTradableManager, OpenOfferManager openOfferManager, + DaoFacade daoFacade, PubKeyRing pubKeyRing, DisputeListService disputeListService, PriceFeedService priceFeedService) { @@ -105,6 +115,7 @@ public DisputeManager(P2PService p2PService, this.tradeManager = tradeManager; this.closedTradableManager = closedTradableManager; this.openOfferManager = openOfferManager; + this.daoFacade = daoFacade; this.pubKeyRing = pubKeyRing; this.disputeListService = disputeListService; this.priceFeedService = priceFeedService; @@ -178,7 +189,7 @@ public void addAndPersistChatMessage(ChatMessage message) { @Nullable public abstract NodeAddress getAgentNodeAddress(Dispute dispute); - protected abstract Trade.DisputeState getDisputeState_StartedByPeer(); + protected abstract Trade.DisputeState getDisputeStateStartedByPeer(); public abstract void cleanupDisputes(); @@ -257,6 +268,7 @@ public Optional findOwnDispute(String tradeId) { return disputeList.stream().filter(e -> e.getTradeId().equals(tradeId)).findAny(); } + /////////////////////////////////////////////////////////////////////////////////////////// // Message handler /////////////////////////////////////////////////////////////////////////////////////////// @@ -278,6 +290,10 @@ protected void onOpenNewDisputeMessage(OpenNewDisputeMessage openNewDisputeMessa Contract contract = dispute.getContract(); addPriceInfoMessage(dispute, 0); + if (!DelayedPayoutTxValidation.isValidDonationAddress(dispute, daoFacade)) { + disputesWithInvalidDonationAddress.add(dispute); + } + PubKeyRing peersPubKeyRing = dispute.isDisputeOpenerIsBuyer() ? contract.getSellerPubKeyRing() : contract.getBuyerPubKeyRing(); if (isAgent(dispute)) { if (!disputeList.contains(dispute)) { @@ -310,7 +326,7 @@ protected void onOpenNewDisputeMessage(OpenNewDisputeMessage openNewDisputeMessa addMediationResultMessage(dispute); } - // not dispute requester receives that from dispute agent + // Not-dispute-requester receives that msg from dispute agent protected void onPeerOpenedDisputeMessage(PeerOpenedDisputeMessage peerOpenedDisputeMessage) { T disputeList = getDisputeList(); if (disputeList == null) { @@ -320,14 +336,34 @@ protected void onPeerOpenedDisputeMessage(PeerOpenedDisputeMessage peerOpenedDis String errorMessage = null; Dispute dispute = peerOpenedDisputeMessage.getDispute(); + + Optional 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 @@ private void doSendPeerOpenedDisputeMessage(Dispute disputeFromOpener, disputeFromOpener.isSupportTicket(), disputeFromOpener.getSupportType()); dispute.setDelayedPayoutTxId(disputeFromOpener.getDelayedPayoutTxId()); + dispute.setDonationAddressOfDelayedPayoutTx(disputeFromOpener.getDonationAddressOfDelayedPayoutTx()); Optional 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 e77daf2b048..2ddacf350f5 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.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 ArbitrationManager(P2PService p2PService, TradeManager tradeManager, ClosedTradableManager closedTradableManager, OpenOfferManager openOfferManager, + DaoFacade daoFacade, PubKeyRing pubKeyRing, ArbitrationDisputeListService arbitrationDisputeListService, PriceFeedService priceFeedService) { super(p2PService, tradeWalletService, walletService, walletsSetup, tradeManager, closedTradableManager, - openOfferManager, pubKeyRing, arbitrationDisputeListService, priceFeedService); + openOfferManager, daoFacade, pubKeyRing, arbitrationDisputeListService, priceFeedService); } @@ -134,7 +136,7 @@ public NodeAddress getAgentNodeAddress(Dispute dispute) { } @Override - protected Trade.DisputeState getDisputeState_StartedByPeer() { + protected Trade.DisputeState getDisputeStateStartedByPeer() { return Trade.DisputeState.DISPUTE_STARTED_BY_PEER; } diff --git a/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java b/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java index 42f9b37f5cf..088af6f8680 100644 --- a/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java +++ b/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java @@ -20,6 +20,7 @@ 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; @@ -80,11 +81,12 @@ public MediationManager(P2PService p2PService, 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 void dispatchMessage(SupportMessage message) { } @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 f28208f050a..f00aca5fbde 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 @@ 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 RefundManager(P2PService p2PService, 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 void dispatchMessage(SupportMessage message) { } @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 6c835d2408b..7b0997d57ae 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.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.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 static class InvalidInputException extends Exception { } } + 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 static void validatePayoutTx(Trade trade, // 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 static void validatePayoutTx(Trade trade, } 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 static void validatePayoutTx(Trade trade, 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 753d8bc3394..4156441334f 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 989d063b25e..588c06cb564 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 void activate() { 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 ff053d90338..ec8e10ede0b 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 @@ private void addContent() { rows++; if (dispute.getDelayedPayoutTxId() != null) rows++; + if (dispute.getDonationAddressOfDelayedPayoutTx() != null) + rows++; if (showAcceptedCountryCodes) rows++; if (showAcceptedBanks) @@ -248,6 +250,11 @@ private void addContent() { 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 6202b6b2042..d78723ec538 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.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.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 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 DisputeSummaryWindow(@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormat TradeWalletService tradeWalletService, BtcWalletService btcWalletService, TxFeeEstimationService txFeeEstimationService, - FeeService feeService) { + FeeService feeService, + DaoFacade daoFacade) { this.formatter = formatter; this.mediationManager = mediationManager; @@ -150,6 +156,7 @@ public DisputeSummaryWindow(@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormat this.btcWalletService = btcWalletService; this.txFeeEstimationService = txFeeEstimationService; this.feeService = feeService; + this.daoFacade = daoFacade; type = Type.Confirmation; } @@ -642,7 +649,10 @@ private void addButtons(Contract contract) { 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 void onFailure(TxBroadcastException exception) { } 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 @@ private void doClose(Button closeTicketButton) { 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 @@ private void doClose(Button closeTicketButton) { 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 6d1a288f118..e1f2b10d9bf 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.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 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 @@ private void doOpenDispute(boolean isSupportTicket, Transaction depositTx) { // 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 @@ private void doOpenDispute(boolean isSupportTicket, Transaction depositTx) { 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 @@ private void doOpenDispute(boolean isSupportTicket, Transaction depositTx) { } 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 @@ private void doOpenDispute(boolean isSupportTicket, Transaction depositTx) { 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 @@ private void doOpenDispute(boolean isSupportTicket, Transaction depositTx) { isSupportTicket, SupportType.REFUND); + dispute.setDonationAddressOfDelayedPayoutTx(donationAddressString.get()); + dispute.setDelayedPayoutTxId(delayedPayoutTx.getHashAsString()); + String tradeId = dispute.getTradeId(); mediationManager.findDispute(tradeId) .ifPresent(mediatorsDispute -> { @@ -651,9 +679,6 @@ private void doOpenDispute(boolean isSupportTicket, Transaction depositTx) { 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 13e52608678..6f6efdd2064 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.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.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 DisputeAgentView(DisputeManager { + 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 @@ protected void activate() { if (multipleHolderNameDetection.hasSuspiciousDisputesDetected()) { suspiciousDisputeDetected(); } + + disputeManager.getDisputesWithInvalidDonationAddress().addListener(disputesWithInvalidDonationAddressListener); + showWarningForInvalidDonationAddress(disputeManager.getDisputesWithInvalidDonationAddress()); } @Override @@ -124,6 +153,8 @@ protected void deactivate() { 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 dcb33c61124..7c2278de28c 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.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 ArbitratorView(ArbitrationManager arbitrationManager, ContractWindow contractWindow, TradeDetailsWindow tradeDetailsWindow, AccountAgeWitnessService accountAgeWitnessService, + DaoFacade daoFacade, @Named(Config.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) { super(arbitrationManager, keyRing, @@ -63,6 +65,7 @@ public ArbitratorView(ArbitrationManager arbitrationManager, 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 ba939c34d23..d96fb9e8f2a 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.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 MediatorView(MediationManager mediationManager, ContractWindow contractWindow, TradeDetailsWindow tradeDetailsWindow, AccountAgeWitnessService accountAgeWitnessService, + DaoFacade daoFacade, @Named(Config.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) { super(mediationManager, keyRing, @@ -63,6 +65,7 @@ public MediatorView(MediationManager mediationManager, 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 5aae5f53f81..71f27f8954e 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.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.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 RefundAgentView(RefundManager refundManager, ContractWindow contractWindow, TradeDetailsWindow tradeDetailsWindow, AccountAgeWitnessService accountAgeWitnessService, + DaoFacade daoFacade, @Named(Config.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) { super(refundManager, keyRing, @@ -64,6 +65,7 @@ public RefundAgentView(RefundManager refundManager, contractWindow, tradeDetailsWindow, accountAgeWitnessService, + daoFacade, useDevPrivilegeKeys); } diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 2f334e06e03..b10a4a02daf 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/34] 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 e1f2b10d9bf..07be33a3c45 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 @@ private void doOpenDispute(boolean isSupportTicket, Transaction depositTx) { // 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/34] 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 5a01fdba56e..d327eaad72c 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 @@ protected void onOpenNewDisputeMessage(OpenNewDisputeMessage openNewDisputeMessa Contract contract = dispute.getContract(); addPriceInfoMessage(dispute, 0); - if (!DelayedPayoutTxValidation.isValidDonationAddress(dispute, daoFacade)) { + try { + DelayedPayoutTxValidation.validateDonationAddress(dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade); + } catch (DelayedPayoutTxValidation.DonationAddressException e) { disputesWithInvalidDonationAddress.add(dispute); } diff --git a/core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java b/core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java index 7b0997d57ae..42717e17460 100644 --- a/core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java +++ b/core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java @@ -23,8 +23,6 @@ import bisq.core.offer.Offer; import bisq.core.support.dispute.Dispute; -import bisq.common.config.Config; - import org.bitcoinj.core.Address; import org.bitcoinj.core.Coin; import org.bitcoinj.core.NetworkParameters; @@ -33,7 +31,6 @@ import org.bitcoinj.core.TransactionOutPoint; import org.bitcoinj.core.TransactionOutput; -import java.util.List; import java.util.Set; import java.util.function.Consumer; @@ -83,26 +80,33 @@ public static class InvalidInputException extends Exception { } } - public static boolean isValidDonationAddress(Dispute dispute, DaoFacade daoFacade) { - String addressAsString = dispute.getDonationAddressOfDelayedPayoutTx(); + public static void validateDonationAddress(String addressAsString, DaoFacade daoFacade) + throws DonationAddressException { - // Old clients don't have it set yet. Can be removed after a forced update if (addressAsString == null) { - return true; + return; } - // 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. + // 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 = 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()); + + // 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 + - 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 (!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 static void validatePayoutTx(Trade trade, 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 static void validatePayoutTx(Trade trade, 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 d78723ec538..d7ce950ea13 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 @@ private void addButtons(Contract contract) { 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 void onFailure(TxBroadcastException exception) { 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/34] 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 50b6402c5a7..bfc542a8621 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 Set getAllPastParamValues(Param param) { }); 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 d327eaad72c..3fc8538e157 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 javax.annotation.Nullable; -import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @Slf4j @@ -292,7 +291,7 @@ protected void onOpenNewDisputeMessage(OpenNewDisputeMessage openNewDisputeMessa try { DelayedPayoutTxValidation.validateDonationAddress(dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade); - } catch (DelayedPayoutTxValidation.DonationAddressException e) { + } catch (DelayedPayoutTxValidation.AddressException e) { disputesWithInvalidDonationAddress.add(dispute); } @@ -340,7 +339,10 @@ protected void onPeerOpenedDisputeMessage(PeerOpenedDisputeMessage peerOpenedDis Dispute dispute = peerOpenedDisputeMessage.getDispute(); Optional optionalTrade = tradeManager.getTradeById(dispute.getTradeId()); - checkArgument(optionalTrade.isPresent()); + if (!optionalTrade.isPresent()) { + return; + } + Trade trade = optionalTrade.get(); try { DelayedPayoutTxValidation.validatePayoutTx(trade, @@ -348,11 +350,7 @@ protected void onPeerOpenedDisputeMessage(PeerOpenedDisputeMessage peerOpenedDis dispute, daoFacade, btcWalletService); - } 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/core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java b/core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java index 42717e17460..023f9d608db 100644 --- a/core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java +++ b/core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java @@ -19,7 +19,6 @@ 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; @@ -44,68 +43,21 @@ @Slf4j public class DelayedPayoutTxValidation { - public static class DonationAddressException extends Exception { - DonationAddressException(String msg) { - super(msg); - } - } - - public static class MissingDelayedPayoutTxException extends Exception { - MissingDelayedPayoutTxException(String msg) { - super(msg); - } - } - - public static class InvalidTxException extends Exception { - InvalidTxException(String msg) { - super(msg); - } - } - - public static class AmountMismatchException extends Exception { - AmountMismatchException(String msg) { - super(msg); - } - } - - public static class InvalidLockTimeException extends Exception { - InvalidLockTimeException(String msg) { - super(msg); - } - } - - public static class InvalidInputException extends Exception { - InvalidInputException(String msg) { - super(msg); - } - } - public static void validateDonationAddress(String addressAsString, DaoFacade daoFacade) - throws DonationAddressException { + throws AddressException { if (addressAsString == null) { + log.warn("address is null at validateDonationAddress. This is expected in case of an not updated trader."); return; } - // 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 = 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 static void validatePayoutTx(Trade trade, 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 static void validatePayoutTx(Trade trade, @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 static void validatePayoutTx(Trade trade, 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 static void validatePayoutTx(Trade trade, 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 static void validatePayoutTx(Trade trade, 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 static void validatePayoutTx(Trade trade, 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 static void validatePayoutTxInput(Transaction depositTx, "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 66962f10d68..6c0f3d79a3b 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 @@ private void initPendingTrades() { 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 fa1aeacdbe6..3aaf36a39d9 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 @@ protected void run() { 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 3242ba8cf6a..7853767d276 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 @@ protected void run() { 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 4156441334f..2f41ef75b0c 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 d7ce950ea13..3e349efc4db 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.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 java.util.Date; import java.util.Optional; -import java.util.Set; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; @@ -650,18 +648,12 @@ private void addButtons(Contract contract) { 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 void onSuccess(Transaction transaction) { 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 void onFailure(TxBroadcastException exception) { } } - 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 @@ private void doClose(Button closeTicketButton) { 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 @@ private void doClose(Button closeTicketButton) { 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 07be33a3c45..3da51c70c78 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 @@ private void doOpenDispute(boolean isSupportTicket, Transaction depositTx) { 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 184dcd038f9..75dfb517c45 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 void activate() { 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 d6d2aeabe33..9011390f599 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 void activate() { 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 6f6efdd2064..f7d059b1105 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.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.collections.ListChangeListener; import java.util.List; -import java.util.Set; import static bisq.desktop.util.FormBuilder.getIconForLabel; @@ -126,13 +124,16 @@ public void initialize() { } 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/34] 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 2f41ef75b0c..b72799295ce 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 3e349efc4db..b374f457b25 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 @@ private boolean isPayoutAmountValid() { .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 @@ private void addButtons(Contract contract) { 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 @@ private void showPayoutTxConfirmation(Contract contract, DisputeResult disputeRe 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/34] 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 a00fe2dca89..3242bd535ed 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 void applyDelayedPayoutTxBytes(byte[] delayedPayoutTxBytes) { @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 6c0f3d79a3b..ea9bcdf2937 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 75dfb517c45..716d6bae04b 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.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 BuyerStep1View(PendingTradesViewModel model) { 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 @@ protected String getFirstHalfOverWarnText() { 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 9011390f599..6096e0cf3d2 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 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 BuyerStep2View(PendingTradesViewModel model) { 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 @@ private void showPopup() { } } + 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/34] 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 a00fe2dca89..3242bd535ed 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 void applyDelayedPayoutTxBytes(byte[] delayedPayoutTxBytes) { @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 6c0f3d79a3b..ea9bcdf2937 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 75dfb517c45..716d6bae04b 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.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 BuyerStep1View(PendingTradesViewModel model) { 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 @@ protected String getFirstHalfOverWarnText() { 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 9011390f599..6096e0cf3d2 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 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 BuyerStep2View(PendingTradesViewModel model) { 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 @@ private void showPopup() { } } + 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/34] 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 3fc8538e157..5084653df57 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.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.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 DisputeManager(P2PService p2PService, ClosedTradableManager closedTradableManager, OpenOfferManager openOfferManager, DaoFacade daoFacade, - PubKeyRing pubKeyRing, + KeyRing keyRing, DisputeListService disputeListService, PriceFeedService priceFeedService) { super(p2PService, walletsSetup); @@ -115,7 +120,8 @@ public DisputeManager(P2PService p2PService, this.closedTradableManager = closedTradableManager; this.openOfferManager = openOfferManager; this.daoFacade = daoFacade; - this.pubKeyRing = pubKeyRing; + this.pubKeyRing = keyRing.getPubKeyRing(); + signatureKeyPair = keyRing.getSignatureKeyPair(); this.disputeListService = disputeListService; this.priceFeedService = priceFeedService; } @@ -267,7 +273,6 @@ public Optional findOwnDispute(String tradeId) { return disputeList.stream().filter(e -> e.getTradeId().equals(tradeId)).findAny(); } - /////////////////////////////////////////////////////////////////////////////////////////// // Message handler /////////////////////////////////////////////////////////////////////////////////////////// @@ -646,7 +651,7 @@ public void onFault(String errorMessage) { } // dispute agent send result to trader - public void sendDisputeResultMessage(DisputeResult disputeResult, Dispute dispute, String text) { + public void sendDisputeResultMessage(DisputeResult disputeResult, Dispute dispute, String summaryText) { T disputeList = getDisputeList(); if (disputeList == null) { log.warn("disputes is null"); @@ -658,7 +663,7 @@ public void sendDisputeResultMessage(DisputeResult disputeResult, Dispute disput dispute.getTradeId(), dispute.getTraderPubKeyRing().hashCode(), false, - text, + summaryText, p2PService.getAddress()); disputeResult.setChatMessage(chatMessage); 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 2ddacf350f5..21b937e54fd 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 @@ -54,6 +54,7 @@ import bisq.common.Timer; import bisq.common.UserThread; import bisq.common.app.Version; +import bisq.common.crypto.KeyRing; import bisq.common.crypto.PubKeyRing; import org.bitcoinj.core.AddressFormatException; @@ -90,11 +91,11 @@ public ArbitrationManager(P2PService p2PService, ClosedTradableManager closedTradableManager, OpenOfferManager openOfferManager, DaoFacade daoFacade, - PubKeyRing pubKeyRing, + KeyRing keyRing, ArbitrationDisputeListService arbitrationDisputeListService, PriceFeedService priceFeedService) { super(p2PService, tradeWalletService, walletService, walletsSetup, tradeManager, closedTradableManager, - openOfferManager, daoFacade, pubKeyRing, arbitrationDisputeListService, priceFeedService); + openOfferManager, daoFacade, keyRing, arbitrationDisputeListService, priceFeedService); } diff --git a/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java b/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java index 088af6f8680..b8f06df5621 100644 --- a/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java +++ b/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java @@ -47,7 +47,7 @@ import bisq.common.Timer; import bisq.common.UserThread; import bisq.common.app.Version; -import bisq.common.crypto.PubKeyRing; +import bisq.common.crypto.KeyRing; import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ResultHandler; @@ -82,11 +82,11 @@ public MediationManager(P2PService p2PService, 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 f00aca5fbde..631218bcc0b 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.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 RefundManager(P2PService p2PService, 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 b72799295ce..91ebd566f82 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 b374f457b25..80dc5905bab 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.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.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.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 @@ private void doCloseIfValid(Button closeTicketButton) { } 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 @@ private void doClose(Button closeTicketButton) { 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 @@ private void doClose(Button closeTicketButton) { } 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 00000000000..c5b25c4e461 --- /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 00000000000..9be98cfc9f3 --- /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 fcba3c3b652..d140a9abfc1 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.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.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 DisputeView(DisputeManager> ContractWindow contractWindow, TradeDetailsWindow tradeDetailsWindow, AccountAgeWitnessService accountAgeWitnessService, + MediatorManager mediatorManager, + RefundAgentManager refundAgentManager, boolean useDevPrivilegeKeys) { this.disputeManager = disputeManager; this.keyRing = keyRing; @@ -167,6 +174,8 @@ public DisputeView(DisputeManager> this.contractWindow = contractWindow; this.tradeDetailsWindow = tradeDetailsWindow; this.accountAgeWitnessService = accountAgeWitnessService; + this.mediatorManager = mediatorManager; + this.refundAgentManager = refundAgentManager; this.useDevPrivilegeKeys = useDevPrivilegeKeys; } @@ -222,6 +231,12 @@ public void initialize() { 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 void initialize() { 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 f7d059b1105..8565c0cab40 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.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 DisputeAgentView(DisputeManager Date: Sat, 12 Sep 2020 00:46:52 -0500 Subject: [PATCH 09/34] 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 c5b25c4e461..f6bfe61ce47 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.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 @@ protected void createGridPane() { } private void addContent() { - Label label = addMultilineLabel(gridPane, ++rowIndex, Res.get("support.sigCheck.popup.info"), 0, width); + 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; From 48066ae124f30251d96a694b53398001328789bb Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sat, 12 Sep 2020 00:49:27 -0500 Subject: [PATCH 10/34] 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 9be98cfc9f3..4ca2be2baca 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 static String signAndApply(DisputeManager Date: Sat, 12 Sep 2020 16:19:42 -0500 Subject: [PATCH 11/34] 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 91ebd566f82..a1c72a3399c 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 80dc5905bab..eb1f467611f 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 @@ private void doClose(Button closeTicketButton) { 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/34] 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 a1c72a3399c..bd2c6183aeb 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 eb1f467611f..4e6751e713b 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 @@ private void addContent() { 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 @@ private void doClose(Button closeTicketButton) { 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 @@ private void doClose(Button closeTicketButton) { 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 f6bfe61ce47..9f274995455 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 void show() { if (headLine == null) headLine = Res.get("support.sigCheck.popup.header"); - width = 1000; + width = 1050; createGridPane(); addHeadLine(); addContent(); 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 4ca2be2baca..c806da3cd95 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 @@ -18,7 +18,6 @@ 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; @@ -36,16 +35,20 @@ import java.security.KeyPair; import java.security.PublicKey; +import lombok.extern.slf4j.Slf4j; + import static com.google.common.base.Preconditions.checkNotNull; +@Slf4j 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"; + private static final String SEPARATOR1 = "\n-----BEGIN SIGNATURE-----\n"; + private static final String SEPARATOR2 = "\n-----END SIGNATURE-----\n"; public static String signAndApply(DisputeManager> disputeManager, - Dispute dispute, DisputeResult disputeResult, String textToSign) { + byte[] hash = Hash.getSha256Hash(textToSign); KeyPair signatureKeyPair = disputeManager.getSignatureKeyPair(); String sigAsHex; @@ -57,25 +60,20 @@ public static String signAndApply(DisputeManager Date: Sat, 12 Sep 2020 20:24:32 -0500 Subject: [PATCH 13/34] 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 4e6751e713b..edd7a54ef66 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 @@ private void doClose(Button closeTicketButton) { 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/34] 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 bfc542a8621..2f20a802bae 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.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 Set getAllDonationAddresses() { // 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/34] 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 fcba3c3b652..4c4e06f5d93 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.SortedList; import javafx.util.Callback; +import javafx.util.Duration; import java.text.DateFormat; import java.text.ParseException; @@ -102,6 +103,7 @@ 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 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 void initialize() { 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 @@ protected void deactivateReOpenDisputeListener() { 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 @@ protected void reOpenDispute() { } } - 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 f7d059b1105..c8661f2bb1d 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 void onSuspiciousDisputeDetected() { /////////////////////////////////////////////////////////////////////////////////////////// @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 f06875202e7..265f5838ecb 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 @@ protected void handleOnSelectDispute(Dispute dispute) { } @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/34] 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 2f20a802bae..7d9d39aaa05 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 Set getAllDonationAddresses() { 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 6a79a9f740d..df80aa8c3fe 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 protobuf.Dispute toProtoMessage() { 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 static Dispute fromProto(protobuf.Dispute proto, CoreProtoResolver corePr 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 3fc8538e157..ea1ff28c540 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 String getNrOfDisputes(boolean isBuyer, Contract contract) { return disputeListService.getNrOfDisputes(isBuyer, contract); } - private T getDisputeList() { + protected T getDisputeList() { return disputeListService.getDisputeList(); } @@ -251,6 +252,20 @@ public void onUpdatedDataReceived() { tryApplyMessages(); cleanupDisputes(); + + getDisputeList().getList().forEach(dispute -> { + 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 @@ protected void onOpenNewDisputeMessage(OpenNewDisputeMessage openNewDisputeMessa String errorMessage = null; Dispute dispute = openNewDisputeMessage.getDispute(); + // Dispute agent sets uid to be sure to identify disputes uniquely to protect against replaying old disputes + dispute.setAgentsUid(UUID.randomUUID().toString()); dispute.setStorage(disputeListService.getStorage()); // Disputes from clients < 1.2.0 always have support type ARBITRATION in dispute as the field didn't exist before dispute.setSupportType(openNewDisputeMessage.getSupportType()); @@ -291,8 +308,10 @@ protected void onOpenNewDisputeMessage(OpenNewDisputeMessage openNewDisputeMessa try { DelayedPayoutTxValidation.validateDonationAddress(dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade); - } catch (DelayedPayoutTxValidation.AddressException e) { - disputesWithInvalidDonationAddress.add(dispute); + DelayedPayoutTxValidation.testIfDisputeTriesReplay(dispute, disputeList.getList()); + } catch (DelayedPayoutTxValidation.AddressException | DelayedPayoutTxValidation.DisputeReplayException e) { + log.error(e.toString()); + validationExceptions.add(e); } PubKeyRing peersPubKeyRing = dispute.isDisputeOpenerIsBuyer() ? contract.getSellerPubKeyRing() : contract.getBuyerPubKeyRing(); @@ -580,6 +599,9 @@ private void doSendPeerOpenedDisputeMessage(Dispute disputeFromOpener, addPriceInfoMessage(dispute, 0); + // Dispute agent sets uid to be sure to identify disputes uniquely to protect against replaying old disputes + dispute.setAgentsUid(UUID.randomUUID().toString()); + disputeList.add(dispute); // We mirrored dispute already! diff --git a/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java b/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java index 088af6f8680..afaf8a7cd1e 100644 --- a/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java +++ b/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java @@ -89,6 +89,7 @@ public MediationManager(P2PService p2PService, 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 f00aca5fbde..ab3008d8b10 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 RefundManager(P2PService p2PService, 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 023f9d608db..25eb0771208 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.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 static void validateDonationAddress(String addressAsString, DaoFacade dao "\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 static void validatePayoutTx(Trade trade, 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 static void validatePayoutTxInput(Transaction depositTx, /////////////////////////////////////////////////////////////////////////////////////////// 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 static class InvalidInputException extends ValidationException { 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 3e349efc4db..2fd0b33e8e2 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 @@ private void doCloseIfValid(Button closeTicketButton) { 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 @@ private void doCloseIfValid(Button closeTicketButton) { 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 c8661f2bb1d..b2802c28b43 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.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 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 void initialize() { 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 @@ protected void activate() { suspiciousDisputeDetected(); } - disputeManager.getDisputesWithInvalidDonationAddress().addListener(disputesWithInvalidDonationAddressListener); - showWarningForInvalidDonationAddress(disputeManager.getDisputesWithInvalidDonationAddress()); + disputeManager.getValidationExceptions().addListener(validationExceptionListener); + showWarningForValidationExceptions(disputeManager.getValidationExceptions()); } @Override @@ -155,7 +163,7 @@ protected void deactivate() { 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 b10a4a02daf..29efbd465b4 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/34] 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 df80aa8c3fe..8087e9a1b35 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.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 static Dispute fromProto(protobuf.Dispute proto, CoreProtoResolver corePr 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/34] 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 7acd1beb29a..f87b45eefbb 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 @@ protected void deactivateReOpenDisputeListener() { 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/34] 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 77117803935..6b900895821 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 static void validateDonationAddress(@Nullable Dispute dispute, String add 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 static void testIfDisputeTriesReplay(Dispute disputeToTest, List 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/34] 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 822ba7656e4..33e6a0e38f1 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 void onUpdatedDataReceived() { cleanupDisputes(); getDisputeList().getList().forEach(dispute -> { - 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 6b900895821..3cc1d3455f2 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.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 static void validateDonationAddress(@Nullable Dispute dispute, String add 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 static void validateDonationAddress(@Nullable Dispute dispute, String add } } - 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 static void testIfDisputeTriesReplay(Dispute disputeToTest, List 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/34] 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 33e6a0e38f1..fc74453bdb5 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 void onUpdatedDataReceived() { tryApplyMessages(); cleanupDisputes(); - getDisputeList().getList().forEach(dispute -> { + ObservableList disputes = getDisputeList().getList(); + disputes.forEach(dispute -> { try { DelayedPayoutTxValidation.validateDonationAddress(dispute, dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade); } catch (DelayedPayoutTxValidation.AddressException e) { @@ -269,7 +270,7 @@ public void onUpdatedDataReceived() { }); - DelayedPayoutTxValidation.testIfAnyDisputeTriedReplay(getDisputeList().getList(), + DelayedPayoutTxValidation.testIfAnyDisputeTriedReplay(disputes, disputeReplayException -> { 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 f87b45eefbb..cc2f317db69 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 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.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 enum FilterResult { 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 void initialize() { 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 @@ else if (sortedList.size() > 0) 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 @@ protected void applyFilteredListPredicate(String filterString) { } 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 @@ protected FilterResult getFilterResult(Dispute dispute, String filterString) { 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 @@ protected FilterResult getFilterResult(Dispute dispute, String filterString) { 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 @@ private void showCompactReport() { 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 @@ private void showCompactReport() { 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("Trade date: ").append(tradeDateString) .append("\n") - .append("Opening date: ") - .append(DisplayUtils.formatDateTime(openingDate)) + .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 @@ private void showCompactReport() { .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 @@ private void showCompactReport() { 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 @@ private void showCompactReport() { 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 @@ private void showCompactReport() { .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/34] 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 7d9d39aaa05..ce52e74e674 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.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 int getFirstBlockOfPhaseForDisplay(int height, DaoPhase.Phase phase) { 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 cc2f317db69..badfe8c339b 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.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.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 enum FilterResult { 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 DisputeView(DisputeManager> AccountAgeWitnessService accountAgeWitnessService, MediatorManager mediatorManager, RefundAgentManager refundAgentManager, + DaoFacade daoFacade, boolean useDevPrivilegeKeys) { this.disputeManager = disputeManager; this.keyRing = keyRing; @@ -197,6 +202,7 @@ public DisputeView(DisputeManager> this.accountAgeWitnessService = accountAgeWitnessService; this.mediatorManager = mediatorManager; this.refundAgentManager = refundAgentManager; + this.daoFacade = daoFacade; this.useDevPrivilegeKeys = useDevPrivilegeKeys; } @@ -612,6 +618,7 @@ private void showCompactReport() { 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 @@ private void showCompactReport() { .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 @@ private void showCompactReport() { 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 @@ private void showCompactReport() { 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 dbfdc1db3be..b8241353ad0 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 @@ 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 DisputeAgentView(DisputeManager Date: Sun, 20 Sep 2020 21:31:51 -0500 Subject: [PATCH 23/34] 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 8087e9a1b35..0bc342bbf5d 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 Dispute(String tradeId, this.supportType = supportType; id = tradeId + "_" + traderId; + uid = UUID.randomUUID().toString(); } @Override @@ -238,7 +240,6 @@ public protobuf.Dispute toProtoMessage() { 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 static Dispute fromProto(protobuf.Dispute proto, CoreProtoResolver corePr 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 fc74453bdb5..59cc848ff10 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 @@ protected void onOpenNewDisputeMessage(OpenNewDisputeMessage openNewDisputeMessa String errorMessage = null; Dispute dispute = openNewDisputeMessage.getDispute(); - // Dispute agent sets uid to be sure to identify disputes uniquely to protect against replaying old disputes - dispute.setAgentsUid(UUID.randomUUID().toString()); dispute.setStorage(disputeListService.getStorage()); // Disputes from clients < 1.2.0 always have support type ARBITRATION in dispute as the field didn't exist before dispute.setSupportType(openNewDisputeMessage.getSupportType()); @@ -607,9 +605,6 @@ private void doSendPeerOpenedDisputeMessage(Dispute disputeFromOpener, addPriceInfoMessage(dispute, 0); - // Dispute agent sets uid to be sure to identify disputes uniquely to protect against replaying old disputes - dispute.setAgentsUid(UUID.randomUUID().toString()); - disputeList.add(dispute); // We mirrored dispute already! diff --git a/core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java b/core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java index 3cc1d3455f2..27bd50b0988 100644 --- a/core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java +++ b/core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java @@ -114,22 +114,22 @@ private static Tuple3>, Map>, Map> 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 @@ private static void testIfDisputeTriesReplay(Dispute disputeToTest, 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 29efbd465b4..b10a4a02daf 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/34] 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 59cc848ff10..3e47353102c 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.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 void onUpdatedDataReceived() { ObservableList 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 @@ protected void onOpenNewDisputeMessage(OpenNewDisputeMessage openNewDisputeMessa addPriceInfoMessage(dispute, 0); try { - DelayedPayoutTxValidation.validateDonationAddress(dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade); - DelayedPayoutTxValidation.testIfDisputeTriesReplay(dispute, disputeList.getList()); - } catch (DelayedPayoutTxValidation.AddressException | DelayedPayoutTxValidation.DisputeReplayException e) { + TradeDataValidation.validateDonationAddress(dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade); + TradeDataValidation.testIfDisputeTriesReplay(dispute, disputeList.getList()); + } catch (TradeDataValidation.AddressException | TradeDataValidation.DisputeReplayException e) { log.error(e.toString()); validationExceptions.add(e); } @@ -370,12 +370,12 @@ protected void onPeerOpenedDisputeMessage(PeerOpenedDisputeMessage peerOpenedDis Trade trade = optionalTrade.get(); try { - DelayedPayoutTxValidation.validatePayoutTx(trade, + TradeDataValidation.validatePayoutTx(trade, trade.getDelayedPayoutTx(), dispute, daoFacade, btcWalletService); - } 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/core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java b/core/src/main/java/bisq/core/trade/TradeDataValidation.java similarity index 99% rename from core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java rename to core/src/main/java/bisq/core/trade/TradeDataValidation.java index 27bd50b0988..b4ff1628eb4 100644 --- a/core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java +++ b/core/src/main/java/bisq/core/trade/TradeDataValidation.java @@ -48,7 +48,7 @@ import static com.google.common.base.Preconditions.checkNotNull; @Slf4j -public class DelayedPayoutTxValidation { +public class TradeDataValidation { public static void validateDonationAddress(String addressAsString, DaoFacade daoFacade) throws AddressException { @@ -96,7 +96,7 @@ public static void testIfAnyDisputeTriedReplay(List disputeList, public static void testIfDisputeTriesReplay(Dispute dispute, List 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 eae15563407..411e31bdf07 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 @@ private void initPendingTrades() { } 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 3aaf36a39d9..99c12216206 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 @@ protected void run() { 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 @@ protected void run() { // 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 7853767d276..a57be35a15c 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 @@ protected void run() { 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 da12af29087..9cf8613cc22 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.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 void onFailure(TxBroadcastException exception) { 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 @@ private void doCloseIfValid(Button closeTicketButton) { 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 04709f358cb..ae7f141542e 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.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 @@ private void doOpenDispute(boolean isSupportTicket, Transaction depositTx) { 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 716d6bae04b..e094ac9ccd2 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.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 @@ protected String getPeriodOverWarnText() { 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 6096e0cf3d2..13fb709d7d1 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.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 @@ private void showPopup() { 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 b8241353ad0..7866e2260e4 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.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 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 @@ protected void showWarningForValidationExceptions(List !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/34] 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 72ed1450bf0..f9096ec2ce7 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 00000000000..2eab99db9ae --- /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 ff22c42153b..22eac439a5f 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.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.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 f2b925081ef..edf96413c87 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.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.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 517f57be089..5aab52269a6 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 @@ 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 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 750cfe72fc4..dd1d53be109 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.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.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 void initialize() { }; 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 f9c803be1e9..ef8c6951096 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.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.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 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 @@ private void initializeGeneralOptions() { // ignoreTraders ignoreTradersListInputTextField = addInputTextField(root, ++gridRow, Res.get("setting.preferences.ignorePeers")); - RegexValidator regexValidator = GUIUtil.addressRegexValidator(); + RegexValidator regexValidator = RegexValidatorFactory.addressRegexValidator(); ignoreTradersListInputTextField.setValidator(regexValidator); ignoreTradersListInputTextField.setErrorMessage(Res.get("validation.invalidAddressList")); ignoreTradersListListener = (observable, oldValue, newValue) -> { @@ -674,9 +675,9 @@ private void initializeAutoConfirmOptions() { autoConfServiceAddressListener = (observable, oldValue, newValue) -> { 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 9ee9da36541..b7b6b8a2096 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.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 static MaterialDesignIcon getIconForSignState(AccountAgeWitnessService.Si 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/34] 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 3e47353102c..f08ca124530 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 void onUpdatedDataReceived() { disputes.forEach(dispute -> { 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 b4ff1628eb4..ae23c32394b 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.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 static void validateDonationAddress(String addressAsString, DaoFacade dao 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 static class DisputeReplayException extends ValidationException { 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 7866e2260e4..41a1f2a3b6d 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 void initialize() { 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/34] 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 0bc342bbf5d..2cfd0850d7f 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 String toString() { 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 f08ca124530..354414a8fe5 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 @@ protected void onOpenNewDisputeMessage(OpenNewDisputeMessage openNewDisputeMessa try { TradeDataValidation.validateDonationAddress(dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade); TradeDataValidation.testIfDisputeTriesReplay(dispute, disputeList.getList()); - } catch (TradeDataValidation.AddressException | TradeDataValidation.DisputeReplayException e) { + TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getBuyerNodeAddress()); + TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getSellerNodeAddress()); + } catch (TradeDataValidation.AddressException | + TradeDataValidation.DisputeReplayException | + TradeDataValidation.NodeAddressException e) { log.error(e.toString()); validationExceptions.add(e); } diff --git a/core/src/main/java/bisq/core/util/validation/RegexValidator.java b/core/src/main/java/bisq/core/util/validation/RegexValidator.java index f9096ec2ce7..2ee772f36cf 100644 --- a/core/src/main/java/bisq/core/util/validation/RegexValidator.java +++ b/core/src/main/java/bisq/core/util/validation/RegexValidator.java @@ -3,7 +3,6 @@ import bisq.core.locale.Res; public class RegexValidator extends InputValidator { - private String pattern; private String errorMessage; diff --git a/core/src/test/java/bisq/core/util/RegexValidatorTest.java b/core/src/test/java/bisq/core/util/RegexValidatorTest.java index 27bca17b4ea..67d5075adeb 100644 --- a/core/src/test/java/bisq/core/util/RegexValidatorTest.java +++ b/core/src/test/java/bisq/core/util/RegexValidatorTest.java @@ -30,7 +30,9 @@ 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")); @@ -308,5 +310,4 @@ public void testLocalhostAddressRegexValidator() { assertFalse(regexValidator.validate("-example.com").isValid); assertFalse(regexValidator.validate("example-.com").isValid); } - } 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 9cf8613cc22..0400065d33f 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 @@ -99,8 +99,6 @@ @Slf4j public class DisputeSummaryWindow extends Overlay { - - private final CoinFormatter formatter; private final MediationManager mediationManager; private final RefundManager refundManager; @@ -862,6 +860,7 @@ private void doClose(Button closeTicketButton) { } 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 ae7f141542e..fe94474fe79 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 @@ private void doOpenDispute(boolean isSupportTicket, Transaction depositTx) { 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 @@ private void doOpenDispute(boolean isSupportTicket, Transaction depositTx) { isSupportTicket, SupportType.REFUND); - String tradeId = dispute.getTradeId(); mediationManager.findDispute(tradeId) .ifPresent(mediatorsDispute -> { @@ -682,7 +681,7 @@ private void doOpenDispute(boolean isSupportTicket, Transaction depositTx) { }); 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 e094ac9ccd2..93f2c20ed0f 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 @@ private void validatePayoutTx() { } 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 13fb709d7d1..7002de2b9f2 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 @@ private void validatePayoutTx() { } 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 badfe8c339b..1f8bfb57d25 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 enum FilterResult { 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/34] 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 1f8bfb57d25..e23df1f2eb6 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 @@ protected void applyFilteredListPredicate(String filterString) { } } - 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/34] 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 354414a8fe5..c6533c2b8ff 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.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 DisputeManager(P2PService p2PService, DaoFacade daoFacade, KeyRing keyRing, DisputeListService disputeListService, + Config config, PriceFeedService priceFeedService) { super(p2PService, walletsSetup); @@ -124,6 +127,7 @@ public DisputeManager(P2PService p2PService, this.pubKeyRing = keyRing.getPubKeyRing(); signatureKeyPair = keyRing.getSignatureKeyPair(); this.disputeListService = disputeListService; + this.config = config; this.priceFeedService = priceFeedService; } @@ -263,8 +267,8 @@ public void onUpdatedDataReceived() { disputes.forEach(dispute -> { 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 @@ protected void onOpenNewDisputeMessage(OpenNewDisputeMessage openNewDisputeMessa try { TradeDataValidation.validateDonationAddress(dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade); TradeDataValidation.testIfDisputeTriesReplay(dispute, disputeList.getList()); - 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.DisputeReplayException | TradeDataValidation.NodeAddressException e) { 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 5a1738c3180..c1e6c8d4d6c 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 @@ -54,6 +54,7 @@ import bisq.common.Timer; import bisq.common.UserThread; import bisq.common.app.Version; +import bisq.common.config.Config; import bisq.common.crypto.KeyRing; import bisq.common.crypto.PubKeyRing; @@ -94,9 +95,10 @@ public ArbitrationManager(P2PService p2PService, DaoFacade daoFacade, KeyRing keyRing, ArbitrationDisputeListService arbitrationDisputeListService, + Config config, PriceFeedService priceFeedService) { super(p2PService, tradeWalletService, walletService, walletsSetup, tradeManager, closedTradableManager, - openOfferManager, daoFacade, keyRing, arbitrationDisputeListService, priceFeedService); + openOfferManager, daoFacade, keyRing, arbitrationDisputeListService, config, priceFeedService); } diff --git a/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java b/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java index 967c490a4c3..ce94b2f3e32 100644 --- a/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java +++ b/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java @@ -47,6 +47,7 @@ import bisq.common.Timer; import bisq.common.UserThread; import bisq.common.app.Version; +import bisq.common.config.Config; import bisq.common.crypto.KeyRing; import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ResultHandler; @@ -84,9 +85,10 @@ public MediationManager(P2PService p2PService, 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 fc616a2475c..25e6b182753 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.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 RefundManager(P2PService p2PService, 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 ae23c32394b..eea5799225b 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.network.p2p.NodeAddress; +import bisq.common.config.Config; import bisq.common.util.Tuple3; import org.bitcoinj.core.Address; @@ -58,9 +59,9 @@ public static void validateDonationAddress(String addressAsString, DaoFacade dao 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/34] 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 c6533c2b8ff..bd1a33d0cc2 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 @@ protected void onOpenNewDisputeMessage(OpenNewDisputeMessage openNewDisputeMessa Contract contract = dispute.getContract(); addPriceInfoMessage(dispute, 0); - try { - TradeDataValidation.validateDonationAddress(dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade); - TradeDataValidation.testIfDisputeTriesReplay(dispute, disputeList.getList()); - TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getBuyerNodeAddress(), config); - TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getSellerNodeAddress(), config); - } catch (TradeDataValidation.AddressException | - TradeDataValidation.DisputeReplayException | - TradeDataValidation.NodeAddressException e) { - log.error(e.toString()); - validationExceptions.add(e); - } - PubKeyRing peersPubKeyRing = dispute.isDisputeOpenerIsBuyer() ? contract.getSellerPubKeyRing() : contract.getBuyerPubKeyRing(); if (isAgent(dispute)) { if (!disputeList.contains(dispute)) { @@ -359,6 +347,18 @@ protected void onOpenNewDisputeMessage(OpenNewDisputeMessage openNewDisputeMessa } addMediationResultMessage(dispute); + + try { + TradeDataValidation.validateDonationAddress(dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade); + TradeDataValidation.testIfDisputeTriesReplay(dispute, disputeList.getList()); + TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getBuyerNodeAddress(), config); + TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getSellerNodeAddress(), config); + } catch (TradeDataValidation.AddressException | + TradeDataValidation.DisputeReplayException | + TradeDataValidation.NodeAddressException e) { + log.error(e.toString()); + validationExceptions.add(e); + } } // Not-dispute-requester receives that msg from dispute agent 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 0400065d33f..82fd1c09f30 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 @@ -733,7 +733,6 @@ private void doPayout(Coin buyerPayoutAmount, 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/34] 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 b49ede27862..06f892731ca 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 cfc35e89681..4c91de86146 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.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.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 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 void onMinuteTick() { updateTimeLeft(); } }; + + newBestBlockListener = block -> { + checkIfLockTimeIsOver(); + }; } public void activate() { @@ -200,14 +211,34 @@ public void onUpdatedDataReceived() { } 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 void deactivate() { 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 @@ private void updateDisputeState(Trade.DisputeState disputeState) { 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 @@ private void updateDisputeState(Trade.DisputeState disputeState) { 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 @@ private void openMediationResultPopup(String headLine) { 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 @@ protected boolean isDisputed() { 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 93f2c20ed0f..cc916cf23d2 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.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 BuyerStep1View(PendingTradesViewModel model) { } @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 7002de2b9f2..6ee580f815b 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 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 BuyerStep2View(PendingTradesViewModel model) { 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 void deactivate() { } } + @Override + protected void onPendingTradesInitialized() { + super.onPendingTradesInitialized(); + validatePayoutTx(); + } + + /////////////////////////////////////////////////////////////////////////////////////////// // Content /////////////////////////////////////////////////////////////////////////////////////////// From f37446b9919a9905d5c7dda97ea983924bcf52af Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Fri, 25 Sep 2020 09:39:45 -0500 Subject: [PATCH 32/34] Change log level --- .../src/main/java/bisq/core/support/dispute/DisputeManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 bd1a33d0cc2..c5dbe54b09a 100644 --- a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java +++ b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java @@ -388,7 +388,7 @@ protected void onPeerOpenedDisputeMessage(PeerOpenedDisputeMessage peerOpenedDis // 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.warn("Donation address invalid. {}", e.toString()); } if (!isAgent(dispute)) { From 8ac468d84f41ea125b415be080b0c62fd3f867fc Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Fri, 25 Sep 2020 14:23:06 -0500 Subject: [PATCH 33/34] Commit to trigger travis as it got stuck... --- .../src/main/java/bisq/core/support/dispute/DisputeManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 c5dbe54b09a..bc4f53c6037 100644 --- a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java +++ b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java @@ -388,7 +388,7 @@ protected void onPeerOpenedDisputeMessage(PeerOpenedDisputeMessage peerOpenedDis // 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.warn("Donation address invalid. {}", e.toString()); + log.warn("Donation address is invalid. {}", e.toString()); } if (!isAgent(dispute)) { From 423cc7134ed7e85d36a314a30f741074ca5a57f9 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Fri, 25 Sep 2020 15:01:20 -0500 Subject: [PATCH 34/34] Add changes from merge conflict (class was renamed) --- .../bisq/core/trade/DelayedPayoutTxValidation.java | 0 .../java/bisq/core/trade/TradeDataValidation.java | 14 +++++--------- 2 files changed, 5 insertions(+), 9 deletions(-) delete mode 100644 core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java diff --git a/core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java b/core/src/main/java/bisq/core/trade/DelayedPayoutTxValidation.java deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/core/src/main/java/bisq/core/trade/TradeDataValidation.java b/core/src/main/java/bisq/core/trade/TradeDataValidation.java index eea5799225b..993759b873e 100644 --- a/core/src/main/java/bisq/core/trade/TradeDataValidation.java +++ b/core/src/main/java/bisq/core/trade/TradeDataValidation.java @@ -291,16 +291,12 @@ public static void validatePayoutTx(Trade trade, } NetworkParameters params = btcWalletService.getParams(); - Address address = output.getAddressFromP2PKHScript(params); + Address address = output.getScriptPubKey().getToAddress(params); if (address == null) { - // 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; - log.error(errorMsg); - log.error(delayedPayoutTx.toString()); - throw new AddressException(dispute, errorMsg); - } + errorMsg = "Donation address cannot be resolved (not of type P2PK nor P2SH nor P2WH). Output: " + output; + log.error(errorMsg); + log.error(delayedPayoutTx.toString()); + throw new AddressException(dispute, errorMsg); } String addressAsString = address.toString();