diff --git a/core/src/main/java/bisq/core/support/dispute/DisputeValidation.java b/core/src/main/java/bisq/core/support/dispute/DisputeValidation.java index 8b6ec62c22..14b06dcf17 100644 --- a/core/src/main/java/bisq/core/support/dispute/DisputeValidation.java +++ b/core/src/main/java/bisq/core/support/dispute/DisputeValidation.java @@ -31,22 +31,25 @@ import bisq.common.crypto.CryptoException; import bisq.common.crypto.Hash; import bisq.common.crypto.Sig; -import bisq.common.util.Tuple5; import org.bitcoinj.core.Address; import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.Transaction; import org.bitcoinj.core.TransactionOutput; +import com.google.common.base.CaseFormat; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.SetMultimap; + import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; +import java.util.EnumMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; +import java.util.function.Function; import lombok.Getter; import lombok.extern.slf4j.Slf4j; @@ -203,153 +206,104 @@ public static void validateDonationAddress(Dispute dispute, "; dispute.getDonationAddressOfDelayedPayoutTx()=" + dispute.getDonationAddressOfDelayedPayoutTx()); } - // TODO: Refactor: public static void testIfAnyDisputeTriedReplay(List disputeList, Consumer exceptionHandler) { - var tuple = getTestReplayHashMaps(disputeList); - Map> disputesPerTradeId = tuple.first; - Map> disputesPerDelayedPayoutTxId = tuple.second; - Map> disputesPerWarningId = tuple.third; - Map> disputesPerRedirectTxId = tuple.fourth; - Map> disputesPerDepositTxId = tuple.fifth; - + var map = getTestReplayMultimaps(disputeList); disputeList.forEach(disputeToTest -> { try { - testIfDisputeTriesReplay(disputeToTest, - disputesPerTradeId, - disputesPerDelayedPayoutTxId, - disputesPerWarningId, - disputesPerRedirectTxId, - disputesPerDepositTxId); - + testIfDisputeTriesReplay(disputeToTest, map); } catch (DisputeReplayException e) { exceptionHandler.accept(e); } }); } - // TODO: Refactor: public static void testIfDisputeTriesReplay(Dispute dispute, List disputeList) throws DisputeReplayException { - var tuple = getTestReplayHashMaps(disputeList); - Map> disputesPerTradeId = tuple.first; - Map> disputesPerDelayedPayoutTxId = tuple.second; - Map> disputesPerWarningTxId = tuple.third; - Map> disputesPerRedirectTxId = tuple.fourth; - Map> disputesPerDepositTxId = tuple.fifth; - - testIfDisputeTriesReplay(dispute, - disputesPerTradeId, - disputesPerDelayedPayoutTxId, - disputesPerWarningTxId, - disputesPerRedirectTxId, - disputesPerDepositTxId); + testIfDisputeTriesReplay(dispute, getTestReplayMultimaps(disputeList)); } - // TODO: Refactor: - private static Tuple5>, Map>, Map>, Map>, Map>> getTestReplayHashMaps( - List disputeList) { - Map> disputesPerTradeId = new HashMap<>(); - Map> disputesPerDelayedPayoutTxId = new HashMap<>(); - Map> disputesPerWarningTxId = new HashMap<>(); - Map> disputesPerRedirectTxId = new HashMap<>(); - Map> disputesPerDepositTxId = new HashMap<>(); + private static Map> getTestReplayMultimaps(List disputeList) { + Map> disputesPerIdMap = new EnumMap<>(DisputeIdField.class); disputeList.forEach(dispute -> { - String uid = dispute.getUid(); + String disputeUid = dispute.getUid(); - String tradeId = dispute.getTradeId(); - disputesPerTradeId.computeIfAbsent(tradeId, id -> new HashSet<>()).add(uid); - - String delayedPayoutTxId = dispute.getDelayedPayoutTxId(); - if (delayedPayoutTxId != null) { - disputesPerDelayedPayoutTxId.computeIfAbsent(delayedPayoutTxId, id -> new HashSet<>()).add(uid); - } - String warningTxId = dispute.getWarningTxId(); - if (warningTxId != null) { - disputesPerWarningTxId.computeIfAbsent(warningTxId, id -> new HashSet<>()).add(uid); - } - String redirectTxId = dispute.getRedirectTxId(); - if (redirectTxId != null) { - disputesPerRedirectTxId.computeIfAbsent(redirectTxId, id -> new HashSet<>()).add(uid); - } - String depositTxId = dispute.getDepositTxId(); - if (depositTxId != null) { - disputesPerDepositTxId.computeIfAbsent(depositTxId, id -> new HashSet<>()).add(uid); + for (var field : DisputeIdField.values()) { + String id = field.apply(dispute); + if (id != null) { + disputesPerIdMap.computeIfAbsent(field, k -> HashMultimap.create()).put(id, disputeUid); + } } }); - return new Tuple5<>(disputesPerTradeId, disputesPerDelayedPayoutTxId, disputesPerWarningTxId, - disputesPerRedirectTxId, disputesPerDepositTxId); + return disputesPerIdMap; } - // TODO: Refactor: private static void testIfDisputeTriesReplay(Dispute disputeToTest, - Map> disputesPerTradeId, - Map> disputesPerDelayedPayoutTxId, - Map> disputesPerWarningTxId, - Map> disputesPerRedirectTxId, - Map> disputesPerDepositTxId) + Map> disputesPerIdMap) throws DisputeReplayException { try { String disputeToTestTradeId = disputeToTest.getTradeId(); - String disputeToTestDelayedPayoutTxId = disputeToTest.getDelayedPayoutTxId(); - String disputeToTestWarningTxId = disputeToTest.getWarningTxId(); - String disputeToTestRedirectTxId = disputeToTest.getRedirectTxId(); - 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. + // With 1.4.0 we send the delayed payout tx also in mediation cases. For v5 protocol trades, there is no DPT + // and it is unknown which staged txs will be published, if any, so they are only sent in refund agent cases. if (disputeToTest.getSupportType() == SupportType.REFUND) { - if (disputeToTestWarningTxId == null) { - checkNotNull(disputeToTestDelayedPayoutTxId, + if (disputeToTest.getWarningTxId() == null) { + checkNotNull(disputeToTest.getDelayedPayoutTxId(), "Delayed payout transaction ID is null. " + "Trade ID: %s", disputeToTestTradeId); } else { - checkNotNull(disputeToTestRedirectTxId, + checkNotNull(disputeToTest.getRedirectTxId(), "Redirect transaction ID is null. " + "Trade ID: %s", disputeToTestTradeId); } } - checkNotNull(disputeToTestDepositTxId, + checkNotNull(disputeToTest.getDepositTxId(), "depositTxId must not be null. Trade ID: %s", disputeToTestTradeId); - checkNotNull(disputeToTestUid, + checkNotNull(disputeToTest.getUid(), "agentsUid must not be null. Trade ID: %s", disputeToTestTradeId); - Set disputesPerTradeIdItems = disputesPerTradeId.get(disputeToTestTradeId); - checkArgument(disputesPerTradeIdItems == null || disputesPerTradeIdItems.size() <= 2, - "We found more than 2 disputes with the same trade ID. " + - "Trade ID: %s", disputeToTestTradeId); - Set disputesPerDelayedPayoutTxIdItems = disputesPerDelayedPayoutTxId.get(disputeToTestDelayedPayoutTxId); - checkArgument(disputesPerDelayedPayoutTxIdItems == null || disputesPerDelayedPayoutTxIdItems.size() <= 2, - "We found more than 2 disputes with the same delayedPayoutTxId. " + - "Trade ID: %s", disputeToTestTradeId); - Set disputesPerWarningTxIdItems = disputesPerWarningTxId.get(disputeToTestWarningTxId); - checkArgument(disputesPerWarningTxIdItems == null || disputesPerWarningTxIdItems.size() <= 2, - "We found more than 2 disputes with the same warningTxId. " + - "Trade ID: %s", disputeToTestTradeId); - Set disputesPerRedirectTxIdItems = disputesPerRedirectTxId.get(disputeToTestRedirectTxId); - checkArgument(disputesPerRedirectTxIdItems == null || disputesPerRedirectTxIdItems.size() <= 2, - "We found more than 2 disputes with the same redirectTxId. " + - "Trade ID: %s", disputeToTestTradeId); - Set disputesPerDepositTxIdItems = disputesPerDepositTxId.get(disputeToTestDepositTxId); - checkArgument(disputesPerDepositTxIdItems == null || disputesPerDepositTxIdItems.size() <= 2, - "We found more than 2 disputes with the same depositTxId. " + - "Trade ID: %s", disputeToTestTradeId); + for (DisputeIdField field : disputesPerIdMap.keySet()) { + String id = field.apply(disputeToTest); + int numDisputesPerId = disputesPerIdMap.get(field).keys().count(id); + checkArgument(numDisputesPerId <= 2, + "We found more than 2 disputes with the same %s. " + + "Trade ID: %s", field, disputeToTestTradeId); + } } catch (IllegalArgumentException e) { throw new DisputeReplayException(disputeToTest, e.getMessage()); } catch (NullPointerException e) { log.error("NullPointerException at testIfDisputeTriesReplay: " + - "disputeToTest={}, disputesPerTradeId={}, disputesPerDelayedPayoutTxId={}, " + - "disputesPerWarningTxId={}, disputesPerRedirectTxId={}, " + - "disputesPerDepositTxId={}", - disputeToTest, disputesPerTradeId, disputesPerDelayedPayoutTxId, disputesPerWarningTxId, - disputesPerRedirectTxId, disputesPerDepositTxId); + "disputeToTest={}, disputesPerIdMap={}", disputeToTest, disputesPerIdMap); throw new DisputeReplayException(disputeToTest, e + " at dispute " + disputeToTest); } } + private enum DisputeIdField implements Function { + TRADE_ID(Dispute::getTradeId), + DELAYED_PAYOUT_TX_ID(Dispute::getDelayedPayoutTxId), + WARNING_TX_ID(Dispute::getWarningTxId), + REDIRECT_TX_ID(Dispute::getRedirectTxId), + DEPOSIT_TX_ID(Dispute::getDepositTxId); + + private final Function getter; + + DisputeIdField(Function getter) { + this.getter = getter; + } + + @Override + public String apply(Dispute dispute) { + return getter.apply(dispute); + } + + @Override + public String toString() { + return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, name()); + } + } + /////////////////////////////////////////////////////////////////////////////////////////// // Exceptions