diff --git a/core/src/main/java/bisq/core/offer/OpenOfferManager.java b/core/src/main/java/bisq/core/offer/OpenOfferManager.java index 9dc131ba0b3..7ea84ff0b3b 100644 --- a/core/src/main/java/bisq/core/offer/OpenOfferManager.java +++ b/core/src/main/java/bisq/core/offer/OpenOfferManager.java @@ -777,10 +777,10 @@ private void handleOfferAvailabilityRequest(OfferAvailabilityRequest request, No if (openOffer.getState() == OpenOffer.State.AVAILABLE) { Offer offer = openOffer.getOffer(); if (preferences.getIgnoreTradersList().stream().noneMatch(fullAddress -> fullAddress.equals(peer.getFullAddress()))) { - mediatorNodeAddress = DisputeAgentSelection.getLeastUsedMediator(tradeStatisticsManager, mediatorManager).getNodeAddress(); + mediatorNodeAddress = DisputeAgentSelection.getRandomMediator(mediatorManager).getNodeAddress(); openOffer.setMediatorNodeAddress(mediatorNodeAddress); - refundAgentNodeAddress = DisputeAgentSelection.getLeastUsedRefundAgent(tradeStatisticsManager, refundAgentManager).getNodeAddress(); + refundAgentNodeAddress = DisputeAgentSelection.getRandomRefundAgent(refundAgentManager).getNodeAddress(); openOffer.setRefundAgentNodeAddress(refundAgentNodeAddress); try { diff --git a/core/src/main/java/bisq/core/offer/availability/DisputeAgentSelection.java b/core/src/main/java/bisq/core/offer/availability/DisputeAgentSelection.java index fe155858584..2e1d0c2963a 100644 --- a/core/src/main/java/bisq/core/offer/availability/DisputeAgentSelection.java +++ b/core/src/main/java/bisq/core/offer/availability/DisputeAgentSelection.java @@ -19,21 +19,11 @@ import bisq.core.support.dispute.agent.DisputeAgent; import bisq.core.support.dispute.agent.DisputeAgentManager; -import bisq.core.trade.statistics.TradeStatistics3; -import bisq.core.trade.statistics.TradeStatisticsManager; -import bisq.common.util.Tuple2; - -import com.google.common.annotations.VisibleForTesting; - -import java.util.Comparator; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; -import java.util.Objects; import java.util.Optional; -import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; -import java.util.stream.Stream; import lombok.extern.slf4j.Slf4j; @@ -43,62 +33,20 @@ public class DisputeAgentSelection { public static final int LOOK_BACK_RANGE = 100; - public static T getLeastUsedMediator(TradeStatisticsManager tradeStatisticsManager, - DisputeAgentManager disputeAgentManager) { - return getLeastUsedDisputeAgent(tradeStatisticsManager, - disputeAgentManager, - true); + public static T getRandomMediator(DisputeAgentManager disputeAgentManager) { + return getRandomDisputeAgent(disputeAgentManager); } - public static T getLeastUsedRefundAgent(TradeStatisticsManager tradeStatisticsManager, - DisputeAgentManager disputeAgentManager) { - return getLeastUsedDisputeAgent(tradeStatisticsManager, - disputeAgentManager, - false); + public static T getRandomRefundAgent(DisputeAgentManager disputeAgentManager) { + return getRandomDisputeAgent(disputeAgentManager); } - private static T getLeastUsedDisputeAgent(TradeStatisticsManager tradeStatisticsManager, - DisputeAgentManager disputeAgentManager, - boolean isMediator) { - // We take last 100 entries from trade statistics - Stream stream = tradeStatisticsManager.getNavigableTradeStatisticsSet().descendingSet().stream() - .limit(LOOK_BACK_RANGE); - - // We stored only first 4 chars of disputeAgents onion address - List lastAddressesUsedInTrades = stream - .map(tradeStatistics3 -> isMediator ? tradeStatistics3.getMediator() : tradeStatistics3.getRefundAgent()) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - - Set disputeAgents = disputeAgentManager.getObservableMap().values().stream() - .map(disputeAgent -> disputeAgent.getNodeAddress().getFullAddress()) - .collect(Collectors.toSet()); + private static T getRandomDisputeAgent(DisputeAgentManager disputeAgentManager) { + List disputeAgents = new ArrayList<>(disputeAgentManager.getObservableMap().values()); + Collections.shuffle(disputeAgents); - String result = getLeastUsedDisputeAgent(lastAddressesUsedInTrades, disputeAgents); - - Optional optionalDisputeAgent = disputeAgentManager.getObservableMap().values().stream() - .filter(e -> e.getNodeAddress().getFullAddress().equals(result)) - .findAny(); + Optional optionalDisputeAgent = disputeAgents.stream().findFirst(); checkArgument(optionalDisputeAgent.isPresent(), "optionalDisputeAgent has to be present"); return optionalDisputeAgent.get(); } - - @VisibleForTesting - static String getLeastUsedDisputeAgent(List lastAddressesUsedInTrades, Set disputeAgents) { - checkArgument(!disputeAgents.isEmpty(), "disputeAgents must not be empty"); - List> disputeAgentTuples = disputeAgents.stream() - .map(e -> new Tuple2<>(e, new AtomicInteger(0))) - .collect(Collectors.toList()); - disputeAgentTuples.forEach(tuple -> { - int count = (int) lastAddressesUsedInTrades.stream() - .filter(tuple.first::startsWith) // we use only first 4 chars for comparing - .mapToInt(e -> 1) - .count(); - tuple.second.set(count); - }); - - disputeAgentTuples.sort(Comparator.comparing(e -> e.first)); - disputeAgentTuples.sort(Comparator.comparingInt(e -> e.second.get())); - return disputeAgentTuples.get(0).first; - } } diff --git a/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java b/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java index 403230806a9..536fa0f6686 100644 --- a/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java +++ b/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java @@ -62,7 +62,7 @@ protected void run() { NodeAddress mediator = offerAvailabilityResponse.getMediator(); if (mediator == null) { // We do not get a mediator from old clients so we need to handle the null case. - mediator = DisputeAgentSelection.getLeastUsedMediator(model.getTradeStatisticsManager(), model.getMediatorManager()).getNodeAddress(); + mediator = DisputeAgentSelection.getRandomMediator(model.getMediatorManager()).getNodeAddress(); } model.setSelectedMediator(mediator); diff --git a/core/src/test/java/bisq/core/offer/availability/ArbitratorSelectionTest.java b/core/src/test/java/bisq/core/offer/availability/ArbitratorSelectionTest.java index 1e4e96ec036..b526c51735d 100644 --- a/core/src/test/java/bisq/core/offer/availability/ArbitratorSelectionTest.java +++ b/core/src/test/java/bisq/core/offer/availability/ArbitratorSelectionTest.java @@ -17,69 +17,96 @@ package bisq.core.offer.availability; +import bisq.core.support.dispute.agent.DisputeAgent; +import bisq.core.support.dispute.agent.DisputeAgentManager; +import bisq.core.support.dispute.mediation.mediator.Mediator; +import bisq.core.support.dispute.mediation.mediator.MediatorManager; + +import bisq.network.p2p.NodeAddress; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableMap; + import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.HashMap; +import java.util.Map; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class ArbitratorSelectionTest { @Test - public void testGetLeastUsedArbitrator() { - // We get least used selected - List lastAddressesUsedInTrades; - Set arbitrators; - String result; - - lastAddressesUsedInTrades = Arrays.asList("arb1", "arb2", "arb1"); - arbitrators = new HashSet<>(Arrays.asList("arb1", "arb2")); - result = DisputeAgentSelection.getLeastUsedDisputeAgent(lastAddressesUsedInTrades, arbitrators); - assertEquals("arb2", result); - - // if all are same we use first according to alphanumeric sorting - lastAddressesUsedInTrades = Arrays.asList("arb1", "arb2", "arb3"); - arbitrators = new HashSet<>(Arrays.asList("arb1", "arb2", "arb3")); - result = DisputeAgentSelection.getLeastUsedDisputeAgent(lastAddressesUsedInTrades, arbitrators); - assertEquals("arb1", result); - - lastAddressesUsedInTrades = Arrays.asList("arb1", "arb2", "arb3", "arb1"); - arbitrators = new HashSet<>(Arrays.asList("arb1", "arb2", "arb3")); - result = DisputeAgentSelection.getLeastUsedDisputeAgent(lastAddressesUsedInTrades, arbitrators); - assertEquals("arb2", result); - - lastAddressesUsedInTrades = Arrays.asList("arb1", "arb2", "arb3", "arb1", "arb2"); - arbitrators = new HashSet<>(Arrays.asList("arb1", "arb2", "arb3")); - result = DisputeAgentSelection.getLeastUsedDisputeAgent(lastAddressesUsedInTrades, arbitrators); - assertEquals("arb3", result); - - lastAddressesUsedInTrades = Arrays.asList("xxx", "ccc", "aaa"); - arbitrators = new HashSet<>(Arrays.asList("aaa", "ccc", "xxx")); - result = DisputeAgentSelection.getLeastUsedDisputeAgent(lastAddressesUsedInTrades, arbitrators); - assertEquals("aaa", result); - lastAddressesUsedInTrades = Arrays.asList("333", "000", "111"); - arbitrators = new HashSet<>(Arrays.asList("111", "333", "000")); - result = DisputeAgentSelection.getLeastUsedDisputeAgent(lastAddressesUsedInTrades, arbitrators); - assertEquals("000", result); - - // if winner is not in our arb list we use our arb from arbitrators even if never used in trades - lastAddressesUsedInTrades = Arrays.asList("arb1", "arb2", "arb3"); - arbitrators = new HashSet<>(Arrays.asList("arb4")); - result = DisputeAgentSelection.getLeastUsedDisputeAgent(lastAddressesUsedInTrades, arbitrators); - assertEquals("arb4", result); - - // if winner (arb2) is not in our arb list we use our arb from arbitrators - lastAddressesUsedInTrades = Arrays.asList("arb1", "arb1", "arb1", "arb2"); - arbitrators = new HashSet<>(Arrays.asList("arb1")); - result = DisputeAgentSelection.getLeastUsedDisputeAgent(lastAddressesUsedInTrades, arbitrators); - assertEquals("arb1", result); - - // arb1 is used least - lastAddressesUsedInTrades = Arrays.asList("arb1", "arb2", "arb2", "arb2", "arb1", "arb1", "arb2"); - arbitrators = new HashSet<>(Arrays.asList("arb1", "arb2")); - result = DisputeAgentSelection.getLeastUsedDisputeAgent(lastAddressesUsedInTrades, arbitrators); - assertEquals("arb1", result); + public void testGetRandomArbitratorFromOne() { + MediatorManager mediatorManager = createFakeMediatorManagerWith( + createFakeMediatorWithId(1) + ); + testArbitratorSelection(10, mediatorManager); + } + + @Test + public void testGetRandomArbitratorFromTwo() { + MediatorManager mediatorManager = createFakeMediatorManagerWith( + createFakeMediatorWithId(1), + createFakeMediatorWithId(2) + ); + testArbitratorSelection(10000, mediatorManager); + } + + @Test + public void testGetRandomArbitratorFromThree() { + MediatorManager mediatorManager = createFakeMediatorManagerWith( + createFakeMediatorWithId(1), + createFakeMediatorWithId(2), + createFakeMediatorWithId(3) + ); + testArbitratorSelection(10000, mediatorManager); + } + @Test + public void testGetRandomArbitratorFromFour() { + MediatorManager mediatorManager = createFakeMediatorManagerWith( + createFakeMediatorWithId(1), + createFakeMediatorWithId(2), + createFakeMediatorWithId(3), + createFakeMediatorWithId(4) + ); + testArbitratorSelection(1000, mediatorManager); + } + + private MediatorManager createFakeMediatorManagerWith(Mediator... mediators) { + ObservableMap observableMap = FXCollections.observableHashMap(); + Arrays.stream(mediators) + .forEach(mediator -> observableMap.put(mediator.getNodeAddress(), mediator)); + + MediatorManager mediatorManager = mock(MediatorManager.class); + when(mediatorManager.getObservableMap()).thenReturn(observableMap); + return mediatorManager; + } + + private Mediator createFakeMediatorWithId(int id) { + Mediator mediator = mock(Mediator.class); + when(mediator.getNodeAddress()).thenReturn(new NodeAddress("127.0.0.1", id)); + return mediator; + } + + private void testArbitratorSelection(int iterations, + DisputeAgentManager disputeAgentManager) { + int numberOfDisputeAgents = disputeAgentManager.getObservableMap().size(); + double expectedPercentage = 1.00 / numberOfDisputeAgents; + System.out.printf("%ntestArbitratorSelection with %d arbitrators %d iterations, expected percentage=%f%n", + numberOfDisputeAgents, iterations, expectedPercentage); + + Map results = new HashMap<>(); + for (int i = 0; i < iterations; i++) { + T selectedArb = DisputeAgentSelection.getRandomMediator(disputeAgentManager); + NodeAddress selectedArbNodeAddress = selectedArb.getNodeAddress(); + results.put(selectedArbNodeAddress, 1 + results.getOrDefault(selectedArbNodeAddress, 0)); + } + + assertEquals(results.size(), numberOfDisputeAgents); + results.forEach((k, v) -> System.out.printf("arb=%s result=%d percentage=%f%n", k, v, (double) v / iterations)); + results.forEach((k, v) -> assertEquals(expectedPercentage, (double) v / iterations, 0.1)); } }