From 77362ff64232333f11d41c8ed6be78e70aa72fdc Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Wed, 8 Apr 2020 18:00:35 -0500 Subject: [PATCH 1/3] Add list to filter to set btc fee receiver addresses --- .../main/java/bisq/core/filter/Filter.java | 17 +++++-- .../bisq/core/offer/OpenOfferManager.java | 7 ++- .../offer/placeoffer/PlaceOfferModel.java | 7 ++- .../placeoffer/tasks/CreateMakerFeeTx.java | 5 +- .../tasks/taker/CreateTakerFeeTx.java | 6 ++- .../bisq/core/util/FeeReceiverSelector.java | 47 +++++++++++++++++++ .../resources/i18n/displayStrings.properties | 1 + .../main/overlays/windows/FilterWindow.java | 5 +- proto/src/main/proto/pb.proto | 1 + 9 files changed, 86 insertions(+), 10 deletions(-) create mode 100644 core/src/main/java/bisq/core/util/FeeReceiverSelector.java diff --git a/core/src/main/java/bisq/core/filter/Filter.java b/core/src/main/java/bisq/core/filter/Filter.java index cfb59f9cd72..25a77454fc6 100644 --- a/core/src/main/java/bisq/core/filter/Filter.java +++ b/core/src/main/java/bisq/core/filter/Filter.java @@ -105,6 +105,10 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload { @Nullable private final List bannedSignerPubKeys; + // added in v1.3.2 + @Nullable + private final List btcFeeReceiverAddresses; + public Filter(List bannedOfferIds, List bannedNodeAddress, List bannedPaymentAccounts, @@ -120,7 +124,8 @@ public Filter(List bannedOfferIds, @Nullable String disableTradeBelowVersion, @Nullable List mediators, @Nullable List refundAgents, - @Nullable List bannedSignerPubKeys) { + @Nullable List bannedSignerPubKeys, + @Nullable List btcFeeReceiverAddresses) { this.bannedOfferIds = bannedOfferIds; this.bannedNodeAddress = bannedNodeAddress; this.bannedPaymentAccounts = bannedPaymentAccounts; @@ -137,6 +142,7 @@ public Filter(List bannedOfferIds, this.mediators = mediators; this.refundAgents = refundAgents; this.bannedSignerPubKeys = bannedSignerPubKeys; + this.btcFeeReceiverAddresses = btcFeeReceiverAddresses; } @@ -163,7 +169,8 @@ public Filter(List bannedOfferIds, @Nullable Map extraDataMap, @Nullable List mediators, @Nullable List refundAgents, - @Nullable List bannedSignerPubKeys) { + @Nullable List bannedSignerPubKeys, + @Nullable List btcFeeReceiverAddresses) { this(bannedOfferIds, bannedNodeAddress, bannedPaymentAccounts, @@ -179,7 +186,8 @@ public Filter(List bannedOfferIds, disableTradeBelowVersion, mediators, refundAgents, - bannedSignerPubKeys); + bannedSignerPubKeys, + btcFeeReceiverAddresses); this.signatureAsBase64 = signatureAsBase64; this.ownerPubKeyBytes = ownerPubKeyBytes; this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap); @@ -215,6 +223,7 @@ public protobuf.StoragePayload toProtoMessage() { Optional.ofNullable(mediators).ifPresent(builder::addAllMediators); Optional.ofNullable(refundAgents).ifPresent(builder::addAllRefundAgents); Optional.ofNullable(bannedSignerPubKeys).ifPresent(builder::addAllBannedSignerPubKeys); + Optional.ofNullable(btcFeeReceiverAddresses).ifPresent(builder::addAllBtcFeeReceiverAddresses); return protobuf.StoragePayload.newBuilder().setFilter(builder).build(); } @@ -242,6 +251,7 @@ public static Filter fromProto(protobuf.Filter proto) { CollectionUtils.isEmpty(proto.getRefundAgentsList()) ? null : new ArrayList<>(proto.getRefundAgentsList()), CollectionUtils.isEmpty(proto.getBannedSignerPubKeysList()) ? null : new ArrayList<>(proto.getBannedSignerPubKeysList())); + CollectionUtils.isEmpty(proto.getBtcFeeReceiverAddressesList()) ? null : new ArrayList<>(proto.getBtcFeeReceiverAddressesList())); } @@ -281,6 +291,7 @@ public String toString() { ",\n mediators=" + mediators + ",\n refundAgents=" + refundAgents + ",\n bannedSignerPubKeys=" + bannedSignerPubKeys + + ",\n btcFeeReceiverAddresses=" + btcFeeReceiverAddresses + "\n}"; } } diff --git a/core/src/main/java/bisq/core/offer/OpenOfferManager.java b/core/src/main/java/bisq/core/offer/OpenOfferManager.java index 2d4c80147ac..cfc9bd1b3c7 100644 --- a/core/src/main/java/bisq/core/offer/OpenOfferManager.java +++ b/core/src/main/java/bisq/core/offer/OpenOfferManager.java @@ -23,6 +23,7 @@ import bisq.core.dao.DaoFacade; import bisq.core.exceptions.TradePriceOutOfToleranceException; import bisq.core.locale.Res; +import bisq.core.filter.FilterManager; import bisq.core.offer.availability.DisputeAgentSelection; import bisq.core.offer.messages.OfferAvailabilityRequest; import bisq.core.offer.messages.OfferAvailabilityResponse; @@ -111,6 +112,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe private final MediatorManager mediatorManager; private final RefundAgentManager refundAgentManager; private final DaoFacade daoFacade; + private final FilterManager filterManager; private final Storage> openOfferTradableListStorage; private final Map offersToBeEdited = new HashMap<>(); private boolean stopped; @@ -139,6 +141,7 @@ public OpenOfferManager(CreateOfferService createOfferService, MediatorManager mediatorManager, RefundAgentManager refundAgentManager, DaoFacade daoFacade, + FilterManager filterManager, Storage> storage) { this.createOfferService = createOfferService; this.keyRing = keyRing; @@ -156,6 +159,7 @@ public OpenOfferManager(CreateOfferService createOfferService, this.mediatorManager = mediatorManager; this.refundAgentManager = refundAgentManager; this.daoFacade = daoFacade; + this.filterManager = filterManager; openOfferTradableListStorage = storage; @@ -361,7 +365,8 @@ public void placeOffer(Offer offer, arbitratorManager, tradeStatisticsManager, daoFacade, - user); + user, + filterManager); PlaceOfferProtocol placeOfferProtocol = new PlaceOfferProtocol( model, transaction -> { diff --git a/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferModel.java b/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferModel.java index 294f7cd19ff..8a139de6eea 100644 --- a/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferModel.java +++ b/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferModel.java @@ -21,6 +21,7 @@ import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.TradeWalletService; import bisq.core.dao.DaoFacade; +import bisq.core.filter.FilterManager; import bisq.core.offer.Offer; import bisq.core.offer.OfferBookService; import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; @@ -51,6 +52,8 @@ public class PlaceOfferModel implements Model { private final TradeStatisticsManager tradeStatisticsManager; private final DaoFacade daoFacade; private final User user; + @Getter + private final FilterManager filterManager; // Mutable @Setter @@ -68,7 +71,8 @@ public PlaceOfferModel(Offer offer, ArbitratorManager arbitratorManager, TradeStatisticsManager tradeStatisticsManager, DaoFacade daoFacade, - User user) { + User user, + FilterManager filterManager) { this.offer = offer; this.reservedFundsForOffer = reservedFundsForOffer; this.useSavingsWallet = useSavingsWallet; @@ -80,6 +84,7 @@ public PlaceOfferModel(Offer offer, this.tradeStatisticsManager = tradeStatisticsManager; this.daoFacade = daoFacade; this.user = user; + this.filterManager = filterManager; } @Override diff --git a/core/src/main/java/bisq/core/offer/placeoffer/tasks/CreateMakerFeeTx.java b/core/src/main/java/bisq/core/offer/placeoffer/tasks/CreateMakerFeeTx.java index da2b60a0ace..b7619ace86b 100644 --- a/core/src/main/java/bisq/core/offer/placeoffer/tasks/CreateMakerFeeTx.java +++ b/core/src/main/java/bisq/core/offer/placeoffer/tasks/CreateMakerFeeTx.java @@ -25,10 +25,10 @@ import bisq.core.btc.wallet.TxBroadcaster; import bisq.core.btc.wallet.WalletService; import bisq.core.dao.exceptions.DaoDisabledException; -import bisq.core.dao.governance.param.Param; import bisq.core.dao.state.model.blockchain.TxType; import bisq.core.offer.Offer; import bisq.core.offer.placeoffer.PlaceOfferModel; +import bisq.core.util.FeeReceiverSelector; import bisq.common.UserThread; import bisq.common.taskrunner.Task; @@ -65,7 +65,8 @@ protected void run() { Address changeAddress = walletService.getFreshAddressEntry().getAddress(); TradeWalletService tradeWalletService = model.getTradeWalletService(); - String feeReceiver = model.getDaoFacade().getParamValue(Param.RECIPIENT_BTC_ADDRESS); + + String feeReceiver = FeeReceiverSelector.getAddress(model.getDaoFacade(), model.getFilterManager()); if (offer.isCurrencyForMakerFeeBtc()) { tradeWalletService.createBtcTradingFeeTx( diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/CreateTakerFeeTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/CreateTakerFeeTx.java index 1e9f1a561c8..cebbbc4bb87 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/CreateTakerFeeTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/CreateTakerFeeTx.java @@ -22,9 +22,9 @@ import bisq.core.btc.wallet.TradeWalletService; import bisq.core.btc.wallet.WalletService; import bisq.core.dao.exceptions.DaoDisabledException; -import bisq.core.dao.governance.param.Param; import bisq.core.trade.Trade; import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.util.FeeReceiverSelector; import bisq.common.taskrunner.TaskRunner; @@ -65,7 +65,9 @@ protected void run() { Address changeAddress = changeAddressEntry.getAddress(); TradeWalletService tradeWalletService = processModel.getTradeWalletService(); Transaction transaction; - String feeReceiver = processModel.getDaoFacade().getParamValue(Param.RECIPIENT_BTC_ADDRESS); + + String feeReceiver = FeeReceiverSelector.getAddress(processModel.getDaoFacade(), processModel.getFilterManager()); + if (trade.isCurrencyForTakerFeeBtc()) { transaction = tradeWalletService.createBtcTradingFeeTx( fundingAddress, diff --git a/core/src/main/java/bisq/core/util/FeeReceiverSelector.java b/core/src/main/java/bisq/core/util/FeeReceiverSelector.java new file mode 100644 index 00000000000..4df8dd3e045 --- /dev/null +++ b/core/src/main/java/bisq/core/util/FeeReceiverSelector.java @@ -0,0 +1,47 @@ +/* + * 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; + +import bisq.core.dao.DaoFacade; +import bisq.core.dao.governance.param.Param; +import bisq.core.filter.Filter; +import bisq.core.filter.FilterManager; + +import java.util.List; +import java.util.Random; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class FeeReceiverSelector { + public static String getAddress(DaoFacade daoFacade, FilterManager filterManager) { + // We keep default value as fallback in case no filter value is available or user has old version. + String feeReceiver = daoFacade.getParamValue(Param.RECIPIENT_BTC_ADDRESS); + + Filter filter = filterManager.getFilter(); + if (filter != null) { + List feeReceivers = filter.getBtcFeeReceiverAddresses(); + if (feeReceivers != null && !feeReceivers.isEmpty()) { + int index = new Random().nextInt(feeReceivers.size()); + feeReceiver = feeReceivers.get(index); + } + } + + return feeReceiver; + } +} diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index bd5fd605eed..77722829cc0 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -2429,6 +2429,7 @@ filterWindow.disableDaoBelowVersion=Min. version required for DAO filterWindow.disableTradeBelowVersion=Min. version required for trading filterWindow.add=Add filter filterWindow.remove=Remove filter +filterWindow.btcFeeReceiverAddresses=BTC fee receiver addresses offerDetailsWindow.minBtcAmount=Min. BTC amount offerDetailsWindow.min=(min. {0}) diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/FilterWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/FilterWindow.java index c3b86f21383..50dbf04f182 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/FilterWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/FilterWindow.java @@ -130,6 +130,7 @@ private void addContent() { InputTextField arbitratorsInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.arbitrators")); InputTextField mediatorsInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.mediators")); InputTextField refundAgentsInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.refundAgents")); + InputTextField btcFeeReceiverAddressesInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.btcFeeReceiverAddresses")); InputTextField seedNodesInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.seedNode")); InputTextField priceRelayNodesInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.priceRelayNode")); InputTextField btcNodesInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.btcNode")); @@ -149,6 +150,7 @@ private void addContent() { setupFieldFromList(arbitratorsInputTextField, filter.getArbitrators()); setupFieldFromList(mediatorsInputTextField, filter.getMediators()); setupFieldFromList(refundAgentsInputTextField, filter.getRefundAgents()); + setupFieldFromList(btcFeeReceiverAddressesInputTextField, filter.getBtcFeeReceiverAddresses()); setupFieldFromList(seedNodesInputTextField, filter.getSeedNodes()); setupFieldFromList(priceRelayNodesInputTextField, filter.getPriceRelayNodes()); setupFieldFromList(btcNodesInputTextField, filter.getBtcNodes()); @@ -177,7 +179,8 @@ private void addContent() { disableTradeBelowVersionInputTextField.getText(), readAsList(mediatorsInputTextField), readAsList(refundAgentsInputTextField), - readAsList(bannedSignerPubKeysInputTextField) + readAsList(bannedSignerPubKeysInputTextField), + readAsList(btcFeeReceiverAddressesInputTextField) ), keyInputTextField.getText()) ) diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 677cd9da685..89901abac4c 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -629,6 +629,7 @@ message Filter { repeated string mediators = 17; repeated string refundAgents = 18; repeated string bannedSignerPubKeys = 19; + repeated string btc_fee_receiver_addresses = 20; } // not used anymore from v0.6 on. But leave it for receiving TradeStatistics objects from older From 752abce61e8365105609c91d34241db6cf80b77b Mon Sep 17 00:00:00 2001 From: sqrrm Date: Thu, 11 Jun 2020 18:22:18 +0200 Subject: [PATCH 2/3] Weighted payout scheme Fix some conflicts from an earlier rebase --- .../main/java/bisq/core/filter/Filter.java | 5 +- .../bisq/core/util/FeeReceiverSelector.java | 53 +++++++++++++++---- .../bisq/core/offer/OpenOfferManagerTest.java | 8 ++- .../core/user/UserPayloadModelVOTest.java | 2 +- 4 files changed, 49 insertions(+), 19 deletions(-) diff --git a/core/src/main/java/bisq/core/filter/Filter.java b/core/src/main/java/bisq/core/filter/Filter.java index 25a77454fc6..0c2e39b0953 100644 --- a/core/src/main/java/bisq/core/filter/Filter.java +++ b/core/src/main/java/bisq/core/filter/Filter.java @@ -250,8 +250,9 @@ public static Filter fromProto(protobuf.Filter proto) { CollectionUtils.isEmpty(proto.getMediatorsList()) ? null : new ArrayList<>(proto.getMediatorsList()), CollectionUtils.isEmpty(proto.getRefundAgentsList()) ? null : new ArrayList<>(proto.getRefundAgentsList()), CollectionUtils.isEmpty(proto.getBannedSignerPubKeysList()) ? - null : new ArrayList<>(proto.getBannedSignerPubKeysList())); - CollectionUtils.isEmpty(proto.getBtcFeeReceiverAddressesList()) ? null : new ArrayList<>(proto.getBtcFeeReceiverAddressesList())); + null : new ArrayList<>(proto.getBannedSignerPubKeysList()), + CollectionUtils.isEmpty(proto.getBtcFeeReceiverAddressesList()) ? null : + new ArrayList<>(proto.getBtcFeeReceiverAddressesList())); } diff --git a/core/src/main/java/bisq/core/util/FeeReceiverSelector.java b/core/src/main/java/bisq/core/util/FeeReceiverSelector.java index 4df8dd3e045..2854a0e09f1 100644 --- a/core/src/main/java/bisq/core/util/FeeReceiverSelector.java +++ b/core/src/main/java/bisq/core/util/FeeReceiverSelector.java @@ -19,10 +19,15 @@ import bisq.core.dao.DaoFacade; import bisq.core.dao.governance.param.Param; -import bisq.core.filter.Filter; import bisq.core.filter.FilterManager; +import org.bitcoinj.core.Coin; + +import com.google.common.annotations.VisibleForTesting; + +import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.Random; import lombok.extern.slf4j.Slf4j; @@ -30,18 +35,44 @@ @Slf4j public class FeeReceiverSelector { public static String getAddress(DaoFacade daoFacade, FilterManager filterManager) { - // We keep default value as fallback in case no filter value is available or user has old version. - String feeReceiver = daoFacade.getParamValue(Param.RECIPIENT_BTC_ADDRESS); - - Filter filter = filterManager.getFilter(); - if (filter != null) { - List feeReceivers = filter.getBtcFeeReceiverAddresses(); - if (feeReceivers != null && !feeReceivers.isEmpty()) { - int index = new Random().nextInt(feeReceivers.size()); - feeReceiver = feeReceivers.get(index); + return getAddress(daoFacade, filterManager, new Random()); + } + + @VisibleForTesting + static String getAddress(DaoFacade daoFacade, FilterManager filterManager, Random rnd) { + List feeReceivers = Optional.ofNullable(filterManager.getFilter()) + .flatMap(f -> Optional.ofNullable(f.getBtcFeeReceiverAddresses())) + .orElse(List.of()); + + List amountList = new ArrayList<>(); + List receiverAddressList = new ArrayList<>(); + + feeReceivers.forEach(e -> { + try { + String[] tokens = e.split("#"); + amountList.add(Coin.parseCoin(tokens[1]).longValue()); // total amount the victim should receive + receiverAddressList.add(tokens[0]); // victim's receiver address + } catch (RuntimeException ignore) { + // If input format is not as expected we ignore entry } + }); + + if (!amountList.isEmpty()) { + return receiverAddressList.get(weightedSelection(amountList, rnd)); } - return feeReceiver; + // We keep default value as fallback in case no filter value is available or user has old version. + return daoFacade.getParamValue(Param.RECIPIENT_BTC_ADDRESS); + } + + @VisibleForTesting + static int weightedSelection(List weights, Random rnd) { + long sum = weights.stream().mapToLong(n -> n).sum(); + long target = rnd.longs(0, sum).findFirst().orElseThrow(); + int i; + for (i = 0; i < weights.size() && target >= 0; i++) { + target -= weights.get(i); + } + return i - 1; } } diff --git a/core/src/test/java/bisq/core/offer/OpenOfferManagerTest.java b/core/src/test/java/bisq/core/offer/OpenOfferManagerTest.java index 62ea46a5485..16de7b79254 100644 --- a/core/src/test/java/bisq/core/offer/OpenOfferManagerTest.java +++ b/core/src/test/java/bisq/core/offer/OpenOfferManagerTest.java @@ -1,7 +1,5 @@ package bisq.core.offer; -import bisq.core.trade.TradableList; - import bisq.network.p2p.P2PService; import bisq.network.p2p.peers.PeerManager; @@ -47,7 +45,7 @@ public void testStartEditOfferForActiveOffer() { final OpenOfferManager manager = new OpenOfferManager(null, null, null, p2PService, null, null, null, offerBookService, null, null, null, - null, null, null, null, null, + null, null, null, null, null, null, new Storage<>(storageDir, null, corruptedDatabaseFilesHandler)); AtomicBoolean startEditOfferSuccessful = new AtomicBoolean(false); @@ -83,7 +81,7 @@ public void testStartEditOfferForDeactivatedOffer() throws IOException { final OpenOfferManager manager = new OpenOfferManager(null, null, null, p2PService, null, null, null, offerBookService, null, null, null, - null, null, null, null, null, + null, null, null, null, null, null, new Storage<>(storageDir, null, corruptedDatabaseFilesHandler)); AtomicBoolean startEditOfferSuccessful = new AtomicBoolean(false); @@ -111,7 +109,7 @@ public void testStartEditOfferForOfferThatIsCurrentlyEdited() { final OpenOfferManager manager = new OpenOfferManager(null, null, null, p2PService, null, null, null, offerBookService, null, null, null, - null, null, null, null, null, + null, null, null, null, null, null, new Storage<>(storageDir, null, corruptedDatabaseFilesHandler)); AtomicBoolean startEditOfferSuccessful = new AtomicBoolean(false); diff --git a/core/src/test/java/bisq/core/user/UserPayloadModelVOTest.java b/core/src/test/java/bisq/core/user/UserPayloadModelVOTest.java index ba9f0242dd2..67d09bb6586 100644 --- a/core/src/test/java/bisq/core/user/UserPayloadModelVOTest.java +++ b/core/src/test/java/bisq/core/user/UserPayloadModelVOTest.java @@ -27,7 +27,6 @@ import org.junit.Ignore; -@SuppressWarnings("UnusedAssignment") public class UserPayloadModelVOTest { @Ignore("TODO InvalidKeySpecException at bisq.common.crypto.Sig.getPublicKeyFromBytes(Sig.java:135)") public void testRoundtrip() { @@ -59,6 +58,7 @@ public void testRoundtripFull() { null, Lists.newArrayList(), Lists.newArrayList(), + Lists.newArrayList(), Lists.newArrayList())); vo.setRegisteredArbitrator(ArbitratorTest.getArbitratorMock()); vo.setRegisteredMediator(MediatorTest.getMediatorMock()); From 9fcc4da73825bccf264862e46c100779f5751a32 Mon Sep 17 00:00:00 2001 From: sqrrm Date: Thu, 11 Jun 2020 20:09:27 +0200 Subject: [PATCH 3/3] Add ReceiverSelectorTest --- .../core/util/FeeReceiverSelectorTest.java | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 core/src/test/java/bisq/core/util/FeeReceiverSelectorTest.java diff --git a/core/src/test/java/bisq/core/util/FeeReceiverSelectorTest.java b/core/src/test/java/bisq/core/util/FeeReceiverSelectorTest.java new file mode 100644 index 00000000000..c340c36ecdd --- /dev/null +++ b/core/src/test/java/bisq/core/util/FeeReceiverSelectorTest.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.core.util; + +import bisq.core.dao.DaoFacade; +import bisq.core.dao.governance.param.Param; +import bisq.core.filter.Filter; +import bisq.core.filter.FilterManager; + +import com.google.common.primitives.Longs; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class FeeReceiverSelectorTest { + @Mock + private DaoFacade daoFacade; + @Mock + private FilterManager filterManager; + + @Test + public void testGetAddress() { + Random rnd = new Random(123); + when(filterManager.getFilter()).thenReturn(filterWithReceivers( + List.of("", "foo#0.001", "ill-formed", "bar#0.002", "baz#0.001", "partial#bad"))); + + Map selectionCounts = new HashMap<>(); + for (int i = 0; i < 400; i++) { + String address = FeeReceiverSelector.getAddress(daoFacade, filterManager, rnd); + selectionCounts.compute(address, (k, n) -> n != null ? n + 1 : 1); + } + + assertEquals(3, selectionCounts.size()); + + // Check within 2 std. of the expected values (95% confidence each): + assertEquals(100.0, selectionCounts.get("foo"), 18); + assertEquals(200.0, selectionCounts.get("bar"), 20); + assertEquals(100.0, selectionCounts.get("baz"), 18); + } + + @Test + public void testGetAddress_noValidReceivers() { + when(daoFacade.getParamValue(Param.RECIPIENT_BTC_ADDRESS)).thenReturn("default"); + + when(filterManager.getFilter()).thenReturn(null); + assertEquals("default", FeeReceiverSelector.getAddress(daoFacade, filterManager)); + + when(filterManager.getFilter()).thenReturn(filterWithReceivers(null)); + assertEquals("default", FeeReceiverSelector.getAddress(daoFacade, filterManager)); + + when(filterManager.getFilter()).thenReturn(filterWithReceivers(List.of())); + assertEquals("default", FeeReceiverSelector.getAddress(daoFacade, filterManager)); + + when(filterManager.getFilter()).thenReturn(filterWithReceivers(List.of("ill-formed"))); + assertEquals("default", FeeReceiverSelector.getAddress(daoFacade, filterManager)); + } + + @Test + public void testWeightedSelection() { + Random rnd = new Random(456); + + int[] selections = new int[3]; + for (int i = 0; i < 6000; i++) { + selections[FeeReceiverSelector.weightedSelection(Longs.asList(1, 2, 3), rnd)]++; + } + + // Check within 2 std. of the expected values (95% confidence each): + assertEquals(1000.0, selections[0], 58); + assertEquals(2000.0, selections[1], 74); + assertEquals(3000.0, selections[2], 78); + } + + private static Filter filterWithReceivers(List btcFeeReceiverAddresses) { + return new Filter(null, null, null, null, + null, null, null, null, + false, null, false, null, + null, null, null, null, + btcFeeReceiverAddresses); + } +}