From 190003b5bad1994f8d3b500be6384decd94785b4 Mon Sep 17 00:00:00 2001 From: woodser Date: Fri, 10 Feb 2023 11:10:51 -0500 Subject: [PATCH] migrate to DisputeValidation Co-authored-by: HenrikJannsen --- .../bisq/core/support/dispute/Dispute.java | 35 +- .../core/support/dispute/DisputeManager.java | 41 +-- .../support/dispute/DisputeValidation.java | 299 ++++++++++++++++++ .../main/java/bisq/core/trade/Contract.java | 9 + .../bisq/core/trade/TradeDataValidation.java | 162 ---------- .../dispute/agent/DisputeAgentView.java | 13 +- 6 files changed, 369 insertions(+), 190 deletions(-) create mode 100644 core/src/main/java/bisq/core/support/dispute/DisputeValidation.java 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 b8091eeab3..f1db16de90 100644 --- a/core/src/main/java/bisq/core/support/dispute/Dispute.java +++ b/core/src/main/java/bisq/core/support/dispute/Dispute.java @@ -104,7 +104,7 @@ public final class Dispute implements NetworkPayload, PersistablePayload { private final String depositTxId; @Nullable private final String payoutTxId; - private final String contractAsJson; + private String contractAsJson; @Nullable private final String makerContractSignature; @Nullable @@ -351,6 +351,39 @@ public final class Dispute implements NetworkPayload, PersistablePayload { return !chatMessages.isEmpty() && chatMessages.get(0).getSupportType() == SupportType.MEDIATION; } + public boolean removeAllChatMessages() { + if (chatMessages.size() > 1) { + // removes all chat except the initial guidelines message. + String firstMessageUid = chatMessages.get(0).getUid(); + chatMessages.removeIf((msg) -> !msg.getUid().equals(firstMessageUid)); + return true; + } + return false; + } + + public void maybeClearSensitiveData() { + String change = ""; + if (contract.maybeClearSensitiveData()) { + change += "contract;"; + } + String edited = Contract.sanitizeContractAsJson(contractAsJson); + if (!edited.equals(contractAsJson)) { + contractAsJson = edited; + change += "contractAsJson;"; + } + if (removeAllChatMessages()) { + change += "chat messages;"; + } + if (change.length() > 0) { + log.info("cleared sensitive data from {} of dispute for trade {}", change, Utilities.getShortId(getTradeId())); + } + } + + // sanitizes a contract json string + public static String sanitizeContractAsJson(String contractAsJson) { + return contractAsJson; + } + /////////////////////////////////////////////////////////////////////////////////////////// // Setters /////////////////////////////////////////////////////////////////////////////////////////// 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 2faeeb5789..f39117be2c 100644 --- a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java +++ b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java @@ -40,7 +40,6 @@ import bisq.core.trade.ClosedTradableManager; import bisq.core.trade.Contract; import bisq.core.trade.HavenoUtils; import bisq.core.trade.Trade; -import bisq.core.trade.TradeDataValidation; import bisq.core.trade.TradeManager; import bisq.core.trade.protocol.TradePeer; import bisq.network.p2p.BootstrapListener; @@ -67,6 +66,7 @@ import javafx.collections.ObservableList; import java.math.BigInteger; import java.security.KeyPair; +import java.time.Instant; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -101,7 +101,7 @@ public abstract class DisputeManager> extends Sup private final PriceFeedService priceFeedService; @Getter - protected final ObservableList validationExceptions = + protected final ObservableList validationExceptions = FXCollections.observableArrayList(); /////////////////////////////////////////////////////////////////////////////////////////// @@ -272,21 +272,14 @@ public abstract class DisputeManager> extends Sup List disputes = getDisputeList().getList(); disputes.forEach(dispute -> { try { - TradeDataValidation.validateDonationAddress(dispute, dispute.getDonationAddressOfDelayedPayoutTx()); - TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getBuyerNodeAddress(), config); - TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getSellerNodeAddress(), config); - } catch (TradeDataValidation.AddressException | TradeDataValidation.NodeAddressException e) { + DisputeValidation.validateNodeAddresses(dispute, config); + } catch (DisputeValidation.ValidationException e) { log.error(e.toString()); validationExceptions.add(e); } }); - // TODO (woodser): disabled for xmr, needed? -// TradeDataValidation.testIfAnyDisputeTriedReplay(disputes, -// disputeReplayException -> { -// log.error(disputeReplayException.toString()); -// validationExceptions.add(disputeReplayException); -// }); + maybeClearSensitiveData(); } public boolean isTrader(Dispute dispute) { @@ -304,6 +297,16 @@ public abstract class DisputeManager> extends Sup } } + public void maybeClearSensitiveData() { + log.info("{} checking closed disputes eligibility for having sensitive data cleared", super.getClass().getSimpleName()); + Instant safeDate = closedTradableManager.getSafeDateForSensitiveDataClearing(); + getDisputeList().getList().stream() + .filter(e -> e.isClosed()) + .filter(e -> e.getOpeningDate().toInstant().isBefore(safeDate)) + .forEach(Dispute::maybeClearSensitiveData); + requestPersistence(); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Dispute handling /////////////////////////////////////////////////////////////////////////////////////////// @@ -457,14 +460,12 @@ public abstract class DisputeManager> extends Sup // validate dispute try { - TradeDataValidation.validatePaymentAccountPayload(dispute); - TradeDataValidation.validateDonationAddress(dispute.getDonationAddressOfDelayedPayoutTx()); - //TradeDataValidation.testIfDisputeTriesReplay(dispute, disputeList.getList()); // TODO (woodser): disabled for xmr, needed? - TradeDataValidation.validateNodeAddress(dispute, contract.getBuyerNodeAddress(), config); - TradeDataValidation.validateNodeAddress(dispute, contract.getSellerNodeAddress(), config); - } catch (TradeDataValidation.AddressException | - TradeDataValidation.NodeAddressException | - TradeDataValidation.InvalidPaymentAccountPayloadException e) { + DisputeValidation.validateDisputeData(dispute); + DisputeValidation.validateNodeAddresses(dispute, config); + DisputeValidation.validateSenderNodeAddress(dispute, message.getSenderNodeAddress()); + DisputeValidation.validatePaymentAccountPayload(dispute); + //DisputeValidation.testIfDisputeTriesReplay(dispute, disputeList.getList()); + } catch (DisputeValidation.ValidationException e) { validationExceptions.add(e); throw e; } diff --git a/core/src/main/java/bisq/core/support/dispute/DisputeValidation.java b/core/src/main/java/bisq/core/support/dispute/DisputeValidation.java new file mode 100644 index 0000000000..2f2d6111a9 --- /dev/null +++ b/core/src/main/java/bisq/core/support/dispute/DisputeValidation.java @@ -0,0 +1,299 @@ +/* + * This file is part of Haveno. + * + * Haveno 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. + * + * Haveno 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 Haveno. If not, see . + */ + +package bisq.core.support.dispute; + +import bisq.core.support.SupportType; +import bisq.core.trade.Contract; +import bisq.core.trade.Trade; +import bisq.core.util.JsonUtil; +import bisq.core.util.validation.RegexValidatorFactory; + +import bisq.network.p2p.NodeAddress; + +import bisq.common.config.Config; +import bisq.common.crypto.CryptoException; +import bisq.common.crypto.Hash; +import bisq.common.crypto.Sig; +import bisq.common.util.Tuple3; + +import org.bitcoinj.core.Address; +import org.bitcoinj.core.NetworkParameters; +import org.bitcoinj.core.Transaction; +import org.bitcoinj.core.TransactionOutput; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Consumer; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +@Slf4j +public class DisputeValidation { + + public static void validatePaymentAccountPayload(Dispute dispute) throws ValidationException { + if (dispute.getSellerPaymentAccountPayload() == null) throw new ValidationException(dispute, "Seller's payment account payload is null in dispute opened for trade " + dispute.getTradeId()); + if (!Arrays.equals(dispute.getSellerPaymentAccountPayload().getHash(), dispute.getContract().getSellerPaymentAccountPayloadHash())) throw new ValidationException(dispute, "Hash of maker's payment account payload does not match contract"); + } + + public static void validateDisputeData(Dispute dispute) throws ValidationException { + try { + Contract contract = dispute.getContract(); + checkArgument(contract.getOfferPayload().getId().equals(dispute.getTradeId()), "Invalid tradeId"); + checkArgument(dispute.getContractAsJson().equals(JsonUtil.objectToJson(contract)), "Invalid contractAsJson"); + checkArgument(Arrays.equals(Objects.requireNonNull(dispute.getContractHash()), Hash.getSha256Hash(checkNotNull(dispute.getContractAsJson()))), + "Invalid contractHash"); + + try { + // Only the dispute opener has set the signature + String makerContractSignature = dispute.getMakerContractSignature(); + if (makerContractSignature != null) { + Sig.verify(contract.getMakerPubKeyRing().getSignaturePubKey(), + dispute.getContractAsJson(), + makerContractSignature); + } + String takerContractSignature = dispute.getTakerContractSignature(); + if (takerContractSignature != null) { + Sig.verify(contract.getTakerPubKeyRing().getSignaturePubKey(), + dispute.getContractAsJson(), + takerContractSignature); + } + } catch (CryptoException e) { + throw new ValidationException(dispute, e.getMessage()); + } + } catch (Throwable t) { + throw new ValidationException(dispute, t.getMessage()); + } + } + + public static void validateTradeAndDispute(Dispute dispute, Trade trade) + throws ValidationException { + try { + checkArgument(dispute.getContract().equals(trade.getContract()), + "contract must match contract from trade"); + + } catch (Throwable t) { + throw new ValidationException(dispute, t.getMessage()); + } + } + + + public static void validateSenderNodeAddress(Dispute dispute, + NodeAddress senderNodeAddress) throws NodeAddressException { + if (!senderNodeAddress.equals(dispute.getContract().getBuyerNodeAddress()) + && !senderNodeAddress.equals(dispute.getContract().getSellerNodeAddress()) + && !senderNodeAddress.equals(dispute.getContract().getArbitratorNodeAddress())) { + throw new NodeAddressException(dispute, "senderNodeAddress not matching any of the traders node addresses"); + } + } + + public static void validateNodeAddresses(Dispute dispute, Config config) + throws NodeAddressException { + if (!config.useLocalhostForP2P) { + validateNodeAddress(dispute, dispute.getContract().getBuyerNodeAddress()); + validateNodeAddress(dispute, dispute.getContract().getSellerNodeAddress()); + } + } + + private 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(Dispute dispute, + Transaction delayedPayoutTx, + NetworkParameters params) + throws AddressException { + TransactionOutput output = delayedPayoutTx.getOutput(0); + Address address = output.getScriptPubKey().getToAddress(params); + if (address == null) { + String 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 DisputeValidation.AddressException(dispute, errorMsg); + } + + // Verify that address in the dispute matches the one in the trade. + String delayedPayoutTxOutputAddress = address.toString(); + checkArgument(delayedPayoutTxOutputAddress.equals(dispute.getDonationAddressOfDelayedPayoutTx()), + "donationAddressOfDelayedPayoutTx from dispute does not match address from delayed payout tx. " + + "delayedPayoutTxOutputAddress=" + delayedPayoutTxOutputAddress + + "; dispute.getDonationAddressOfDelayedPayoutTx()=" + dispute.getDonationAddressOfDelayedPayoutTx()); + } + + 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 = 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 uid = dispute.getUid(); + + String tradeId = dispute.getTradeId(); + disputesPerTradeId.putIfAbsent(tradeId, new HashSet<>()); + Set set = disputesPerTradeId.get(tradeId); + set.add(uid); + + String delayedPayoutTxId = dispute.getDelayedPayoutTxId(); + if (delayedPayoutTxId != null) { + disputesPerDelayedPayoutTxId.putIfAbsent(delayedPayoutTxId, new HashSet<>()); + set = disputesPerDelayedPayoutTxId.get(delayedPayoutTxId); + set.add(uid); + } + + String depositTxId = dispute.getDepositTxId(); + if (depositTxId != null) { + disputesPerDepositTxId.putIfAbsent(depositTxId, new HashSet<>()); + set = disputesPerDepositTxId.get(depositTxId); + set.add(uid); + } + }); + + 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 disputeToTestUid = disputeToTest.getUid(); + + // For pre v1.4.0 we do not get the delayed payout tx sent in mediation cases but in refund agent case we do. + // So until all users have updated to 1.4.0 we only check in refund agent case. With 1.4.0 we send the + // delayed payout tx also in mediation cases and that if check can be removed. + if (disputeToTest.getSupportType() == SupportType.REFUND) { + checkNotNull(disputeToTestDelayedPayoutTxId, + "Delayed payout transaction ID is null. " + + "Trade ID: " + disputeToTestTradeId); + } + checkNotNull(disputeToTestDepositTxId, + "depositTxId must not be null. Trade ID: " + disputeToTestTradeId); + checkNotNull(disputeToTestUid, + "agentsUid must not be null. Trade ID: " + disputeToTestTradeId); + + Set disputesPerTradeIdItems = disputesPerTradeId.get(disputeToTestTradeId); + checkArgument(disputesPerTradeIdItems != null && disputesPerTradeIdItems.size() <= 2, + "We found more then 2 disputes with the same trade ID. " + + "Trade ID: " + disputeToTestTradeId); + if (!disputesPerDelayedPayoutTxId.isEmpty()) { + Set disputesPerDelayedPayoutTxIdItems = disputesPerDelayedPayoutTxId.get(disputeToTestDelayedPayoutTxId); + checkArgument(disputesPerDelayedPayoutTxIdItems != null && disputesPerDelayedPayoutTxIdItems.size() <= 2, + "We found more then 2 disputes with the same delayedPayoutTxId. " + + "Trade ID: " + disputeToTestTradeId); + } + if (!disputesPerDepositTxId.isEmpty()) { + Set disputesPerDepositTxIdItems = disputesPerDepositTxId.get(disputeToTestDepositTxId); + checkArgument(disputesPerDepositTxIdItems != null && disputesPerDepositTxIdItems.size() <= 2, + "We found more then 2 disputes with the same depositTxId. " + + "Trade ID: " + disputeToTestTradeId); + } + } catch (IllegalArgumentException e) { + throw new DisputeReplayException(disputeToTest, e.getMessage()); + } catch (NullPointerException e) { + log.error("NullPointerException at testIfDisputeTriesReplay: " + + "disputeToTest={}, disputesPerTradeId={}, disputesPerDelayedPayoutTxId={}, " + + "disputesPerDepositTxId={}", + disputeToTest, disputesPerTradeId, disputesPerDelayedPayoutTxId, disputesPerDepositTxId); + throw new DisputeReplayException(disputeToTest, e.toString() + " at dispute " + disputeToTest.toString()); + } + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Exceptions + /////////////////////////////////////////////////////////////////////////////////////////// + + public static class ValidationException extends Exception { + @Getter + private final Dispute dispute; + + ValidationException(Dispute dispute, String msg) { + super(msg); + this.dispute = dispute; + } + } + + public static class NodeAddressException extends ValidationException { + NodeAddressException(Dispute dispute, String msg) { + super(dispute, msg); + } + } + + + public static class AddressException extends ValidationException { + AddressException(Dispute dispute, String msg) { + super(dispute, msg); + } + } + + public static class DisputeReplayException extends ValidationException { + DisputeReplayException(Dispute dispute, String msg) { + super(dispute, msg); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/Contract.java b/core/src/main/java/bisq/core/trade/Contract.java index fa2bc3f084..35d4d03b84 100644 --- a/core/src/main/java/bisq/core/trade/Contract.java +++ b/core/src/main/java/bisq/core/trade/Contract.java @@ -257,6 +257,15 @@ public final class Contract implements NetworkPayload { return isBuyerMakerAndSellerTaker() == isMyRoleBuyer(myPubKeyRing); } + public boolean maybeClearSensitiveData() { + return false; // TODO: anything to clear? + } + + // edits a contract json string + public static String sanitizeContractAsJson(String contractAsJson) { + return contractAsJson; // TODO: anything to sanitize? + } + public void printDiff(@Nullable String peersContractAsJson) { String json = JsonUtil.objectToJson(this); String diff = StringUtils.difference(json, peersContractAsJson); diff --git a/core/src/main/java/bisq/core/trade/TradeDataValidation.java b/core/src/main/java/bisq/core/trade/TradeDataValidation.java index 2f41eef93b..0ac7955a1f 100644 --- a/core/src/main/java/bisq/core/trade/TradeDataValidation.java +++ b/core/src/main/java/bisq/core/trade/TradeDataValidation.java @@ -19,14 +19,7 @@ package bisq.core.trade; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.offer.Offer; -import bisq.core.support.SupportType; import bisq.core.support.dispute.Dispute; -import bisq.core.util.validation.RegexValidatorFactory; - -import bisq.network.p2p.NodeAddress; - -import bisq.common.config.Config; -import bisq.common.util.Tuple3; import org.bitcoinj.core.Address; import org.bitcoinj.core.Coin; @@ -35,12 +28,6 @@ import org.bitcoinj.core.Transaction; import org.bitcoinj.core.TransactionInput; import org.bitcoinj.core.TransactionOutPoint; import org.bitcoinj.core.TransactionOutput; -import java.util.Arrays; -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; @@ -54,153 +41,6 @@ import static com.google.common.base.Preconditions.checkNotNull; @Slf4j public class TradeDataValidation { - public static void validatePaymentAccountPayload(Dispute dispute) throws InvalidPaymentAccountPayloadException { - if (dispute.getSellerPaymentAccountPayload() == null) throw new InvalidPaymentAccountPayloadException(dispute, "Seller's payment account payload is null in dispute opened for trade " + dispute.getTradeId()); - if (!Arrays.equals(dispute.getSellerPaymentAccountPayload().getHash(), dispute.getContract().getSellerPaymentAccountPayloadHash())) throw new InvalidPaymentAccountPayloadException(dispute, "Hash of maker's payment account payload does not match contract"); - } - - public static void validateDonationAddress(String addressAsString) - throws AddressException { - validateDonationAddress(null, addressAsString); - } - - public static void validateNodeAddress(Dispute dispute, NodeAddress nodeAddress, Config config) - throws NodeAddressException { - 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); - throw new NodeAddressException(dispute, msg); - } - } - - public static void validateDonationAddress(@Nullable Dispute dispute, String addressAsString) - throws AddressException { - - if (addressAsString == null) { - log.debug("address is null at validateDonationAddress. This is expected in case of an not updated trader."); - return; - } - } - - 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 = TradeDataValidation.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 uid = dispute.getUid(); - - String tradeId = dispute.getTradeId(); - disputesPerTradeId.putIfAbsent(tradeId, new HashSet<>()); - Set set = disputesPerTradeId.get(tradeId); - set.add(uid); - - String delayedPayoutTxId = dispute.getDelayedPayoutTxId(); - if (delayedPayoutTxId != null) { - disputesPerDelayedPayoutTxId.putIfAbsent(delayedPayoutTxId, new HashSet<>()); - set = disputesPerDelayedPayoutTxId.get(delayedPayoutTxId); - set.add(uid); - } - - String depositTxId = dispute.getDepositTxId(); - if (depositTxId != null) { - disputesPerDepositTxId.putIfAbsent(depositTxId, new HashSet<>()); - set = disputesPerDepositTxId.get(depositTxId); - set.add(uid); - } - }); - - 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 disputeToTestUid = disputeToTest.getUid(); - - // For pre v1.4.0 we do not get the delayed payout tx sent in mediation cases but in refund agent case we do. - // So until all users have updated to 1.4.0 we only check in refund agent case. With 1.4.0 we send the - // delayed payout tx also in mediation cases and that if check can be removed. - if (disputeToTest.getSupportType() == SupportType.REFUND) { - checkNotNull(disputeToTestDelayedPayoutTxId, - "Delayed payout transaction ID is null. " + - "Trade ID: " + disputeToTestTradeId); - } - checkNotNull(disputeToTestDepositTxId, - "depositTxId must not be null. Trade ID: " + disputeToTestTradeId); - checkNotNull(disputeToTestUid, - "agentsUid must not be null. Trade ID: " + disputeToTestTradeId); - - Set disputesPerTradeIdItems = disputesPerTradeId.get(disputeToTestTradeId); - checkArgument(disputesPerTradeIdItems != null && disputesPerTradeIdItems.size() <= 2, - "We found more then 2 disputes with the same trade ID. " + - "Trade ID: " + disputeToTestTradeId); - if (!disputesPerDelayedPayoutTxId.isEmpty()) { - Set disputesPerDelayedPayoutTxIdItems = disputesPerDelayedPayoutTxId.get(disputeToTestDelayedPayoutTxId); - checkArgument(disputesPerDelayedPayoutTxIdItems != null && disputesPerDelayedPayoutTxIdItems.size() <= 2, - "We found more then 2 disputes with the same delayedPayoutTxId. " + - "Trade ID: " + disputeToTestTradeId); - } - if (!disputesPerDepositTxId.isEmpty()) { - Set disputesPerDepositTxIdItems = disputesPerDepositTxId.get(disputeToTestDepositTxId); - checkArgument(disputesPerDepositTxIdItems != null && disputesPerDepositTxIdItems.size() <= 2, - "We found more then 2 disputes with the same depositTxId. " + - "Trade ID: " + disputeToTestTradeId); - } - } catch (IllegalArgumentException e) { - throw new DisputeReplayException(disputeToTest, e.getMessage()); - } catch (NullPointerException e) { - log.error("NullPointerException at testIfDisputeTriesReplay: " + - "disputeToTest={}, disputesPerTradeId={}, disputesPerDelayedPayoutTxId={}, " + - "disputesPerDepositTxId={}", - disputeToTest, disputesPerTradeId, disputesPerDelayedPayoutTxId, disputesPerDepositTxId); - throw new DisputeReplayException(disputeToTest, e.toString() + " at dispute " + disputeToTest.toString()); - } - } - public static void validateDelayedPayoutTx(Trade trade, Transaction delayedPayoutTx, BtcWalletService btcWalletService) @@ -315,8 +155,6 @@ public class TradeDataValidation { addressConsumer.accept(addressAsString); } - validateDonationAddress(addressAsString); - if (dispute != null) { // Verify that address in the dispute matches the one in the trade. String donationAddressOfDelayedPayoutTx = dispute.getDonationAddressOfDelayedPayoutTx(); 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 36686c925a..fbd46ce9e9 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 @@ -30,9 +30,9 @@ 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.DisputeValidation; import bisq.core.support.dispute.agent.MultipleHolderNameDetection; import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; -import bisq.core.trade.TradeDataValidation; import bisq.core.trade.TradeManager; import bisq.core.user.DontShowAgainLookup; import bisq.core.user.Preferences; @@ -60,13 +60,12 @@ import java.util.List; import org.jetbrains.annotations.NotNull; -import static bisq.core.trade.TradeDataValidation.ValidationException; import static bisq.desktop.util.FormBuilder.getIconForLabel; public abstract class DisputeAgentView extends DisputeView implements MultipleHolderNameDetection.Listener { private final MultipleHolderNameDetection multipleHolderNameDetection; - private ListChangeListener validationExceptionListener; + private ListChangeListener validationExceptionListener; public DisputeAgentView(DisputeManager> disputeManager, KeyRing keyRing, @@ -126,7 +125,7 @@ public abstract class DisputeAgentView extends DisputeView implements MultipleHo }; } - protected void showWarningForValidationExceptions(List exceptions) { + protected void showWarningForValidationExceptions(List exceptions) { exceptions.stream() .filter(ex -> ex.getDispute() != null) .filter(ex -> !ex.getDispute().isClosed()) // we show warnings only for open cases @@ -134,7 +133,7 @@ public abstract class DisputeAgentView extends DisputeView implements MultipleHo .forEach(ex -> new Popup().width(900).warning(getValidationExceptionMessage(ex)).dontShowAgainId(getKey(ex)).show()); } - private String getKey(ValidationException exception) { + private String getKey(DisputeValidation.ValidationException exception) { Dispute dispute = exception.getDispute(); if (dispute != null) { return "ValExcPopup-" + dispute.getTradeId() + "-" + dispute.getTraderId(); @@ -142,9 +141,9 @@ public abstract class DisputeAgentView extends DisputeView implements MultipleHo return "ValExcPopup-" + exception.toString(); } - private String getValidationExceptionMessage(ValidationException exception) { + private String getValidationExceptionMessage(DisputeValidation.ValidationException exception) { Dispute dispute = exception.getDispute(); - if (dispute != null && exception instanceof TradeDataValidation.AddressException) { + if (dispute != null && exception instanceof DisputeValidation.AddressException) { return getAddressExceptionMessage(dispute); } else if (exception.getMessage() != null && !exception.getMessage().isEmpty()) { return exception.getMessage();