Refactor dispute agent selection.

This commit is contained in:
jmacxx 2023-07-19 08:53:10 -05:00
parent 638848de24
commit 2d340432eb
No known key found for this signature in database
GPG Key ID: 155297BABFE94A1B
4 changed files with 50 additions and 105 deletions

View File

@ -777,10 +777,10 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
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 {

View File

@ -19,21 +19,13 @@ package bisq.core.offer.availability;
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.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Random;
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,38 +35,20 @@ import static com.google.common.base.Preconditions.checkArgument;
public class DisputeAgentSelection {
public static final int LOOK_BACK_RANGE = 100;
public static <T extends DisputeAgent> T getLeastUsedMediator(TradeStatisticsManager tradeStatisticsManager,
DisputeAgentManager<T> disputeAgentManager) {
return getLeastUsedDisputeAgent(tradeStatisticsManager,
disputeAgentManager,
true);
public static <T extends DisputeAgent> T getRandomMediator(DisputeAgentManager<T> disputeAgentManager) {
return getRandomDisputeAgent(disputeAgentManager);
}
public static <T extends DisputeAgent> T getLeastUsedRefundAgent(TradeStatisticsManager tradeStatisticsManager,
DisputeAgentManager<T> disputeAgentManager) {
return getLeastUsedDisputeAgent(tradeStatisticsManager,
disputeAgentManager,
false);
public static <T extends DisputeAgent> T getRandomRefundAgent(DisputeAgentManager<T> disputeAgentManager) {
return getRandomDisputeAgent(disputeAgentManager);
}
private static <T extends DisputeAgent> T getLeastUsedDisputeAgent(TradeStatisticsManager tradeStatisticsManager,
DisputeAgentManager<T> disputeAgentManager,
boolean isMediator) {
// We take last 100 entries from trade statistics
Stream<TradeStatistics3> stream = tradeStatisticsManager.getNavigableTradeStatisticsSet().descendingSet().stream()
.limit(LOOK_BACK_RANGE);
// We stored only first 4 chars of disputeAgents onion address
List<String> lastAddressesUsedInTrades = stream
.map(tradeStatistics3 -> isMediator ? tradeStatistics3.getMediator() : tradeStatistics3.getRefundAgent())
.filter(Objects::nonNull)
.collect(Collectors.toList());
private static <T extends DisputeAgent> T getRandomDisputeAgent(DisputeAgentManager<T> disputeAgentManager) {
Set<String> disputeAgents = disputeAgentManager.getObservableMap().values().stream()
.map(disputeAgent -> disputeAgent.getNodeAddress().getFullAddress())
.collect(Collectors.toSet());
String result = getLeastUsedDisputeAgent(lastAddressesUsedInTrades, disputeAgents);
String result = getRandomDisputeAgent(disputeAgents);
Optional<T> optionalDisputeAgent = disputeAgentManager.getObservableMap().values().stream()
.filter(e -> e.getNodeAddress().getFullAddress().equals(result))
@ -84,21 +58,10 @@ public class DisputeAgentSelection {
}
@VisibleForTesting
static String getLeastUsedDisputeAgent(List<String> lastAddressesUsedInTrades, Set<String> disputeAgents) {
checkArgument(!disputeAgents.isEmpty(), "disputeAgents must not be empty");
List<Tuple2<String, AtomicInteger>> 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;
static String getRandomDisputeAgent(Set<String> disputeAgents) {
if (disputeAgents.isEmpty()) {
return null;
}
return (String) disputeAgents.toArray()[new Random().nextInt(disputeAgents.size())];
}
}

View File

@ -62,7 +62,7 @@ public class ProcessOfferAvailabilityResponse extends Task<OfferAvailabilityMode
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);

View File

@ -18,8 +18,9 @@
package bisq.core.offer.availability;
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 org.junit.jupiter.api.Test;
@ -28,58 +29,39 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
public class ArbitratorSelectionTest {
@Test
public void testGetLeastUsedArbitrator() {
// We get least used selected
List<String> lastAddressesUsedInTrades;
Set<String> arbitrators;
String result;
public void testGetRandomArbitratorFromZero() {
testArbitratorSelection(10, new HashSet<>());
}
@Test
public void testGetRandomArbitratorFromOne() {
testArbitratorSelection(10, new HashSet<>(Arrays.asList("arb1")));
}
@Test
public void testGetRandomArbitratorFromTwo() {
testArbitratorSelection(10000, new HashSet<>(Arrays.asList("arb1", "arb2")));
}
@Test
public void testGetRandomArbitratorFromThree() {
testArbitratorSelection(10000, new HashSet<>(Arrays.asList("arb1", "arb2", "arb3")));
}
@Test
public void testGetRandomArbitratorFromFour() {
testArbitratorSelection(1000, new HashSet<>(Arrays.asList("arb1", "arb2", "arb3", "arb4")));
}
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);
private void testArbitratorSelection(int iterations, Set<String> arbitrators) {
double expectedPercentage = 1.00 / arbitrators.size();
System.out.printf("%ntestArbitratorSelection with %d arbitrators %d iterations, expected percentage=%f%n",
arbitrators.size(), iterations, expectedPercentage);
Map<String, Integer> results = new HashMap<>();
for (int i=0; i < iterations; i++) {
String selectedArb = DisputeAgentSelection.getRandomDisputeAgent(arbitrators);
if (selectedArb != null) {
results.put(selectedArb, 1 + results.getOrDefault(selectedArb, 0));
}
}
assertEquals(results.size(), arbitrators.size());
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));
}
}