From b013503c9be712d10f6ca5a21e5730e7151abdf7 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Fri, 1 Jan 2021 14:05:21 -0500 Subject: [PATCH 01/31] Do not shutDown with CloseConnectionReason.PEER_BANNED if we received a CloseConnectionMessage as it would trigger a wrong log on the peers side, that he got banned. --- p2p/src/main/java/bisq/network/p2p/network/Connection.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/p2p/src/main/java/bisq/network/p2p/network/Connection.java b/p2p/src/main/java/bisq/network/p2p/network/Connection.java index 0bec36f8ac5..80cf65f32cd 100644 --- a/p2p/src/main/java/bisq/network/p2p/network/Connection.java +++ b/p2p/src/main/java/bisq/network/p2p/network/Connection.java @@ -842,10 +842,8 @@ && reportInvalidRequest(RuleViolation.WRONG_NETWORK_ID)) { if (CloseConnectionReason.PEER_BANNED.name().equals(proto.getCloseConnectionMessage().getReason())) { log.warn("We got shut down because we are banned by the other peer. (InputHandler.run CloseConnectionMessage)"); - shutDown(CloseConnectionReason.PEER_BANNED); - } else { - shutDown(CloseConnectionReason.CLOSE_REQUESTED_BY_PEER); } + shutDown(CloseConnectionReason.CLOSE_REQUESTED_BY_PEER); return; } else if (!stopped) { // We don't want to get the activity ts updated by ping/pong msg From 456d1f891cc9a263506215d1b3a5842f002b32fe Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Fri, 1 Jan 2021 14:06:16 -0500 Subject: [PATCH 02/31] Improve logs, optimize stream --- .../getdata/messages/GetDataResponse.java | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/p2p/src/main/java/bisq/network/p2p/peers/getdata/messages/GetDataResponse.java b/p2p/src/main/java/bisq/network/p2p/peers/getdata/messages/GetDataResponse.java index a86d6f27834..255c8c13474 100644 --- a/p2p/src/main/java/bisq/network/p2p/peers/getdata/messages/GetDataResponse.java +++ b/p2p/src/main/java/bisq/network/p2p/peers/getdata/messages/GetDataResponse.java @@ -27,8 +27,8 @@ import bisq.common.app.Version; import bisq.common.proto.network.NetworkEnvelope; import bisq.common.proto.network.NetworkProtoResolver; +import bisq.common.util.Utilities; -import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; @@ -107,24 +107,18 @@ public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { protobuf.NetworkEnvelope proto = getNetworkEnvelopeBuilder() .setGetDataResponse(builder) .build(); - log.info("Sending a GetDataResponse with {} kB", proto.getSerializedSize() / 1000d); + log.info("Sending a GetDataResponse with {}", Utilities.readableFileSize(proto.getSerializedSize())); return proto; } public static GetDataResponse fromProto(protobuf.GetDataResponse proto, NetworkProtoResolver resolver, int messageVersion) { - log.info("Received a GetDataResponse with {} kB", proto.getSerializedSize() / 1000d); - Set dataSet = new HashSet<>( - proto.getDataSetList().stream() - .map(entry -> (ProtectedStorageEntry) resolver.fromProto(entry)) - .collect(Collectors.toSet())); - - Set persistableNetworkPayloadSet = new HashSet<>( - proto.getPersistableNetworkPayloadItemsList().stream() - .map(e -> (PersistableNetworkPayload) resolver.fromProto(e)) - .collect(Collectors.toSet())); - + log.info("Received a GetDataResponse with {}", Utilities.readableFileSize(proto.getSerializedSize())); + Set dataSet = proto.getDataSetList().stream() + .map(entry -> (ProtectedStorageEntry) resolver.fromProto(entry)).collect(Collectors.toSet()); + Set persistableNetworkPayloadSet = proto.getPersistableNetworkPayloadItemsList().stream() + .map(e -> (PersistableNetworkPayload) resolver.fromProto(e)).collect(Collectors.toSet()); return new GetDataResponse(dataSet, persistableNetworkPayloadSet, proto.getRequestNonce(), From a7127d4b317a5bad1faf7d2ebecb3b6107a4a449 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Fri, 1 Jan 2021 14:07:17 -0500 Subject: [PATCH 03/31] Improve logging of received data The numbers did not match up from delivered response size and items as we did not count in the overhead of the ProtectedStorageEntry (pub key+sig) and did estimate the size with taking only first item and multiplying it. A measurement resulted in 20 ms costs for the exact calculation (toProtoMessage().getSerializedSize() has some costs). I guess that is acceptable to get correct metrics. --- .../p2p/peers/getdata/RequestDataHandler.java | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/p2p/src/main/java/bisq/network/p2p/peers/getdata/RequestDataHandler.java b/p2p/src/main/java/bisq/network/p2p/peers/getdata/RequestDataHandler.java index 34620b53fd2..37cd40ce90a 100644 --- a/p2p/src/main/java/bisq/network/p2p/peers/getdata/RequestDataHandler.java +++ b/p2p/src/main/java/bisq/network/p2p/peers/getdata/RequestDataHandler.java @@ -32,10 +32,10 @@ import bisq.common.Timer; import bisq.common.UserThread; import bisq.common.proto.network.NetworkEnvelope; +import bisq.common.proto.network.NetworkPayload; import bisq.common.util.Tuple2; import bisq.common.util.Utilities; -import com.google.common.collect.Streams; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.MoreExecutors; @@ -43,7 +43,6 @@ import java.util.HashMap; import java.util.Map; -import java.util.Objects; import java.util.Random; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; @@ -232,20 +231,15 @@ public void stop() { private void logContents(GetDataResponse getDataResponse) { Set dataSet = getDataResponse.getDataSet(); Set persistableNetworkPayloadSet = getDataResponse.getPersistableNetworkPayloadSet(); - Map> numPayloadsByClassName = new HashMap<>(); - Streams.concat(dataSet.stream().map(ProtectedStorageEntry::getProtectedStoragePayload).filter(Objects::nonNull), - persistableNetworkPayloadSet.stream()) - .forEach(data -> { - String className = data.getClass().getSimpleName(); - // The data.toProtoMessage().getSerializedSize() call is not cheap, so want to avoid to call it on - // each object. As most objects of the same data type are expected to have a similar size, - // we only take the first and multiply later to get the total size. - // This is sufficient for the informational purpose of that log. - numPayloadsByClassName.putIfAbsent(className, new Tuple2<>(new AtomicInteger(0), - data.toProtoMessage().getSerializedSize())); - numPayloadsByClassName.get(className).first.getAndIncrement(); - - }); + Map> numPayloadsByClassName = new HashMap<>(); + dataSet.forEach(protectedStorageEntry -> { + String className = protectedStorageEntry.getProtectedStoragePayload().getClass().getSimpleName(); + addDetails(numPayloadsByClassName, protectedStorageEntry, className); + }); + persistableNetworkPayloadSet.forEach(persistableNetworkPayload -> { + String className = persistableNetworkPayload.getClass().getSimpleName(); + addDetails(numPayloadsByClassName, persistableNetworkPayload, className); + }); StringBuilder sb = new StringBuilder(); String sep = System.lineSeparator(); sb.append(sep).append("#################################################################").append(sep); @@ -256,13 +250,21 @@ private void logContents(GetDataResponse getDataResponse) { numPayloadsByClassName.forEach((key, value) -> sb.append(key) .append(": ") .append(value.first.get()) - .append(" / ≈") - .append(Utilities.readableFileSize(value.second * value.first.get())) + .append(" / ") + .append(Utilities.readableFileSize(value.second.get())) .append(sep)); sb.append("#################################################################"); log.info(sb.toString()); } + private void addDetails(Map> numPayloadsByClassName, + NetworkPayload networkPayload, String className) { + numPayloadsByClassName.putIfAbsent(className, new Tuple2<>(new AtomicInteger(0), + new AtomicInteger(0))); + numPayloadsByClassName.get(className).first.getAndIncrement(); + numPayloadsByClassName.get(className).second.getAndAdd(networkPayload.toProtoMessage().getSerializedSize()); + } + @SuppressWarnings("UnusedParameters") private void handleFault(String errorMessage, NodeAddress nodeAddress, From ac8711cbed4de9f11a919736cfa80be65521ff85 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Fri, 1 Jan 2021 14:11:41 -0500 Subject: [PATCH 04/31] Add NetworkFilter, remove BanList --- .../main/java/bisq/core/app/CoreModule.java | 5 +++ .../core/app/misc/ModuleForAppWithP2p.java | 3 ++ .../java/bisq/core/filter/FilterManager.java | 4 +++ .../bisq/core/network/CoreNetworkFilter.java | 36 +++++++++---------- .../core/user/UserPayloadModelVOTest.java | 5 ++- .../core/util/FeeReceiverSelectorTest.java | 4 ++- .../java/bisq/inventory/InventoryMonitor.java | 1 + .../bisq/monitor/metric/P2PNetworkLoad.java | 2 +- .../metric/P2PSeedNodeSnapshotBase.java | 2 +- .../bisq/network/p2p/NetworkNodeProvider.java | 14 ++++---- .../main/java/bisq/network/p2p/P2PModule.java | 2 -- .../bisq/network/p2p/network/Connection.java | 24 ++++++++----- .../p2p/network/InboundConnection.java | 7 ++-- .../p2p/network/LocalhostNetworkNode.java | 6 ++-- .../network/p2p/network/NetworkFilter.java | 24 +++++++++++++ .../bisq/network/p2p/network/NetworkNode.java | 13 +++++-- .../p2p/network/OutboundConnection.java | 7 ++-- .../java/bisq/network/p2p/network/Server.java | 11 ++++-- .../network/p2p/network/TorNetworkNode.java | 9 +++-- .../java/bisq/network/p2p/DummySeedNode.java | 5 ++- .../p2p/network/LocalhostNetworkNodeTest.java | 7 ++-- .../p2p/network/TorNetworkNodeTest.java | 10 +++--- 22 files changed, 134 insertions(+), 67 deletions(-) rename p2p/src/main/java/bisq/network/p2p/peers/BanList.java => core/src/main/java/bisq/core/network/CoreNetworkFilter.java (56%) create mode 100644 p2p/src/main/java/bisq/network/p2p/network/NetworkFilter.java diff --git a/core/src/main/java/bisq/core/app/CoreModule.java b/core/src/main/java/bisq/core/app/CoreModule.java index 8f300705323..4984a84aaee 100644 --- a/core/src/main/java/bisq/core/app/CoreModule.java +++ b/core/src/main/java/bisq/core/app/CoreModule.java @@ -21,6 +21,7 @@ import bisq.core.btc.BitcoinModule; import bisq.core.dao.DaoModule; import bisq.core.filter.FilterModule; +import bisq.core.network.CoreNetworkFilter; import bisq.core.network.p2p.seed.DefaultSeedNodeRepository; import bisq.core.offer.OfferModule; import bisq.core.presentation.CorePresentationModule; @@ -35,6 +36,7 @@ import bisq.network.crypto.EncryptionServiceModule; import bisq.network.p2p.P2PModule; import bisq.network.p2p.network.BridgeAddressProvider; +import bisq.network.p2p.network.NetworkFilter; import bisq.network.p2p.seed.SeedNodeRepository; import bisq.common.app.AppModule; @@ -44,6 +46,8 @@ import bisq.common.proto.network.NetworkProtoResolver; import bisq.common.proto.persistable.PersistenceProtoResolver; +import com.google.inject.Singleton; + import java.io.File; import static bisq.common.config.Config.*; @@ -62,6 +66,7 @@ protected void configure() { bind(BridgeAddressProvider.class).to(Preferences.class); bind(SeedNodeRepository.class).to(DefaultSeedNodeRepository.class); + bind(NetworkFilter.class).to(CoreNetworkFilter.class).in(Singleton.class); bind(File.class).annotatedWith(named(STORAGE_DIR)).toInstance(config.storageDir); diff --git a/core/src/main/java/bisq/core/app/misc/ModuleForAppWithP2p.java b/core/src/main/java/bisq/core/app/misc/ModuleForAppWithP2p.java index 4ffdb7f79cb..d4402b765dc 100644 --- a/core/src/main/java/bisq/core/app/misc/ModuleForAppWithP2p.java +++ b/core/src/main/java/bisq/core/app/misc/ModuleForAppWithP2p.java @@ -22,6 +22,7 @@ import bisq.core.btc.BitcoinModule; import bisq.core.dao.DaoModule; import bisq.core.filter.FilterModule; +import bisq.core.network.CoreNetworkFilter; import bisq.core.network.p2p.seed.DefaultSeedNodeRepository; import bisq.core.offer.OfferModule; import bisq.core.proto.network.CoreNetworkProtoResolver; @@ -33,6 +34,7 @@ import bisq.network.crypto.EncryptionServiceModule; import bisq.network.p2p.P2PModule; import bisq.network.p2p.network.BridgeAddressProvider; +import bisq.network.p2p.network.NetworkFilter; import bisq.network.p2p.seed.SeedNodeRepository; import bisq.common.ClockWatcher; @@ -73,6 +75,7 @@ protected void configure() { bind(TorSetup.class).in(Singleton.class); bind(SeedNodeRepository.class).to(DefaultSeedNodeRepository.class).in(Singleton.class); + bind(NetworkFilter.class).to(CoreNetworkFilter.class).in(Singleton.class); bind(File.class).annotatedWith(named(STORAGE_DIR)).toInstance(config.storageDir); bind(File.class).annotatedWith(named(KEY_STORAGE_DIR)).toInstance(config.keyStorageDir); diff --git a/core/src/main/java/bisq/core/filter/FilterManager.java b/core/src/main/java/bisq/core/filter/FilterManager.java index 67f06220dd2..6656d4974c3 100644 --- a/core/src/main/java/bisq/core/filter/FilterManager.java +++ b/core/src/main/java/bisq/core/filter/FilterManager.java @@ -19,6 +19,7 @@ import bisq.core.btc.nodes.BtcNodes; import bisq.core.locale.Res; +import bisq.core.network.CoreNetworkFilter; import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.payment.payload.PaymentMethod; import bisq.core.provider.ProvidersRepository; @@ -96,6 +97,7 @@ public interface Listener { private final Preferences preferences; private final ConfigFileEditor configFileEditor; private final ProvidersRepository providersRepository; + private final CoreNetworkFilter coreNetworkFilter; private final boolean ignoreDevMsg; private final ObjectProperty filterProperty = new SimpleObjectProperty<>(); private final List listeners = new CopyOnWriteArrayList<>(); @@ -115,6 +117,7 @@ public FilterManager(P2PService p2PService, Preferences preferences, Config config, ProvidersRepository providersRepository, + CoreNetworkFilter coreNetworkFilter, @Named(Config.IGNORE_DEV_MSG) boolean ignoreDevMsg, @Named(Config.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) { this.p2PService = p2PService; @@ -123,6 +126,7 @@ public FilterManager(P2PService p2PService, this.preferences = preferences; this.configFileEditor = new ConfigFileEditor(config.configFile); this.providersRepository = providersRepository; + this.coreNetworkFilter = coreNetworkFilter; this.ignoreDevMsg = ignoreDevMsg; publicKeys = useDevPrivilegeKeys ? diff --git a/p2p/src/main/java/bisq/network/p2p/peers/BanList.java b/core/src/main/java/bisq/core/network/CoreNetworkFilter.java similarity index 56% rename from p2p/src/main/java/bisq/network/p2p/peers/BanList.java rename to core/src/main/java/bisq/core/network/CoreNetworkFilter.java index 212a715a95a..1363dd3671b 100644 --- a/p2p/src/main/java/bisq/network/p2p/peers/BanList.java +++ b/core/src/main/java/bisq/core/network/CoreNetworkFilter.java @@ -15,37 +15,35 @@ * along with Bisq. If not, see . */ -package bisq.network.p2p.peers; +package bisq.core.network; import bisq.network.p2p.NodeAddress; +import bisq.network.p2p.network.NetworkFilter; import bisq.common.config.Config; -import javax.inject.Named; - import javax.inject.Inject; +import javax.inject.Named; -import org.apache.commons.lang3.StringUtils; - -import java.util.ArrayList; -import java.util.Arrays; +import java.util.HashSet; import java.util.List; -import java.util.stream.Collectors; +import java.util.Set; -public class BanList { - private static List list = new ArrayList<>(); +import lombok.extern.slf4j.Slf4j; - public static void add(NodeAddress onionAddress) { - list.add(onionAddress); - } +@Slf4j +public class CoreNetworkFilter implements NetworkFilter { + private final Set bannedPeersFromOptions = new HashSet<>(); - public static boolean isBanned(NodeAddress nodeAddress) { - return list.contains(nodeAddress); + /** + * @param banList List of banned peers from program argument + */ + @Inject + public CoreNetworkFilter(@Named(Config.BAN_LIST) List banList) { + banList.stream().map(NodeAddress::new).forEach(bannedPeersFromOptions::add); } - @Inject - public BanList(@Named(Config.BAN_LIST) List banList) { - if (!banList.isEmpty()) - BanList.list = banList.stream().map(NodeAddress::new).collect(Collectors.toList()); + public boolean isPeerBanned(NodeAddress nodeAddress) { + return bannedPeersFromOptions.contains(nodeAddress); } } diff --git a/core/src/test/java/bisq/core/user/UserPayloadModelVOTest.java b/core/src/test/java/bisq/core/user/UserPayloadModelVOTest.java index 144e299181c..4b7f6e3f79e 100644 --- a/core/src/test/java/bisq/core/user/UserPayloadModelVOTest.java +++ b/core/src/test/java/bisq/core/user/UserPayloadModelVOTest.java @@ -25,6 +25,8 @@ import com.google.common.collect.Lists; +import java.util.HashSet; + import org.junit.Ignore; public class UserPayloadModelVOTest { @@ -64,7 +66,8 @@ public void testRoundtripFull() { null, null, false, - Lists.newArrayList())); + Lists.newArrayList(), + new HashSet<>())); vo.setRegisteredArbitrator(ArbitratorTest.getArbitratorMock()); vo.setRegisteredMediator(MediatorTest.getMediatorMock()); diff --git a/core/src/test/java/bisq/core/util/FeeReceiverSelectorTest.java b/core/src/test/java/bisq/core/util/FeeReceiverSelectorTest.java index ca7b77bcffb..d605e01efc5 100644 --- a/core/src/test/java/bisq/core/util/FeeReceiverSelectorTest.java +++ b/core/src/test/java/bisq/core/util/FeeReceiverSelectorTest.java @@ -26,6 +26,7 @@ import com.google.common.primitives.Longs; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Random; @@ -123,6 +124,7 @@ private static Filter filterWithReceivers(List btcFeeReceiverAddresses) null, null, false, - Lists.newArrayList()); + Lists.newArrayList(), + new HashSet<>()); } } diff --git a/inventory/src/main/java/bisq/inventory/InventoryMonitor.java b/inventory/src/main/java/bisq/inventory/InventoryMonitor.java index 4eacd79991f..6da6e63d3e5 100644 --- a/inventory/src/main/java/bisq/inventory/InventoryMonitor.java +++ b/inventory/src/main/java/bisq/inventory/InventoryMonitor.java @@ -257,6 +257,7 @@ private NetworkNode getNetworkNode(File torDir) { CoreNetworkProtoResolver networkProtoResolver = new CoreNetworkProtoResolver(Clock.systemDefaultZone()); return new NetworkNodeProvider(networkProtoResolver, ArrayList::new, + null, useLocalhostForP2P, 9999, torDir, diff --git a/monitor/src/main/java/bisq/monitor/metric/P2PNetworkLoad.java b/monitor/src/main/java/bisq/monitor/metric/P2PNetworkLoad.java index 8c3eab2e637..48c6089776e 100644 --- a/monitor/src/main/java/bisq/monitor/metric/P2PNetworkLoad.java +++ b/monitor/src/main/java/bisq/monitor/metric/P2PNetworkLoad.java @@ -116,7 +116,7 @@ protected void execute() { // start the network node networkNode = new TorNetworkNode(Integer.parseInt(configuration.getProperty(TOR_PROXY_PORT, "9053")), new CoreNetworkProtoResolver(Clock.systemDefaultZone()), false, - new AvailableTor(Monitor.TOR_WORKING_DIR, torHiddenServiceDir.getName())); + new AvailableTor(Monitor.TOR_WORKING_DIR, torHiddenServiceDir.getName()), null); networkNode.start(this); // wait for the HS to be published diff --git a/monitor/src/main/java/bisq/monitor/metric/P2PSeedNodeSnapshotBase.java b/monitor/src/main/java/bisq/monitor/metric/P2PSeedNodeSnapshotBase.java index fa37505fb5d..9550b3dae9f 100644 --- a/monitor/src/main/java/bisq/monitor/metric/P2PSeedNodeSnapshotBase.java +++ b/monitor/src/main/java/bisq/monitor/metric/P2PSeedNodeSnapshotBase.java @@ -155,7 +155,7 @@ protected void execute() { // start the network node final NetworkNode networkNode = new TorNetworkNode(Integer.parseInt(configuration.getProperty(TOR_PROXY_PORT, "9054")), new CoreNetworkProtoResolver(Clock.systemDefaultZone()), false, - new AvailableTor(Monitor.TOR_WORKING_DIR, "unused")); + new AvailableTor(Monitor.TOR_WORKING_DIR, "unused"), null); // we do not need to start the networkNode, as we do not need the HS //networkNode.start(this); diff --git a/p2p/src/main/java/bisq/network/p2p/NetworkNodeProvider.java b/p2p/src/main/java/bisq/network/p2p/NetworkNodeProvider.java index 078db3b21e5..521ba12a714 100644 --- a/p2p/src/main/java/bisq/network/p2p/NetworkNodeProvider.java +++ b/p2p/src/main/java/bisq/network/p2p/NetworkNodeProvider.java @@ -19,6 +19,7 @@ import bisq.network.p2p.network.BridgeAddressProvider; import bisq.network.p2p.network.LocalhostNetworkNode; +import bisq.network.p2p.network.NetworkFilter; import bisq.network.p2p.network.NetworkNode; import bisq.network.p2p.network.NewTor; import bisq.network.p2p.network.RunningTor; @@ -27,10 +28,9 @@ import bisq.common.config.Config; import bisq.common.proto.network.NetworkProtoResolver; -import javax.inject.Provider; -import javax.inject.Named; - import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Provider; import java.io.File; @@ -43,6 +43,7 @@ public class NetworkNodeProvider implements Provider { @Inject public NetworkNodeProvider(NetworkProtoResolver networkProtoResolver, BridgeAddressProvider bridgeAddressProvider, + @Nullable NetworkFilter networkFilter, @Named(Config.USE_LOCALHOST_FOR_P2P) boolean useLocalhostForP2P, @Named(Config.NODE_PORT) int port, @Named(Config.TOR_DIR) File torDir, @@ -52,13 +53,14 @@ public NetworkNodeProvider(NetworkProtoResolver networkProtoResolver, @Named(Config.TOR_CONTROL_PASSWORD) String password, @Nullable @Named(Config.TOR_CONTROL_COOKIE_FILE) File cookieFile, @Named(Config.TOR_STREAM_ISOLATION) boolean streamIsolation, - @Named(Config.TOR_CONTROL_USE_SAFE_COOKIE_AUTH) boolean useSafeCookieAuthentication ) { + @Named(Config.TOR_CONTROL_USE_SAFE_COOKIE_AUTH) boolean useSafeCookieAuthentication) { networkNode = useLocalhostForP2P ? - new LocalhostNetworkNode(port, networkProtoResolver) : + new LocalhostNetworkNode(port, networkProtoResolver, networkFilter) : new TorNetworkNode(port, networkProtoResolver, streamIsolation, controlPort != Config.UNSPECIFIED_PORT ? new RunningTor(torDir, controlPort, password, cookieFile, useSafeCookieAuthentication) : - new NewTor(torDir, torrcFile, torrcOptions, bridgeAddressProvider.getBridgeAddresses())); + new NewTor(torDir, torrcFile, torrcOptions, bridgeAddressProvider.getBridgeAddresses()), + networkFilter); } @Override diff --git a/p2p/src/main/java/bisq/network/p2p/P2PModule.java b/p2p/src/main/java/bisq/network/p2p/P2PModule.java index 896fe8098c2..e5df2c6e802 100644 --- a/p2p/src/main/java/bisq/network/p2p/P2PModule.java +++ b/p2p/src/main/java/bisq/network/p2p/P2PModule.java @@ -22,7 +22,6 @@ import bisq.network.http.HttpClientImpl; import bisq.network.p2p.network.Connection; import bisq.network.p2p.network.NetworkNode; -import bisq.network.p2p.peers.BanList; import bisq.network.p2p.peers.Broadcaster; import bisq.network.p2p.peers.PeerManager; import bisq.network.p2p.peers.getdata.RequestDataManager; @@ -68,7 +67,6 @@ protected void configure() { bind(PeerExchangeManager.class).in(Singleton.class); bind(KeepAliveManager.class).in(Singleton.class); bind(Broadcaster.class).in(Singleton.class); - bind(BanList.class).in(Singleton.class); bind(NetworkNode.class).toProvider(NetworkNodeProvider.class).in(Singleton.class); bind(Socks5ProxyProvider.class).in(Singleton.class); bind(HttpClient.class).to(HttpClientImpl.class); diff --git a/p2p/src/main/java/bisq/network/p2p/network/Connection.java b/p2p/src/main/java/bisq/network/p2p/network/Connection.java index 80cf65f32cd..b5bf23d1b01 100644 --- a/p2p/src/main/java/bisq/network/p2p/network/Connection.java +++ b/p2p/src/main/java/bisq/network/p2p/network/Connection.java @@ -24,7 +24,6 @@ import bisq.network.p2p.PrefixedSealedAndSignedMessage; import bisq.network.p2p.SendersNodeAddressMessage; import bisq.network.p2p.SupportedCapabilitiesMessage; -import bisq.network.p2p.peers.BanList; import bisq.network.p2p.peers.getdata.messages.GetDataRequest; import bisq.network.p2p.peers.getdata.messages.GetDataResponse; import bisq.network.p2p.peers.keepalive.messages.KeepAliveMessage; @@ -141,6 +140,8 @@ public static int getPermittedMessageSize() { private final Socket socket; // private final MessageListener messageListener; private final ConnectionListener connectionListener; + @Nullable + private final NetworkFilter networkFilter; @Getter private final String uid; private final ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(runnable -> new Thread(runnable, "Connection.java executor-service")); @@ -184,9 +185,11 @@ public static int getPermittedMessageSize() { MessageListener messageListener, ConnectionListener connectionListener, @Nullable NodeAddress peersNodeAddress, - NetworkProtoResolver networkProtoResolver) { + NetworkProtoResolver networkProtoResolver, + @Nullable NetworkFilter networkFilter) { this.socket = socket; this.connectionListener = connectionListener; + this.networkFilter = networkFilter; uid = UUID.randomUUID().toString(); statistic = new Statistic(); @@ -209,9 +212,12 @@ private void init(@Nullable NodeAddress peersNodeAddress) { // We create a thread for handling inputStream data singleThreadExecutor.submit(this); - if (peersNodeAddress != null) + if (peersNodeAddress != null) { setPeersNodeAddress(peersNodeAddress); - + if (networkFilter != null && networkFilter.isPeerBanned(peersNodeAddress)) { + reportInvalidRequest(RuleViolation.PEER_BANNED); + } + } UserThread.execute(() -> connectionListener.onConnection(this)); } catch (Throwable e) { handleException(e); @@ -484,13 +490,9 @@ private void setPeersNodeAddress(NodeAddress peerNodeAddress) { } peersNodeAddressProperty.set(peerNodeAddress); - - if (BanList.isBanned(peerNodeAddress)) { - log.warn("We detected a connection to a banned peer. We will close that connection. (setPeersNodeAddress)"); - reportInvalidRequest(RuleViolation.PEER_BANNED); - } } + /////////////////////////////////////////////////////////////////////////////////////////// // Getters /////////////////////////////////////////////////////////////////////////////////////////// @@ -881,6 +883,10 @@ && reportInvalidRequest(RuleViolation.WRONG_NETWORK_ID)) { // We check for a banned peer inside setPeersNodeAddress() and shut down if banned. setPeersNodeAddress(senderNodeAddress); } + + if (networkFilter != null && networkFilter.isPeerBanned(senderNodeAddress)) { + reportInvalidRequest(RuleViolation.PEER_BANNED); + } } } diff --git a/p2p/src/main/java/bisq/network/p2p/network/InboundConnection.java b/p2p/src/main/java/bisq/network/p2p/network/InboundConnection.java index 1e6948fab1e..b691d906d2b 100644 --- a/p2p/src/main/java/bisq/network/p2p/network/InboundConnection.java +++ b/p2p/src/main/java/bisq/network/p2p/network/InboundConnection.java @@ -21,11 +21,14 @@ import java.net.Socket; +import org.jetbrains.annotations.Nullable; + public class InboundConnection extends Connection { public InboundConnection(Socket socket, MessageListener messageListener, ConnectionListener connectionListener, - NetworkProtoResolver networkProtoResolver) { - super(socket, messageListener, connectionListener, null, networkProtoResolver); + NetworkProtoResolver networkProtoResolver, + @Nullable NetworkFilter networkFilter) { + super(socket, messageListener, connectionListener, null, networkProtoResolver, networkFilter); } } diff --git a/p2p/src/main/java/bisq/network/p2p/network/LocalhostNetworkNode.java b/p2p/src/main/java/bisq/network/p2p/network/LocalhostNetworkNode.java index b49bff1e737..97fce6e14f8 100644 --- a/p2p/src/main/java/bisq/network/p2p/network/LocalhostNetworkNode.java +++ b/p2p/src/main/java/bisq/network/p2p/network/LocalhostNetworkNode.java @@ -54,8 +54,10 @@ public static void setSimulateTorDelayHiddenService(int simulateTorDelayHiddenSe // Constructor /////////////////////////////////////////////////////////////////////////////////////////// - public LocalhostNetworkNode(int port, NetworkProtoResolver networkProtoResolver) { - super(port, networkProtoResolver); + public LocalhostNetworkNode(int port, + NetworkProtoResolver networkProtoResolver, + @Nullable NetworkFilter networkFilter) { + super(port, networkProtoResolver, networkFilter); } @Override diff --git a/p2p/src/main/java/bisq/network/p2p/network/NetworkFilter.java b/p2p/src/main/java/bisq/network/p2p/network/NetworkFilter.java new file mode 100644 index 00000000000..5dd116cc90c --- /dev/null +++ b/p2p/src/main/java/bisq/network/p2p/network/NetworkFilter.java @@ -0,0 +1,24 @@ +/* + * 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.network.p2p.network; + +import bisq.network.p2p.NodeAddress; + +public interface NetworkFilter { + boolean isPeerBanned(NodeAddress nodeAddress); +} diff --git a/p2p/src/main/java/bisq/network/p2p/network/NetworkNode.java b/p2p/src/main/java/bisq/network/p2p/network/NetworkNode.java index c2695833f7a..ae32233bc1b 100644 --- a/p2p/src/main/java/bisq/network/p2p/network/NetworkNode.java +++ b/p2p/src/main/java/bisq/network/p2p/network/NetworkNode.java @@ -69,6 +69,8 @@ public abstract class NetworkNode implements MessageListener { final int servicePort; private final NetworkProtoResolver networkProtoResolver; + @Nullable + private final NetworkFilter networkFilter; private final CopyOnWriteArraySet inBoundConnections = new CopyOnWriteArraySet<>(); private final CopyOnWriteArraySet messageListeners = new CopyOnWriteArraySet<>(); @@ -87,9 +89,12 @@ public abstract class NetworkNode implements MessageListener { // Constructor /////////////////////////////////////////////////////////////////////////////////////////// - NetworkNode(int servicePort, NetworkProtoResolver networkProtoResolver) { + NetworkNode(int servicePort, + NetworkProtoResolver networkProtoResolver, + @Nullable NetworkFilter networkFilter) { this.servicePort = servicePort; this.networkProtoResolver = networkProtoResolver; + this.networkFilter = networkFilter; } /////////////////////////////////////////////////////////////////////////////////////////// @@ -190,7 +195,8 @@ public void onError(Throwable throwable) { NetworkNode.this, connectionListener, peersNodeAddress, - networkProtoResolver); + networkProtoResolver, + networkFilter); if (log.isDebugEnabled()) { log.debug("\n\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n" + @@ -457,7 +463,8 @@ public void onError(Throwable throwable) { server = new Server(serverSocket, NetworkNode.this, connectionListener, - networkProtoResolver); + networkProtoResolver, + networkFilter); executorService.submit(server); } diff --git a/p2p/src/main/java/bisq/network/p2p/network/OutboundConnection.java b/p2p/src/main/java/bisq/network/p2p/network/OutboundConnection.java index 5b27d02e2d5..6c6c6d341a8 100644 --- a/p2p/src/main/java/bisq/network/p2p/network/OutboundConnection.java +++ b/p2p/src/main/java/bisq/network/p2p/network/OutboundConnection.java @@ -23,12 +23,15 @@ import java.net.Socket; +import org.jetbrains.annotations.Nullable; + public class OutboundConnection extends Connection { public OutboundConnection(Socket socket, MessageListener messageListener, ConnectionListener connectionListener, NodeAddress peersNodeAddress, - NetworkProtoResolver networkProtoResolver) { - super(socket, messageListener, connectionListener, peersNodeAddress, networkProtoResolver); + NetworkProtoResolver networkProtoResolver, + @Nullable NetworkFilter networkFilter) { + super(socket, messageListener, connectionListener, peersNodeAddress, networkProtoResolver, networkFilter); } } diff --git a/p2p/src/main/java/bisq/network/p2p/network/Server.java b/p2p/src/main/java/bisq/network/p2p/network/Server.java index 8f8db624ebd..f44622e6025 100644 --- a/p2p/src/main/java/bisq/network/p2p/network/Server.java +++ b/p2p/src/main/java/bisq/network/p2p/network/Server.java @@ -31,12 +31,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.jetbrains.annotations.Nullable; + // Runs in UserThread class Server implements Runnable { private static final Logger log = LoggerFactory.getLogger(Server.class); private final MessageListener messageListener; private final ConnectionListener connectionListener; + @Nullable + private final NetworkFilter networkFilter; // accessed from different threads private final ServerSocket serverSocket; @@ -48,11 +52,13 @@ class Server implements Runnable { public Server(ServerSocket serverSocket, MessageListener messageListener, ConnectionListener connectionListener, - NetworkProtoResolver networkProtoResolver) { + NetworkProtoResolver networkProtoResolver, + @Nullable NetworkFilter networkFilter) { this.networkProtoResolver = networkProtoResolver; this.serverSocket = serverSocket; this.messageListener = messageListener; this.connectionListener = connectionListener; + this.networkFilter = networkFilter; } @Override @@ -69,7 +75,8 @@ public void run() { InboundConnection connection = new InboundConnection(socket, messageListener, connectionListener, - networkProtoResolver); + networkProtoResolver, + networkFilter); log.debug("\n\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n" + "Server created new inbound connection:" diff --git a/p2p/src/main/java/bisq/network/p2p/network/TorNetworkNode.java b/p2p/src/main/java/bisq/network/p2p/network/TorNetworkNode.java index 2ca0cd5b781..a57e5404bc1 100644 --- a/p2p/src/main/java/bisq/network/p2p/network/TorNetworkNode.java +++ b/p2p/src/main/java/bisq/network/p2p/network/TorNetworkNode.java @@ -87,9 +87,12 @@ public class TorNetworkNode extends NetworkNode { // Constructor /////////////////////////////////////////////////////////////////////////////////////////// - public TorNetworkNode(int servicePort, NetworkProtoResolver networkProtoResolver, boolean useStreamIsolation, - TorMode torMode) { - super(servicePort, networkProtoResolver); + public TorNetworkNode(int servicePort, + NetworkProtoResolver networkProtoResolver, + boolean useStreamIsolation, + TorMode torMode, + @Nullable NetworkFilter networkFilter) { + super(servicePort, networkProtoResolver, networkFilter); this.torMode = torMode; this.streamIsolation = useStreamIsolation; createExecutorService(); diff --git a/p2p/src/test/java/bisq/network/p2p/DummySeedNode.java b/p2p/src/test/java/bisq/network/p2p/DummySeedNode.java index 3fe5205c661..405b7fd5fc9 100644 --- a/p2p/src/test/java/bisq/network/p2p/DummySeedNode.java +++ b/p2p/src/test/java/bisq/network/p2p/DummySeedNode.java @@ -17,8 +17,6 @@ package bisq.network.p2p; -import bisq.network.p2p.peers.BanList; - import bisq.common.UserThread; import bisq.common.app.Log; import bisq.common.app.Version; @@ -137,10 +135,11 @@ public void processArgs(String[] args) { checkArgument(arg.contains(":") && arg.split(":").length > 1 && arg.split(":")[1].length() > 3, "Wrong program argument " + arg); List list = Arrays.asList(arg.split(",")); + Set bannedPeers = new HashSet<>(); list.forEach(e -> { checkArgument(e.contains(":") && e.split(":").length == 2 && e.split(":")[1].length() == 4, "Wrong program argument " + e); - BanList.add(new NodeAddress(e)); + bannedPeers.add(new NodeAddress(e)); }); log.debug("From processArgs: ignoreList=" + list); } else if (arg.startsWith(HELP)) { diff --git a/p2p/src/test/java/bisq/network/p2p/network/LocalhostNetworkNodeTest.java b/p2p/src/test/java/bisq/network/p2p/network/LocalhostNetworkNodeTest.java index 1a09281e0d6..8315332c34f 100644 --- a/p2p/src/test/java/bisq/network/p2p/network/LocalhostNetworkNodeTest.java +++ b/p2p/src/test/java/bisq/network/p2p/network/LocalhostNetworkNodeTest.java @@ -38,13 +38,10 @@ public class LocalhostNetworkNodeTest { private static final Logger log = LoggerFactory.getLogger(LocalhostNetworkNodeTest.class); - - - @Test public void testMessage() throws InterruptedException, IOException { CountDownLatch msgLatch = new CountDownLatch(2); - LocalhostNetworkNode node1 = new LocalhostNetworkNode(9001, TestUtils.getNetworkProtoResolver()); + LocalhostNetworkNode node1 = new LocalhostNetworkNode(9001, TestUtils.getNetworkProtoResolver(), null); node1.addMessageListener((message, connection) -> { log.debug("onMessage node1 " + message); msgLatch.countDown(); @@ -72,7 +69,7 @@ public void onRequestCustomBridges() { } }); - LocalhostNetworkNode node2 = new LocalhostNetworkNode(9002, TestUtils.getNetworkProtoResolver()); + LocalhostNetworkNode node2 = new LocalhostNetworkNode(9002, TestUtils.getNetworkProtoResolver(), null); node2.addMessageListener((message, connection) -> { log.debug("onMessage node2 " + message); msgLatch.countDown(); diff --git a/p2p/src/test/java/bisq/network/p2p/network/TorNetworkNodeTest.java b/p2p/src/test/java/bisq/network/p2p/network/TorNetworkNodeTest.java index b050fdf6596..3b544da4e59 100644 --- a/p2p/src/test/java/bisq/network/p2p/network/TorNetworkNodeTest.java +++ b/p2p/src/test/java/bisq/network/p2p/network/TorNetworkNodeTest.java @@ -27,6 +27,7 @@ import java.io.File; import java.io.IOException; + import java.util.ArrayList; import java.util.concurrent.CountDownLatch; @@ -49,13 +50,12 @@ public class TorNetworkNodeTest { private CountDownLatch latch; - @Test public void testTorNodeBeforeSecondReady() throws InterruptedException, IOException { latch = new CountDownLatch(1); int port = 9001; TorNetworkNode node1 = new TorNetworkNode(port, TestUtils.getNetworkProtoResolver(), false, - new NewTor(new File("torNode_" + port), null, "", new ArrayList())); + new NewTor(new File("torNode_" + port), null, "", new ArrayList()), null); node1.start(new SetupListener() { @Override public void onTorNodeReady() { @@ -82,7 +82,7 @@ public void onRequestCustomBridges() { latch = new CountDownLatch(1); int port2 = 9002; TorNetworkNode node2 = new TorNetworkNode(port2, TestUtils.getNetworkProtoResolver(), false, - new NewTor(new File("torNode_" + port), null, "", new ArrayList())); + new NewTor(new File("torNode_" + port), null, "", new ArrayList()), null); node2.start(new SetupListener() { @Override public void onTorNodeReady() { @@ -140,7 +140,7 @@ public void testTorNodeAfterBothReady() throws InterruptedException, IOException latch = new CountDownLatch(2); int port = 9001; TorNetworkNode node1 = new TorNetworkNode(port, TestUtils.getNetworkProtoResolver(), false, - new NewTor(new File("torNode_" + port), null, "", new ArrayList())); + new NewTor(new File("torNode_" + port), null, "", new ArrayList()), null); node1.start(new SetupListener() { @Override public void onTorNodeReady() { @@ -166,7 +166,7 @@ public void onRequestCustomBridges() { int port2 = 9002; TorNetworkNode node2 = new TorNetworkNode(port2, TestUtils.getNetworkProtoResolver(), false, - new NewTor(new File("torNode_" + port), null, "", new ArrayList())); + new NewTor(new File("torNode_" + port), null, "", new ArrayList()), null); node2.start(new SetupListener() { @Override public void onTorNodeReady() { From 1ef086a947e76517777a710b0d0a830c75bfdff1 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Fri, 1 Jan 2021 14:17:12 -0500 Subject: [PATCH 05/31] Make code more readable --- .../bisq/network/p2p/NetworkNodeProvider.java | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/p2p/src/main/java/bisq/network/p2p/NetworkNodeProvider.java b/p2p/src/main/java/bisq/network/p2p/NetworkNodeProvider.java index 521ba12a714..e8cf8d92385 100644 --- a/p2p/src/main/java/bisq/network/p2p/NetworkNodeProvider.java +++ b/p2p/src/main/java/bisq/network/p2p/NetworkNodeProvider.java @@ -23,6 +23,7 @@ import bisq.network.p2p.network.NetworkNode; import bisq.network.p2p.network.NewTor; import bisq.network.p2p.network.RunningTor; +import bisq.network.p2p.network.TorMode; import bisq.network.p2p.network.TorNetworkNode; import bisq.common.config.Config; @@ -54,13 +55,32 @@ public NetworkNodeProvider(NetworkProtoResolver networkProtoResolver, @Nullable @Named(Config.TOR_CONTROL_COOKIE_FILE) File cookieFile, @Named(Config.TOR_STREAM_ISOLATION) boolean streamIsolation, @Named(Config.TOR_CONTROL_USE_SAFE_COOKIE_AUTH) boolean useSafeCookieAuthentication) { - networkNode = useLocalhostForP2P ? - new LocalhostNetworkNode(port, networkProtoResolver, networkFilter) : - new TorNetworkNode(port, networkProtoResolver, streamIsolation, - controlPort != Config.UNSPECIFIED_PORT ? - new RunningTor(torDir, controlPort, password, cookieFile, useSafeCookieAuthentication) : - new NewTor(torDir, torrcFile, torrcOptions, bridgeAddressProvider.getBridgeAddresses()), - networkFilter); + if (useLocalhostForP2P) { + networkNode = new LocalhostNetworkNode(port, networkProtoResolver, networkFilter); + } else { + TorMode torMode = getTorMode(bridgeAddressProvider, + torDir, + torrcFile, + torrcOptions, + controlPort, + password, + cookieFile, + useSafeCookieAuthentication); + networkNode = new TorNetworkNode(port, networkProtoResolver, streamIsolation, torMode, networkFilter); + } + } + + private TorMode getTorMode(BridgeAddressProvider bridgeAddressProvider, + File torDir, + @Nullable File torrcFile, + String torrcOptions, + int controlPort, + String password, + @Nullable File cookieFile, + boolean useSafeCookieAuthentication) { + return controlPort != Config.UNSPECIFIED_PORT ? + new RunningTor(torDir, controlPort, password, cookieFile, useSafeCookieAuthentication) : + new NewTor(torDir, torrcFile, torrcOptions, bridgeAddressProvider.getBridgeAddresses()); } @Override From 79a0874b5da83a3e465d7a6c3c295796ab96303f Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Fri, 1 Jan 2021 16:00:22 -0500 Subject: [PATCH 06/31] Add support to filter manager for network wide banned nodes --- .../java/bisq/common/proto/ProtoUtil.java | 4 +++ .../main/java/bisq/core/filter/Filter.java | 32 +++++++++++++++---- .../java/bisq/core/filter/FilterManager.java | 13 +++++--- .../bisq/core/network/CoreNetworkFilter.java | 11 ++++++- .../resources/i18n/displayStrings.properties | 3 +- .../main/overlays/windows/FilterWindow.java | 14 +++++--- .../bisq/network/p2p/network/Connection.java | 1 + .../network/p2p/network/NetworkFilter.java | 4 +++ proto/src/main/proto/pb.proto | 1 + 9 files changed, 66 insertions(+), 17 deletions(-) diff --git a/common/src/main/java/bisq/common/proto/ProtoUtil.java b/common/src/main/java/bisq/common/proto/ProtoUtil.java index e8471a4d823..d7450e9d509 100644 --- a/common/src/main/java/bisq/common/proto/ProtoUtil.java +++ b/common/src/main/java/bisq/common/proto/ProtoUtil.java @@ -28,6 +28,7 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; @@ -105,4 +106,7 @@ public static List protocolStringListToList(ProtocolStringList protocolS return CollectionUtils.isEmpty(protocolStringList) ? new ArrayList<>() : new ArrayList<>(protocolStringList); } + public static Set protocolStringListToSet(ProtocolStringList protocolStringList) { + return CollectionUtils.isEmpty(protocolStringList) ? new HashSet<>() : new HashSet<>(protocolStringList); + } } diff --git a/core/src/main/java/bisq/core/filter/Filter.java b/core/src/main/java/bisq/core/filter/Filter.java index a642c9511c9..dfcbe3f5f0d 100644 --- a/core/src/main/java/bisq/core/filter/Filter.java +++ b/core/src/main/java/bisq/core/filter/Filter.java @@ -35,6 +35,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -47,7 +48,10 @@ @Value public final class Filter implements ProtectedStoragePayload, ExpirablePayload { private final List bannedOfferIds; + + // Those banned nodes cannot trade but still can access the network private final List bannedNodeAddress; + private final List bannedAutoConfExplorers; private final List bannedPaymentAccounts; private final List bannedCurrencies; @@ -91,6 +95,11 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload { // added at v1.3.8 private final boolean disableAutoConf; + // added at v1.5.5 + // In contrast to bannedNodeAddress those addresses cannot access the network but are rejected immediately. + // Should be only used in case of dos attacks + private final Set nodeAddressesBannedFromNetwork; + // After we have created the signature from the filter data we clone it and apply the signature static Filter cloneWithSig(Filter filter, String signatureAsBase64) { return new Filter(filter.getBannedOfferIds(), @@ -117,7 +126,8 @@ static Filter cloneWithSig(Filter filter, String signatureAsBase64) { filter.getSignerPubKeyAsHex(), filter.getBannedPrivilegedDevPubKeys(), filter.isDisableAutoConf(), - filter.getBannedAutoConfExplorers()); + filter.getBannedAutoConfExplorers(), + filter.getNodeAddressesBannedFromNetwork()); } // Used for signature verification as we created the sig without the signatureAsBase64 field we set it to null again @@ -146,7 +156,8 @@ static Filter cloneWithoutSig(Filter filter) { filter.getSignerPubKeyAsHex(), filter.getBannedPrivilegedDevPubKeys(), filter.isDisableAutoConf(), - filter.getBannedAutoConfExplorers()); + filter.getBannedAutoConfExplorers(), + filter.getNodeAddressesBannedFromNetwork()); } public Filter(List bannedOfferIds, @@ -170,7 +181,8 @@ public Filter(List bannedOfferIds, String signerPubKeyAsHex, List bannedPrivilegedDevPubKeys, boolean disableAutoConf, - List bannedAutoConfExplorers) { + List bannedAutoConfExplorers, + Set nodeAddressesBannedFromNetwork) { this(bannedOfferIds, bannedNodeAddress, bannedPaymentAccounts, @@ -195,7 +207,8 @@ public Filter(List bannedOfferIds, signerPubKeyAsHex, bannedPrivilegedDevPubKeys, disableAutoConf, - bannedAutoConfExplorers); + bannedAutoConfExplorers, + nodeAddressesBannedFromNetwork); } @@ -228,7 +241,8 @@ public Filter(List bannedOfferIds, String signerPubKeyAsHex, List bannedPrivilegedDevPubKeys, boolean disableAutoConf, - List bannedAutoConfExplorers) { + List bannedAutoConfExplorers, + Set nodeAddressesBannedFromNetwork) { this.bannedOfferIds = bannedOfferIds; this.bannedNodeAddress = bannedNodeAddress; this.bannedPaymentAccounts = bannedPaymentAccounts; @@ -254,6 +268,7 @@ public Filter(List bannedOfferIds, this.bannedPrivilegedDevPubKeys = bannedPrivilegedDevPubKeys; this.disableAutoConf = disableAutoConf; this.bannedAutoConfExplorers = bannedAutoConfExplorers; + this.nodeAddressesBannedFromNetwork = nodeAddressesBannedFromNetwork; // ownerPubKeyBytes can be null when called from tests if (ownerPubKeyBytes != null) { @@ -291,7 +306,8 @@ public protobuf.StoragePayload toProtoMessage() { .setCreationDate(creationDate) .addAllBannedPrivilegedDevPubKeys(bannedPrivilegedDevPubKeys) .setDisableAutoConf(disableAutoConf) - .addAllBannedAutoConfExplorers(bannedAutoConfExplorers); + .addAllBannedAutoConfExplorers(bannedAutoConfExplorers) + .addAllNodeAddressesBannedFromNetwork(nodeAddressesBannedFromNetwork); Optional.ofNullable(signatureAsBase64).ifPresent(builder::setSignatureAsBase64); Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData); @@ -329,7 +345,8 @@ public static Filter fromProto(protobuf.Filter proto) { proto.getSignerPubKeyAsHex(), ProtoUtil.protocolStringListToList(proto.getBannedPrivilegedDevPubKeysList()), proto.getDisableAutoConf(), - ProtoUtil.protocolStringListToList(proto.getBannedAutoConfExplorersList()) + ProtoUtil.protocolStringListToList(proto.getBannedAutoConfExplorersList()), + ProtoUtil.protocolStringListToSet(proto.getNodeAddressesBannedFromNetworkList()) ); } @@ -372,6 +389,7 @@ public String toString() { ",\n extraDataMap=" + extraDataMap + ",\n ownerPubKey=" + ownerPubKey + ",\n disableAutoConf=" + disableAutoConf + + ",\n nodeAddressesBannedFromNetwork=" + nodeAddressesBannedFromNetwork + "\n}"; } } diff --git a/core/src/main/java/bisq/core/filter/FilterManager.java b/core/src/main/java/bisq/core/filter/FilterManager.java index 6656d4974c3..e2d99082fd2 100644 --- a/core/src/main/java/bisq/core/filter/FilterManager.java +++ b/core/src/main/java/bisq/core/filter/FilterManager.java @@ -19,7 +19,6 @@ import bisq.core.btc.nodes.BtcNodes; import bisq.core.locale.Res; -import bisq.core.network.CoreNetworkFilter; import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.payment.payload.PaymentMethod; import bisq.core.provider.ProvidersRepository; @@ -29,6 +28,7 @@ import bisq.network.p2p.NodeAddress; import bisq.network.p2p.P2PService; import bisq.network.p2p.P2PServiceListener; +import bisq.network.p2p.network.NetworkFilter; import bisq.network.p2p.storage.HashMapChangedListener; import bisq.network.p2p.storage.payload.ProtectedStorageEntry; @@ -97,7 +97,6 @@ public interface Listener { private final Preferences preferences; private final ConfigFileEditor configFileEditor; private final ProvidersRepository providersRepository; - private final CoreNetworkFilter coreNetworkFilter; private final boolean ignoreDevMsg; private final ObjectProperty filterProperty = new SimpleObjectProperty<>(); private final List listeners = new CopyOnWriteArrayList<>(); @@ -117,7 +116,7 @@ public FilterManager(P2PService p2PService, Preferences preferences, Config config, ProvidersRepository providersRepository, - CoreNetworkFilter coreNetworkFilter, + NetworkFilter networkFilter, @Named(Config.IGNORE_DEV_MSG) boolean ignoreDevMsg, @Named(Config.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) { this.p2PService = p2PService; @@ -126,7 +125,6 @@ public FilterManager(P2PService p2PService, this.preferences = preferences; this.configFileEditor = new ConfigFileEditor(config.configFile); this.providersRepository = providersRepository; - this.coreNetworkFilter = coreNetworkFilter; this.ignoreDevMsg = ignoreDevMsg; publicKeys = useDevPrivilegeKeys ? @@ -135,6 +133,7 @@ public FilterManager(P2PService p2PService, "029340c3e7d4bb0f9e651b5f590b434fecb6175aeaa57145c7804ff05d210e534f", "034dc7530bf66ffd9580aa98031ea9a18ac2d269f7c56c0e71eca06105b9ed69f9"); + networkFilter.setBannedNodeFunction(this::isNodeAddressBannedFromNetwork); } @@ -402,6 +401,12 @@ public boolean isNodeAddressBanned(NodeAddress nodeAddress) { .anyMatch(e -> e.equals(nodeAddress.getFullAddress())); } + public boolean isNodeAddressBannedFromNetwork(NodeAddress nodeAddress) { + return getFilter() != null && + getFilter().getNodeAddressesBannedFromNetwork().stream() + .anyMatch(e -> e.equals(nodeAddress.getFullAddress())); + } + public boolean isAutoConfExplorerBanned(String address) { return getFilter() != null && getFilter().getBannedAutoConfExplorers().stream() diff --git a/core/src/main/java/bisq/core/network/CoreNetworkFilter.java b/core/src/main/java/bisq/core/network/CoreNetworkFilter.java index 1363dd3671b..b261d421537 100644 --- a/core/src/main/java/bisq/core/network/CoreNetworkFilter.java +++ b/core/src/main/java/bisq/core/network/CoreNetworkFilter.java @@ -28,12 +28,14 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.function.Function; import lombok.extern.slf4j.Slf4j; @Slf4j public class CoreNetworkFilter implements NetworkFilter { private final Set bannedPeersFromOptions = new HashSet<>(); + private Function bannedNodeFunction; /** * @param banList List of banned peers from program argument @@ -43,7 +45,14 @@ public CoreNetworkFilter(@Named(Config.BAN_LIST) List banList) { banList.stream().map(NodeAddress::new).forEach(bannedPeersFromOptions::add); } + @Override + public void setBannedNodeFunction(Function bannedNodeFunction) { + this.bannedNodeFunction = bannedNodeFunction; + } + + @Override public boolean isPeerBanned(NodeAddress nodeAddress) { - return bannedPeersFromOptions.contains(nodeAddress); + return bannedPeersFromOptions.contains(nodeAddress) || + bannedNodeFunction != null && bannedNodeFunction.apply(nodeAddress); } } diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 4a3f82283c4..e7d5912e1c9 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -2594,7 +2594,8 @@ enterPrivKeyWindow.headline=Enter private key for registration filterWindow.headline=Edit filter list filterWindow.offers=Filtered offers (comma sep.) -filterWindow.onions=Filtered onion addresses (comma sep.) +filterWindow.onions=Banned from trading addresses (comma sep.) +filterWindow.bannedFromNetwork=Banned from network addresses (comma sep.) filterWindow.accounts=Filtered trading account data:\nFormat: comma sep. list of [payment method id | data field | value] filterWindow.bannedCurrencies=Filtered currency codes (comma sep.) filterWindow.bannedPaymentMethods=Filtered payment method IDs (comma sep.) 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 a82f00d02b7..97efe076b14 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 @@ -37,8 +37,6 @@ import org.apache.commons.lang3.StringUtils; -import javafx.collections.FXCollections; - import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.CheckBox; @@ -51,7 +49,11 @@ import javafx.geometry.HPos; import javafx.geometry.Insets; +import javafx.collections.FXCollections; + import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.stream.Collectors; @@ -124,6 +126,8 @@ private void addContent() { Res.get("filterWindow.offers")); InputTextField nodesTF = addTopLabelInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.onions")).second; + InputTextField bannedFromNetworkTF = addTopLabelInputTextField(gridPane, ++rowIndex, + Res.get("filterWindow.bannedFromNetwork")).second; nodesTF.setPromptText("E.g. zqnzx6o3nifef5df.onion:9999"); // Do not translate InputTextField paymentAccountFilterTF = addTopLabelInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.accounts")).second; @@ -170,6 +174,7 @@ private void addContent() { if (filter != null) { setupFieldFromList(offerIdsTF, filter.getBannedOfferIds()); setupFieldFromList(nodesTF, filter.getBannedNodeAddress()); + setupFieldFromList(bannedFromNetworkTF, filter.getNodeAddressesBannedFromNetwork()); setupFieldFromPaymentAccountFiltersList(paymentAccountFilterTF, filter.getBannedPaymentAccounts()); setupFieldFromList(bannedCurrenciesTF, filter.getBannedCurrencies()); setupFieldFromList(bannedPaymentMethodsTF, filter.getBannedPaymentMethods()); @@ -221,7 +226,8 @@ private void addContent() { signerPubKeyAsHex, readAsList(bannedPrivilegedDevPubKeysTF), disableAutoConfCheckBox.isSelected(), - readAsList(autoConfExplorersTF) + readAsList(autoConfExplorersTF), + new HashSet<>(readAsList(bannedFromNetworkTF)) ); // We remove first the old filter @@ -270,7 +276,7 @@ private void addDevFilter(Button removeFilterMessageButton, String privKeyString hide(); } - private void setupFieldFromList(InputTextField field, List values) { + private void setupFieldFromList(InputTextField field, Collection values) { if (values != null) field.setText(String.join(", ", values)); } diff --git a/p2p/src/main/java/bisq/network/p2p/network/Connection.java b/p2p/src/main/java/bisq/network/p2p/network/Connection.java index b5bf23d1b01..dce59280859 100644 --- a/p2p/src/main/java/bisq/network/p2p/network/Connection.java +++ b/p2p/src/main/java/bisq/network/p2p/network/Connection.java @@ -886,6 +886,7 @@ && reportInvalidRequest(RuleViolation.WRONG_NETWORK_ID)) { if (networkFilter != null && networkFilter.isPeerBanned(senderNodeAddress)) { reportInvalidRequest(RuleViolation.PEER_BANNED); + return; } } } diff --git a/p2p/src/main/java/bisq/network/p2p/network/NetworkFilter.java b/p2p/src/main/java/bisq/network/p2p/network/NetworkFilter.java index 5dd116cc90c..3dcf040e21f 100644 --- a/p2p/src/main/java/bisq/network/p2p/network/NetworkFilter.java +++ b/p2p/src/main/java/bisq/network/p2p/network/NetworkFilter.java @@ -19,6 +19,10 @@ import bisq.network.p2p.NodeAddress; +import java.util.function.Function; + public interface NetworkFilter { boolean isPeerBanned(NodeAddress nodeAddress); + + void setBannedNodeFunction(Function isNodeAddressBanned); } diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 66af295531f..86d7a778b27 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -677,6 +677,7 @@ message Filter { repeated string bannedPrivilegedDevPubKeys = 23; bool disable_auto_conf = 24; repeated string banned_auto_conf_explorers = 25; + repeated string node_addresses_banned_from_network = 26; } // Deprecated From e86805015815d61bf170ed6a64dee10187777172 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Fri, 1 Jan 2021 16:20:05 -0500 Subject: [PATCH 07/31] Add checks if peer is banned at send msg and InputHandlers --- .../bisq/network/p2p/network/Connection.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/p2p/src/main/java/bisq/network/p2p/network/Connection.java b/p2p/src/main/java/bisq/network/p2p/network/Connection.java index dce59280859..89356ebb701 100644 --- a/p2p/src/main/java/bisq/network/p2p/network/Connection.java +++ b/p2p/src/main/java/bisq/network/p2p/network/Connection.java @@ -242,6 +242,13 @@ public void sendMessage(NetworkEnvelope networkEnvelope) { log.debug(">> Send networkEnvelope of type: {}", networkEnvelope.getClass().getSimpleName()); if (!stopped) { + if (networkFilter != null && + peersNodeAddressOptional.isPresent() && + networkFilter.isPeerBanned(peersNodeAddressOptional.get())) { + reportInvalidRequest(RuleViolation.PEER_BANNED); + return; + } + if (noCapabilityRequiredOrCapabilityIsSupported(networkEnvelope)) { try { String peersNodeAddress = peersNodeAddressOptional.map(NodeAddress::toString).orElse("null"); @@ -740,6 +747,13 @@ public void run() { return; } + if (networkFilter != null && + peersNodeAddressOptional.isPresent() && + networkFilter.isPeerBanned(peersNodeAddressOptional.get())) { + reportInvalidRequest(RuleViolation.PEER_BANNED); + return; + } + // Throttle inbound network_messages long now = System.currentTimeMillis(); long elapsed = now - lastReadTimeStamp; @@ -843,7 +857,8 @@ && reportInvalidRequest(RuleViolation.WRONG_NETWORK_ID)) { "connection={}", proto.getCloseConnectionMessage().getReason(), this); if (CloseConnectionReason.PEER_BANNED.name().equals(proto.getCloseConnectionMessage().getReason())) { - log.warn("We got shut down because we are banned by the other peer. (InputHandler.run CloseConnectionMessage)"); + log.warn("We got shut down because we are banned by the other peer. " + + "(InputHandler.run CloseConnectionMessage). Peer: {}", getPeersNodeAddressOptional()); } shutDown(CloseConnectionReason.CLOSE_REQUESTED_BY_PEER); return; From fbfd95b5bf3d17dda40b161c0dbc4cb0feb1f15f Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Fri, 1 Jan 2021 16:24:17 -0500 Subject: [PATCH 08/31] Refactor sendMessage method: Return early --- .../bisq/network/p2p/network/Connection.java | 160 +++++++++--------- 1 file changed, 82 insertions(+), 78 deletions(-) diff --git a/p2p/src/main/java/bisq/network/p2p/network/Connection.java b/p2p/src/main/java/bisq/network/p2p/network/Connection.java index 89356ebb701..5a2508c81df 100644 --- a/p2p/src/main/java/bisq/network/p2p/network/Connection.java +++ b/p2p/src/main/java/bisq/network/p2p/network/Connection.java @@ -241,95 +241,99 @@ public Capabilities getCapabilities() { public void sendMessage(NetworkEnvelope networkEnvelope) { log.debug(">> Send networkEnvelope of type: {}", networkEnvelope.getClass().getSimpleName()); - if (!stopped) { - if (networkFilter != null && - peersNodeAddressOptional.isPresent() && - networkFilter.isPeerBanned(peersNodeAddressOptional.get())) { - reportInvalidRequest(RuleViolation.PEER_BANNED); - return; - } + if (stopped) { + log.debug("called sendMessage but was already stopped"); + return; + } - if (noCapabilityRequiredOrCapabilityIsSupported(networkEnvelope)) { - try { - String peersNodeAddress = peersNodeAddressOptional.map(NodeAddress::toString).orElse("null"); - - if (networkEnvelope instanceof PrefixedSealedAndSignedMessage && peersNodeAddressOptional.isPresent()) { - setPeerType(Connection.PeerType.DIRECT_MSG_PEER); - - log.debug("\n\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n" + - "Sending direct message to peer" + - "Write object to outputStream to peer: {} (uid={})\ntruncated message={} / size={}" + - "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n", - peersNodeAddress, uid, Utilities.toTruncatedString(networkEnvelope), -1); - } else if (networkEnvelope instanceof GetDataResponse && ((GetDataResponse) networkEnvelope).isGetUpdatedDataResponse()) { - setPeerType(Connection.PeerType.PEER); - } + if (networkFilter != null && + peersNodeAddressOptional.isPresent() && + networkFilter.isPeerBanned(peersNodeAddressOptional.get())) { + reportInvalidRequest(RuleViolation.PEER_BANNED); + return; + } - // Throttle outbound network_messages - long now = System.currentTimeMillis(); - long elapsed = now - lastSendTimeStamp; - if (elapsed < getSendMsgThrottleTrigger()) { - log.debug("We got 2 sendMessage requests in less than {} ms. We set the thread to sleep " + - "for {} ms to avoid flooding our peer. lastSendTimeStamp={}, now={}, elapsed={}, networkEnvelope={}", - getSendMsgThrottleTrigger(), getSendMsgThrottleSleep(), lastSendTimeStamp, now, elapsed, - networkEnvelope.getClass().getSimpleName()); - - // check if BundleOfEnvelopes is supported - if (getCapabilities().containsAll(new Capabilities(Capability.BUNDLE_OF_ENVELOPES))) { - synchronized (lock) { - // check if current envelope fits size - // - no? create new envelope - if (queueOfBundles.isEmpty() || queueOfBundles.element().toProtoNetworkEnvelope().getSerializedSize() + networkEnvelope.toProtoNetworkEnvelope().getSerializedSize() > MAX_PERMITTED_MESSAGE_SIZE * 0.9) { - // - no? create a bucket - queueOfBundles.add(new BundleOfEnvelopes()); - - // - and schedule it for sending - lastSendTimeStamp += getSendMsgThrottleSleep(); - - bundleSender.schedule(() -> { - if (!stopped) { - synchronized (lock) { - BundleOfEnvelopes bundle = queueOfBundles.poll(); - if (bundle != null && !stopped) { - NetworkEnvelope envelope = bundle.getEnvelopes().size() == 1 ? - bundle.getEnvelopes().get(0) : - bundle; - try { - protoOutputStream.writeEnvelope(envelope); - } catch (Throwable t) { - log.error("Sending envelope of class {} to address {} " + - "failed due {}", - envelope.getClass().getSimpleName(), - this.getPeersNodeAddressOptional(), - t.toString()); - log.error("envelope: {}", envelope); - } - } + if (!noCapabilityRequiredOrCapabilityIsSupported(networkEnvelope)) { + log.debug("Capability for networkEnvelope is required but not supported"); + return; + } + + try { + String peersNodeAddress = peersNodeAddressOptional.map(NodeAddress::toString).orElse("null"); + + if (networkEnvelope instanceof PrefixedSealedAndSignedMessage && peersNodeAddressOptional.isPresent()) { + setPeerType(Connection.PeerType.DIRECT_MSG_PEER); + + log.debug("\n\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n" + + "Sending direct message to peer" + + "Write object to outputStream to peer: {} (uid={})\ntruncated message={} / size={}" + + "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n", + peersNodeAddress, uid, Utilities.toTruncatedString(networkEnvelope), -1); + } else if (networkEnvelope instanceof GetDataResponse && ((GetDataResponse) networkEnvelope).isGetUpdatedDataResponse()) { + setPeerType(Connection.PeerType.PEER); + } + + // Throttle outbound network_messages + long now = System.currentTimeMillis(); + long elapsed = now - lastSendTimeStamp; + if (elapsed < getSendMsgThrottleTrigger()) { + log.debug("We got 2 sendMessage requests in less than {} ms. We set the thread to sleep " + + "for {} ms to avoid flooding our peer. lastSendTimeStamp={}, now={}, elapsed={}, networkEnvelope={}", + getSendMsgThrottleTrigger(), getSendMsgThrottleSleep(), lastSendTimeStamp, now, elapsed, + networkEnvelope.getClass().getSimpleName()); + + // check if BundleOfEnvelopes is supported + if (getCapabilities().containsAll(new Capabilities(Capability.BUNDLE_OF_ENVELOPES))) { + synchronized (lock) { + // check if current envelope fits size + // - no? create new envelope + if (queueOfBundles.isEmpty() || queueOfBundles.element().toProtoNetworkEnvelope().getSerializedSize() + networkEnvelope.toProtoNetworkEnvelope().getSerializedSize() > MAX_PERMITTED_MESSAGE_SIZE * 0.9) { + // - no? create a bucket + queueOfBundles.add(new BundleOfEnvelopes()); + + // - and schedule it for sending + lastSendTimeStamp += getSendMsgThrottleSleep(); + + bundleSender.schedule(() -> { + if (!stopped) { + synchronized (lock) { + BundleOfEnvelopes bundle = queueOfBundles.poll(); + if (bundle != null && !stopped) { + NetworkEnvelope envelope = bundle.getEnvelopes().size() == 1 ? + bundle.getEnvelopes().get(0) : + bundle; + try { + protoOutputStream.writeEnvelope(envelope); + } catch (Throwable t) { + log.error("Sending envelope of class {} to address {} " + + "failed due {}", + envelope.getClass().getSimpleName(), + this.getPeersNodeAddressOptional(), + t.toString()); + log.error("envelope: {}", envelope); } } - }, lastSendTimeStamp - now, TimeUnit.MILLISECONDS); + } } - - // - yes? add to bucket - queueOfBundles.element().add(networkEnvelope); - } - return; + }, lastSendTimeStamp - now, TimeUnit.MILLISECONDS); } - Thread.sleep(getSendMsgThrottleSleep()); + // - yes? add to bucket + queueOfBundles.element().add(networkEnvelope); } + return; + } - lastSendTimeStamp = now; + Thread.sleep(getSendMsgThrottleSleep()); + } - if (!stopped) { - protoOutputStream.writeEnvelope(networkEnvelope); - } - } catch (Throwable t) { - handleException(t); - } + lastSendTimeStamp = now; + + if (!stopped) { + protoOutputStream.writeEnvelope(networkEnvelope); } - } else { - log.debug("called sendMessage but was already stopped"); + } catch (Throwable t) { + handleException(t); } } From de5b69a5d7331f93b62f3f23dabab6d24aeda4fb Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Fri, 1 Jan 2021 16:27:47 -0500 Subject: [PATCH 09/31] Refactor sendMessage method: Inline debug value --- p2p/src/main/java/bisq/network/p2p/network/Connection.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/p2p/src/main/java/bisq/network/p2p/network/Connection.java b/p2p/src/main/java/bisq/network/p2p/network/Connection.java index 5a2508c81df..f6a1ed15e8b 100644 --- a/p2p/src/main/java/bisq/network/p2p/network/Connection.java +++ b/p2p/src/main/java/bisq/network/p2p/network/Connection.java @@ -259,8 +259,6 @@ public void sendMessage(NetworkEnvelope networkEnvelope) { } try { - String peersNodeAddress = peersNodeAddressOptional.map(NodeAddress::toString).orElse("null"); - if (networkEnvelope instanceof PrefixedSealedAndSignedMessage && peersNodeAddressOptional.isPresent()) { setPeerType(Connection.PeerType.DIRECT_MSG_PEER); @@ -268,7 +266,8 @@ public void sendMessage(NetworkEnvelope networkEnvelope) { "Sending direct message to peer" + "Write object to outputStream to peer: {} (uid={})\ntruncated message={} / size={}" + "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n", - peersNodeAddress, uid, Utilities.toTruncatedString(networkEnvelope), -1); + peersNodeAddressOptional.map(NodeAddress::toString).orElse("null"), + uid, Utilities.toTruncatedString(networkEnvelope), -1); } else if (networkEnvelope instanceof GetDataResponse && ((GetDataResponse) networkEnvelope).isGetUpdatedDataResponse()) { setPeerType(Connection.PeerType.PEER); } From e73b6b09fec759894ce81d68865d5debcf423c5f Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Fri, 1 Jan 2021 20:44:26 -0500 Subject: [PATCH 10/31] If we select TransferWise we switch to show all currencies as TransferWise supports sending to most currencies. --- .../main/offer/offerbook/OfferBookView.java | 14 ++++++++++---- .../main/offer/offerbook/OfferBookViewModel.java | 11 +++++++++-- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java index e1133a16b24..417fe20730d 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java @@ -324,11 +324,8 @@ protected void activate() { currencyComboBox.getSelectionModel().select(SHOW_ALL); model.onSetTradeCurrency(currencyComboBox.getSelectionModel().getSelectedItem()); }); + updateCurrencyComboBoxFromModel(); - if (model.showAllTradeCurrenciesProperty.get()) - currencyComboBox.getSelectionModel().select(SHOW_ALL); - else - currencyComboBox.getSelectionModel().select(model.getSelectedTradeCurrency()); currencyComboBox.getEditor().setText(new CurrencyStringConverter(currencyComboBox).toString(currencyComboBox.getSelectionModel().getSelectedItem())); volumeColumn.sortableProperty().bind(model.showAllTradeCurrenciesProperty.not()); @@ -359,6 +356,7 @@ protected void activate() { if (paymentMethodComboBox.getEditor().getText().isEmpty()) paymentMethodComboBox.getSelectionModel().select(SHOW_ALL); model.onSetPaymentMethod(paymentMethodComboBox.getSelectionModel().getSelectedItem()); + updateCurrencyComboBoxFromModel(); updateSigningStateColumn(); }); @@ -405,6 +403,14 @@ protected void activate() { model.priceFeedService.updateCounterProperty().addListener(priceFeedUpdateCounterListener); } + private void updateCurrencyComboBoxFromModel() { + if (model.showAllTradeCurrenciesProperty.get()) { + currencyComboBox.getSelectionModel().select(SHOW_ALL); + } else { + currencyComboBox.getSelectionModel().select(model.getSelectedTradeCurrency()); + } + } + private void updateSigningStateColumn() { if (model.hasSelectionAccountSigning()) { if (!tableView.getColumns().contains(signingStateColumn)) { diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java index 98737f4036c..72ad32fd20e 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java @@ -281,10 +281,17 @@ void onSetPaymentMethod(PaymentMethod paymentMethod) { return; showAllPaymentMethods = isShowAllEntry(paymentMethod.getId()); - if (!showAllPaymentMethods) + if (!showAllPaymentMethods) { this.selectedPaymentMethod = paymentMethod; - else + + // If we select TransferWise we switch to show all currencies as TransferWise supports + // sending to most currencies. + if (paymentMethod.getId().equals(PaymentMethod.TRANSFERWISE_ID)) { + onSetTradeCurrency(new CryptoCurrency(GUIUtil.SHOW_ALL_FLAG, "")); + } + } else { this.selectedPaymentMethod = PaymentMethod.getDummyPaymentMethod(GUIUtil.SHOW_ALL_FLAG); + } applyFilterPredicate(); } From 82644fcfd1d76e0aa2e80566ebcb01bc71f573f9 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Fri, 1 Jan 2021 21:52:34 -0500 Subject: [PATCH 11/31] Add option in preferences to hide payment methods which are not part of the users accounts. Default value is false, so same behaviour as before the change. If no payment account is setup then we also show all payment methods. In that case (no payment account) we disable the toggle as well as set it to false. --- .../main/java/bisq/core/user/Preferences.java | 7 +++++ .../bisq/core/user/PreferencesPayload.java | 8 ++++-- .../resources/i18n/displayStrings.properties | 1 + .../offer/offerbook/OfferBookViewModel.java | 9 +++++++ .../settings/preferences/PreferencesView.java | 26 ++++++++++++++++--- proto/src/main/proto/pb.proto | 1 + 6 files changed, 46 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/bisq/core/user/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java index 4dd43b85d2a..c07a912cb5b 100644 --- a/core/src/main/java/bisq/core/user/Preferences.java +++ b/core/src/main/java/bisq/core/user/Preferences.java @@ -487,6 +487,11 @@ public void setAutoConfTradeLimit(String currencyCode, long tradeLimit) { }); } + public void setHideNonAccountPaymentMethods(boolean hideNonAccountPaymentMethods) { + prefPayload.setHideNonAccountPaymentMethods(hideNonAccountPaymentMethods); + requestPersistence(); + } + private void requestPersistence() { if (initialReadDone) persistenceManager.requestPersistence(); @@ -1074,5 +1079,7 @@ private interface ExcludesDelegateMethods { void setBsqAverageTrimThreshold(double bsqAverageTrimThreshold); void setAutoConfirmSettings(AutoConfirmSettings autoConfirmSettings); + + void setHideNonAccountPaymentMethods(boolean hideNonAccountPaymentMethods); } } diff --git a/core/src/main/java/bisq/core/user/PreferencesPayload.java b/core/src/main/java/bisq/core/user/PreferencesPayload.java index 0a7aede7e36..82b800f13eb 100644 --- a/core/src/main/java/bisq/core/user/PreferencesPayload.java +++ b/core/src/main/java/bisq/core/user/PreferencesPayload.java @@ -129,6 +129,8 @@ public final class PreferencesPayload implements PersistableEnvelope { // Added at 1.3.8 private List autoConfirmSettingsList = new ArrayList<>(); + // Added in 1.5.5 + private boolean hideNonAccountPaymentMethods; /////////////////////////////////////////////////////////////////////////////////////////// // Constructor @@ -192,7 +194,8 @@ public Message toProtoMessage() { .setBsqAverageTrimThreshold(bsqAverageTrimThreshold) .addAllAutoConfirmSettings(autoConfirmSettingsList.stream() .map(autoConfirmSettings -> ((protobuf.AutoConfirmSettings) autoConfirmSettings.toProtoMessage())) - .collect(Collectors.toList())); + .collect(Collectors.toList())) + .setHideNonAccountPaymentMethods(hideNonAccountPaymentMethods); Optional.ofNullable(backupDirectory).ifPresent(builder::setBackupDirectory); Optional.ofNullable(preferredTradeCurrency).ifPresent(e -> builder.setPreferredTradeCurrency((protobuf.TradeCurrency) e.toProtoMessage())); @@ -286,7 +289,8 @@ public static PreferencesPayload fromProto(protobuf.PreferencesPayload proto, Co proto.getAutoConfirmSettingsList().isEmpty() ? new ArrayList<>() : new ArrayList<>(proto.getAutoConfirmSettingsList().stream() .map(AutoConfirmSettings::fromProto) - .collect(Collectors.toList())) + .collect(Collectors.toList())), + proto.getHideNonAccountPaymentMethods() ); } } diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index e7d5912e1c9..a2c34c5fad9 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -1219,6 +1219,7 @@ setting.preferences.showOwnOffers=Show my own offers in offer book setting.preferences.useAnimations=Use animations setting.preferences.useDarkMode=Use dark mode setting.preferences.sortWithNumOffers=Sort market lists with no. of offers/trades +setting.preferences.onlyShowPaymentMethodsFromAccount=Hide non-supported payment methods setting.preferences.resetAllFlags=Reset all \"Don't show again\" flags settings.preferences.languageChange=To apply the language change to all screens requires a restart. settings.preferences.supportLanguageWarning=In case of a dispute, please note that mediation is handled in {0} and arbitration in {1}. diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java index 72ad32fd20e..92b97e1e29b 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java @@ -87,6 +87,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; @@ -338,6 +339,14 @@ TradeCurrency getSelectedTradeCurrency() { ObservableList getPaymentMethods() { ObservableList list = FXCollections.observableArrayList(PaymentMethod.getPaymentMethods()); + if (preferences.isHideNonAccountPaymentMethods() && user.getPaymentAccounts() != null) { + Set supportedPaymentMethods = user.getPaymentAccounts().stream() + .map(PaymentAccount::getPaymentMethod).collect(Collectors.toSet()); + if (!supportedPaymentMethods.isEmpty()) { + list = FXCollections.observableArrayList(supportedPaymentMethods); + } + } + list.sort(Comparator.naturalOrder()); list.add(0, PaymentMethod.getDummyPaymentMethod(GUIUtil.SHOW_ALL_FLAG)); return list; diff --git a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java index 8d000b3139b..79870291161 100644 --- a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java +++ b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java @@ -45,8 +45,11 @@ import bisq.core.locale.LanguageUtil; import bisq.core.locale.Res; import bisq.core.locale.TradeCurrency; +import bisq.core.payment.PaymentAccount; +import bisq.core.payment.payload.PaymentMethod; import bisq.core.provider.fee.FeeService; import bisq.core.user.Preferences; +import bisq.core.user.User; import bisq.core.util.FormattingUtils; import bisq.core.util.ParsingUtils; import bisq.core.util.coin.CoinFormatter; @@ -100,13 +103,16 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import static bisq.desktop.util.FormBuilder.*; import static com.google.common.base.Preconditions.checkArgument; @FxmlView public class PreferencesView extends ActivatableViewAndModel { + private final User user; private final CoinFormatter formatter; private TextField btcExplorerTextField, bsqExplorerTextField; private ComboBox userLanguageComboBox; @@ -114,7 +120,7 @@ public class PreferencesView extends ActivatableViewAndModel preferredTradeCurrencyComboBox; private ToggleButton showOwnOffersInOfferBook, useAnimations, useDarkMode, sortMarketCurrenciesNumerically, - avoidStandbyMode, useCustomFee, autoConfirmXmrToggle; + avoidStandbyMode, useCustomFee, autoConfirmXmrToggle, hideNonAccountPaymentMethodsToggle; private int gridRow = 0; private int displayCurrenciesGridRowIndex = 0; private InputTextField transactionFeeInputTextField, ignoreTradersListInputTextField, ignoreDustThresholdInputTextField, @@ -171,12 +177,14 @@ public PreferencesView(PreferencesViewModel model, FilterManager filterManager, DaoFacade daoFacade, Config config, + User user, @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter, @Named(Config.RPC_USER) String rpcUser, @Named(Config.RPC_PASSWORD) String rpcPassword, @Named(Config.RPC_BLOCK_NOTIFICATION_PORT) int rpcBlockNotificationPort, @Named(Config.STORAGE_DIR) File storageDir) { super(model); + this.user = user; this.formatter = formatter; this.preferences = preferences; this.feeService = feeService; @@ -595,14 +603,14 @@ public CryptoCurrency fromString(String s) { } private void initializeDisplayOptions() { - TitledGroupBg titledGroupBg = addTitledGroupBg(root, ++gridRow, 5, Res.get("setting.preferences.displayOptions"), Layout.GROUP_DISTANCE); + TitledGroupBg titledGroupBg = addTitledGroupBg(root, ++gridRow, 6, Res.get("setting.preferences.displayOptions"), Layout.GROUP_DISTANCE); GridPane.setColumnSpan(titledGroupBg, 1); showOwnOffersInOfferBook = addSlideToggleButton(root, gridRow, Res.get("setting.preferences.showOwnOffers"), Layout.FIRST_ROW_AND_GROUP_DISTANCE); useAnimations = addSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.useAnimations")); useDarkMode = addSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.useDarkMode")); - // useStickyMarketPriceCheckBox = addLabelCheckBox(root, ++gridRow, "Use sticky market price:", "").second; sortMarketCurrenciesNumerically = addSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.sortWithNumOffers")); + hideNonAccountPaymentMethodsToggle = addSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.onlyShowPaymentMethodsFromAccount")); resetDontShowAgainButton = addButton(root, ++gridRow, Res.get("setting.preferences.resetAllFlags"), 0); resetDontShowAgainButton.getStyleClass().add("compact-button"); resetDontShowAgainButton.setMaxWidth(Double.MAX_VALUE); @@ -932,6 +940,16 @@ private void activateDisplayPreferences() { sortMarketCurrenciesNumerically.setSelected(preferences.isSortMarketCurrenciesNumerically()); sortMarketCurrenciesNumerically.setOnAction(e -> preferences.setSortMarketCurrenciesNumerically(sortMarketCurrenciesNumerically.isSelected())); + boolean disableToggle = false; + if (user.getPaymentAccounts() != null) { + Set supportedPaymentMethods = user.getPaymentAccounts().stream() + .map(PaymentAccount::getPaymentMethod).collect(Collectors.toSet()); + disableToggle = supportedPaymentMethods.isEmpty(); + } + hideNonAccountPaymentMethodsToggle.setSelected(preferences.isHideNonAccountPaymentMethods() && !disableToggle); + hideNonAccountPaymentMethodsToggle.setOnAction(e -> preferences.setHideNonAccountPaymentMethods(hideNonAccountPaymentMethodsToggle.isSelected())); + hideNonAccountPaymentMethodsToggle.setDisable(disableToggle); + resetDontShowAgainButton.setOnAction(e -> preferences.resetDontShowAgain()); editCustomBtcExplorer.setOnAction(e -> { @@ -1108,8 +1126,8 @@ private void deactivateDisplayCurrencies() { private void deactivateDisplayPreferences() { useAnimations.setOnAction(null); useDarkMode.setOnAction(null); - // useStickyMarketPriceCheckBox.setOnAction(null); sortMarketCurrenciesNumerically.setOnAction(null); + hideNonAccountPaymentMethodsToggle.setOnAction(null); showOwnOffersInOfferBook.setOnAction(null); resetDontShowAgainButton.setOnAction(null); if (displayStandbyModeFeature) { diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 86d7a778b27..90633ee502b 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -1614,6 +1614,7 @@ message PreferencesPayload { bool tac_accepted_v120 = 55; repeated AutoConfirmSettings auto_confirm_settings = 56; double bsq_average_trim_threshold = 57; + bool hide_non_account_payment_methods = 58; } message AutoConfirmSettings { From 039d8fb2d9b505c903bd2e13a2a1c49eec3a4f43 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Fri, 1 Jan 2021 22:10:39 -0500 Subject: [PATCH 12/31] Extract methods for show all and edit entries. Use isShowAllEntry methods instead of equals checks --- .../offer/offerbook/OfferBookViewModel.java | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java index 92b97e1e29b..0a20891ea0c 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java @@ -122,7 +122,7 @@ class OfferBookViewModel extends ActivatableViewModel { // If id is empty string we ignore filter (display all methods) - PaymentMethod selectedPaymentMethod = PaymentMethod.getDummyPaymentMethod(GUIUtil.SHOW_ALL_FLAG); + PaymentMethod selectedPaymentMethod = getShowAllEntryForPaymentMethod(); private boolean isTabSelected; final BooleanProperty showAllTradeCurrenciesProperty = new SimpleBooleanProperty(true); @@ -213,7 +213,7 @@ protected void activate() { filteredItems.addListener(filterItemsListener); String code = direction == OfferPayload.Direction.BUY ? preferences.getBuyScreenCurrencyCode() : preferences.getSellScreenCurrencyCode(); - if (code != null && !code.equals(GUIUtil.SHOW_ALL_FLAG) && !code.isEmpty() && + if (code != null && !code.isEmpty() && !isShowAllEntry(code) && CurrencyUtil.getTradeCurrency(code).isPresent()) { showAllTradeCurrenciesProperty.set(false); selectedTradeCurrency = CurrencyUtil.getTradeCurrency(code).get(); @@ -288,10 +288,10 @@ void onSetPaymentMethod(PaymentMethod paymentMethod) { // If we select TransferWise we switch to show all currencies as TransferWise supports // sending to most currencies. if (paymentMethod.getId().equals(PaymentMethod.TRANSFERWISE_ID)) { - onSetTradeCurrency(new CryptoCurrency(GUIUtil.SHOW_ALL_FLAG, "")); + onSetTradeCurrency(getShowAllEntryForCurrency()); } } else { - this.selectedPaymentMethod = PaymentMethod.getDummyPaymentMethod(GUIUtil.SHOW_ALL_FLAG); + this.selectedPaymentMethod = getShowAllEntryForPaymentMethod(); } applyFilterPredicate(); @@ -348,7 +348,7 @@ ObservableList getPaymentMethods() { } list.sort(Comparator.naturalOrder()); - list.add(0, PaymentMethod.getDummyPaymentMethod(GUIUtil.SHOW_ALL_FLAG)); + list.add(0, getShowAllEntryForPaymentMethod()); return list; } @@ -528,11 +528,12 @@ private void setMarketPriceFeedCurrency() { private void fillAllTradeCurrencies() { allTradeCurrencies.clear(); // Used for ignoring filter (show all) - allTradeCurrencies.add(new CryptoCurrency(GUIUtil.SHOW_ALL_FLAG, "")); + allTradeCurrencies.add(getShowAllEntryForCurrency()); allTradeCurrencies.addAll(preferences.getTradeCurrenciesAsObservable()); - allTradeCurrencies.add(new CryptoCurrency(GUIUtil.EDIT_FLAG, "")); + allTradeCurrencies.add(getEditEntryForCurrency()); } + /////////////////////////////////////////////////////////////////////////////////////////// // Checks /////////////////////////////////////////////////////////////////////////////////////////// @@ -645,11 +646,11 @@ int getNumTrades(Offer offer) { public boolean hasSelectionAccountSigning() { if (showAllTradeCurrenciesProperty.get()) { - if (!selectedPaymentMethod.getId().equals(GUIUtil.SHOW_ALL_FLAG)) { + if (!isShowAllEntry(selectedPaymentMethod.getId())) { return PaymentMethod.hasChargebackRisk(selectedPaymentMethod); } } else { - if (selectedPaymentMethod.getId().equals(GUIUtil.SHOW_ALL_FLAG)) + if (isShowAllEntry(selectedPaymentMethod.getId())) return CurrencyUtil.getMatureMarketCurrencies().stream() .anyMatch(c -> c.getCode().equals(selectedTradeCurrency.getCode())); else @@ -675,4 +676,16 @@ public String formatDepositString(Coin deposit, long amount) { var percentage = FormattingUtils.formatToRoundedPercentWithSymbol(deposit.getValue() / (double) amount); return btcFormatter.formatCoin(deposit) + " (" + percentage + ")"; } + + private TradeCurrency getShowAllEntryForCurrency() { + return new CryptoCurrency(GUIUtil.SHOW_ALL_FLAG, ""); + } + + private TradeCurrency getEditEntryForCurrency() { + return new CryptoCurrency(GUIUtil.EDIT_FLAG, ""); + } + + private PaymentMethod getShowAllEntryForPaymentMethod() { + return PaymentMethod.getDummyPaymentMethod(GUIUtil.SHOW_ALL_FLAG); + } } From 645594f3e26b7fa3285696cab060305087a4e7c8 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sat, 2 Jan 2021 18:36:56 -0500 Subject: [PATCH 13/31] Add toggle for filtering offers which can be taken with users accounts --- .../main/java/bisq/core/user/Preferences.java | 7 ++ .../bisq/core/user/PreferencesPayload.java | 8 +- .../resources/i18n/displayStrings.properties | 1 + .../main/offer/offerbook/OfferBookView.java | 41 ++++--- .../offer/offerbook/OfferBookViewModel.java | 100 ++++++++++++++++-- proto/src/main/proto/pb.proto | 1 + 6 files changed, 134 insertions(+), 24 deletions(-) diff --git a/core/src/main/java/bisq/core/user/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java index c07a912cb5b..c7080614830 100644 --- a/core/src/main/java/bisq/core/user/Preferences.java +++ b/core/src/main/java/bisq/core/user/Preferences.java @@ -772,6 +772,11 @@ public void setIgnoreDustThreshold(int value) { requestPersistence(); } + public void setShowOffersMatchingMyAccounts(boolean value) { + prefPayload.setShowOffersMatchingMyAccounts(value); + requestPersistence(); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Getter @@ -1081,5 +1086,7 @@ private interface ExcludesDelegateMethods { void setAutoConfirmSettings(AutoConfirmSettings autoConfirmSettings); void setHideNonAccountPaymentMethods(boolean hideNonAccountPaymentMethods); + + void setShowOffersMatchingMyAccounts(boolean value); } } diff --git a/core/src/main/java/bisq/core/user/PreferencesPayload.java b/core/src/main/java/bisq/core/user/PreferencesPayload.java index 82b800f13eb..03628791f36 100644 --- a/core/src/main/java/bisq/core/user/PreferencesPayload.java +++ b/core/src/main/java/bisq/core/user/PreferencesPayload.java @@ -131,6 +131,8 @@ public final class PreferencesPayload implements PersistableEnvelope { // Added in 1.5.5 private boolean hideNonAccountPaymentMethods; + private boolean showOffersMatchingMyAccounts; + /////////////////////////////////////////////////////////////////////////////////////////// // Constructor @@ -195,7 +197,8 @@ public Message toProtoMessage() { .addAllAutoConfirmSettings(autoConfirmSettingsList.stream() .map(autoConfirmSettings -> ((protobuf.AutoConfirmSettings) autoConfirmSettings.toProtoMessage())) .collect(Collectors.toList())) - .setHideNonAccountPaymentMethods(hideNonAccountPaymentMethods); + .setHideNonAccountPaymentMethods(hideNonAccountPaymentMethods) + .setShowOffersMatchingMyAccounts(showOffersMatchingMyAccounts); Optional.ofNullable(backupDirectory).ifPresent(builder::setBackupDirectory); Optional.ofNullable(preferredTradeCurrency).ifPresent(e -> builder.setPreferredTradeCurrency((protobuf.TradeCurrency) e.toProtoMessage())); @@ -290,7 +293,8 @@ public static PreferencesPayload fromProto(protobuf.PreferencesPayload proto, Co new ArrayList<>(proto.getAutoConfirmSettingsList().stream() .map(AutoConfirmSettings::fromProto) .collect(Collectors.toList())), - proto.getHideNonAccountPaymentMethods() + proto.getHideNonAccountPaymentMethods(), + proto.getShowOffersMatchingMyAccounts() ); } } diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index a2c34c5fad9..ed0e92ed395 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -340,6 +340,7 @@ offerbook.offerersAcceptedBankSeats=Accepted seat of bank countries (taker):\n { offerbook.availableOffers=Available offers offerbook.filterByCurrency=Filter by currency offerbook.filterByPaymentMethod=Filter by payment method +offerbook.matchingOffers=Offers matching my accounts offerbook.timeSinceSigning=Account info offerbook.timeSinceSigning.info=This account was verified and {0} offerbook.timeSinceSigning.info.arbitrator=signed by an arbitrator and can sign peer accounts diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java index 417fe20730d..6c3fa3d6c24 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java @@ -22,6 +22,7 @@ import bisq.desktop.common.view.FxmlView; import bisq.desktop.components.AutoTooltipButton; import bisq.desktop.components.AutoTooltipLabel; +import bisq.desktop.components.AutoTooltipSlideToggleButton; import bisq.desktop.components.AutoTooltipTableColumn; import bisq.desktop.components.AutocompleteComboBox; import bisq.desktop.components.ColoredDecimalPlacesWithZerosText; @@ -126,6 +127,7 @@ public class OfferBookView extends ActivatableViewAndModel currencyComboBox; private AutocompleteComboBox paymentMethodComboBox; private AutoTooltipButton createOfferButton; + private AutoTooltipSlideToggleButton matchingOffersToggle; private AutoTooltipTableColumn amountColumn, volumeColumn, marketColumn, priceColumn, paymentMethodColumn, depositColumn, signingStateColumn, avatarColumn; private TableView tableView; @@ -174,10 +176,25 @@ public void initialize() { hBox.setSpacing(35); hBox.setPadding(new Insets(10, 0, 0, 0)); - final Tuple3> currencyBoxTuple = FormBuilder.addTopLabelAutocompleteComboBox( + Tuple3> currencyBoxTuple = FormBuilder.addTopLabelAutocompleteComboBox( Res.get("offerbook.filterByCurrency")); - final Tuple3> paymentBoxTuple = FormBuilder.addTopLabelAutocompleteComboBox( + currencyComboBox = currencyBoxTuple.third; + currencyComboBox.setPrefWidth(270); + + Tuple3> paymentBoxTuple = FormBuilder.addTopLabelAutocompleteComboBox( Res.get("offerbook.filterByPaymentMethod")); + paymentMethodComboBox = paymentBoxTuple.third; + paymentMethodComboBox.setCellFactory(GUIUtil.getPaymentMethodCellFactory()); + paymentMethodComboBox.setPrefWidth(270); + + matchingOffersToggle = new AutoTooltipSlideToggleButton(); + matchingOffersToggle.setText(Res.get("offerbook.matchingOffers")); + HBox.setMargin(matchingOffersToggle, new Insets(7, 0, -9, -15)); + + hBox.getChildren().addAll(currencyBoxTuple.first, paymentBoxTuple.first, matchingOffersToggle); + AnchorPane.setLeftAnchor(hBox, 0d); + AnchorPane.setTopAnchor(hBox, 0d); + AnchorPane.setBottomAnchor(hBox, 0d); createOfferButton = new AutoTooltipButton(); createOfferButton.setMinHeight(40); @@ -185,11 +202,6 @@ public void initialize() { AnchorPane.setRightAnchor(createOfferButton, 0d); AnchorPane.setBottomAnchor(createOfferButton, 0d); - hBox.getChildren().addAll(currencyBoxTuple.first, paymentBoxTuple.first, createOfferButton); - AnchorPane.setLeftAnchor(hBox, 0d); - AnchorPane.setTopAnchor(hBox, 0d); - AnchorPane.setBottomAnchor(hBox, 0d); - AnchorPane anchorPane = new AnchorPane(); anchorPane.getChildren().addAll(hBox, createOfferButton); @@ -199,11 +211,6 @@ public void initialize() { GridPane.setMargin(anchorPane, new Insets(Layout.FIRST_ROW_DISTANCE, 0, 0, 0)); root.getChildren().add(anchorPane); - currencyComboBox = currencyBoxTuple.third; - - paymentMethodComboBox = paymentBoxTuple.third; - paymentMethodComboBox.setCellFactory(GUIUtil.getPaymentMethodCellFactory()); - tableView = new TableView<>(); GridPane.setRowIndex(tableView, ++gridRow); @@ -328,6 +335,10 @@ protected void activate() { currencyComboBox.getEditor().setText(new CurrencyStringConverter(currencyComboBox).toString(currencyComboBox.getSelectionModel().getSelectedItem())); + matchingOffersToggle.setSelected(model.useOffersMatchingMyAccountsFilter); + matchingOffersToggle.disableProperty().bind(model.disableMatchToggle); + matchingOffersToggle.setOnAction(e -> model.onShowOffersMatchingMyAccounts(matchingOffersToggle.isSelected())); + volumeColumn.sortableProperty().bind(model.showAllTradeCurrenciesProperty.not()); model.getOfferList().comparatorProperty().bind(tableView.comparatorProperty()); @@ -424,6 +435,8 @@ private void updateSigningStateColumn() { @Override protected void deactivate() { createOfferButton.setOnAction(null); + matchingOffersToggle.setOnAction(null); + matchingOffersToggle.disableProperty().unbind(); model.getOfferList().comparatorProperty().unbind(); volumeColumn.sortableProperty().unbind(); @@ -1024,6 +1037,10 @@ public void updateItem(final OfferBookListItem item, boolean empty) { final Offer offer = item.getOffer(); boolean myOffer = model.isMyOffer(offer); if (tableRow != null) { + // this code is duplicated in model.getOffersMatchingMyAccountsPredicate but as + // we want to pass the results for displaying relevant info in popups we + // cannot simply replace it with the predicate. If there are any changes we + // need to maintain both. isPaymentAccountValidForOffer = model.isAnyPaymentAccountValidForOffer(offer); isInsufficientCounterpartyTradeLimit = model.isInsufficientCounterpartyTradeLimit(offer); hasSameProtocolVersion = model.hasSameProtocolVersion(offer); diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java index 0a20891ea0c..0e9f42b738d 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java @@ -78,16 +78,19 @@ import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; +import javafx.collections.SetChangeListener; import javafx.collections.transformation.FilteredList; import javafx.collections.transformation.SortedList; import java.text.DecimalFormat; import java.util.Comparator; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.function.Predicate; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; @@ -126,11 +129,15 @@ class OfferBookViewModel extends ActivatableViewModel { private boolean isTabSelected; final BooleanProperty showAllTradeCurrenciesProperty = new SimpleBooleanProperty(true); + final BooleanProperty disableMatchToggle = new SimpleBooleanProperty(); final IntegerProperty maxPlacesForAmount = new SimpleIntegerProperty(); final IntegerProperty maxPlacesForVolume = new SimpleIntegerProperty(); final IntegerProperty maxPlacesForPrice = new SimpleIntegerProperty(); final IntegerProperty maxPlacesForMarketPriceMargin = new SimpleIntegerProperty(); boolean showAllPaymentMethods = true; + boolean useOffersMatchingMyAccountsFilter; + private final Map myInsufficientTradeLimitCache = new HashMap<>(); + private final Map insufficientCounterpartyTradeLimitCache = new HashMap<>(); /////////////////////////////////////////////////////////////////////////////////////////// @@ -206,6 +213,10 @@ public OfferBookViewModel(User user, highestMarketPriceMarginOffer.ifPresent(offerBookListItem -> maxPlacesForMarketPriceMargin.set(formatMarketPriceMargin(offerBookListItem.getOffer(), false).length())); }; + + // If our accounts have changed we reset our myInsufficientTradeLimitCache as it depends on account data + user.getPaymentAccountsAsObservable().addListener((SetChangeListener) c -> + myInsufficientTradeLimitCache.clear()); } @Override @@ -223,10 +234,13 @@ protected void activate() { } tradeCurrencyCode.set(selectedTradeCurrency.getCode()); + disableMatchToggle.set(user.getPaymentAccounts() == null || user.getPaymentAccounts().isEmpty()); + useOffersMatchingMyAccountsFilter = !disableMatchToggle.get() && isShowOffersMatchingMyAccounts(); + fillAllTradeCurrencies(); preferences.getTradeCurrenciesAsObservable().addListener(tradeCurrencyListChangeListener); offerBook.fillOfferBookListItems(); - applyFilterPredicate(); + filterOffers(); setMarketPriceFeedCurrency(); priceUtil.recalculateBsq30DayAveragePrice(); @@ -238,6 +252,7 @@ protected void deactivate() { preferences.getTradeCurrenciesAsObservable().removeListener(tradeCurrencyListChangeListener); } + /////////////////////////////////////////////////////////////////////////////////////////// // API /////////////////////////////////////////////////////////////////////////////////////////// @@ -268,7 +283,7 @@ else if (!showAllEntry) { } setMarketPriceFeedCurrency(); - applyFilterPredicate(); + filterOffers(); if (direction == OfferPayload.Direction.BUY) preferences.setBuyScreenCurrencyCode(code); @@ -294,17 +309,28 @@ void onSetPaymentMethod(PaymentMethod paymentMethod) { this.selectedPaymentMethod = getShowAllEntryForPaymentMethod(); } - applyFilterPredicate(); + filterOffers(); } void onRemoveOpenOffer(Offer offer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { openOfferManager.removeOffer(offer, resultHandler, errorMessageHandler); } + void onShowOffersMatchingMyAccounts(boolean isSelected) { + useOffersMatchingMyAccountsFilter = isSelected; + preferences.setShowOffersMatchingMyAccounts(useOffersMatchingMyAccountsFilter); + filterOffers(); + } + + /////////////////////////////////////////////////////////////////////////////////////////// // Getters /////////////////////////////////////////////////////////////////////////////////////////// + boolean isShowOffersMatchingMyAccounts() { + return preferences.isShowOffersMatchingMyAccounts(); + } + SortedList getOfferList() { return sortedItems; } @@ -561,8 +587,15 @@ boolean canCreateOrTakeOffer() { // Filters /////////////////////////////////////////////////////////////////////////////////////////// - private void applyFilterPredicate() { - filteredItems.setPredicate(offerBookListItem -> { + private void filterOffers() { + Predicate predicate = useOffersMatchingMyAccountsFilter ? + getCurrencyAndMethodPredicate().and(getOffersMatchingMyAccountsPredicate()) : + getCurrencyAndMethodPredicate(); + filteredItems.setPredicate(predicate); + } + + private Predicate getCurrencyAndMethodPredicate() { + return offerBookListItem -> { Offer offer = offerBookListItem.getOffer(); boolean directionResult = offer.getDirection() != direction; boolean currencyResult = (showAllTradeCurrenciesProperty.get()) || @@ -571,7 +604,37 @@ private void applyFilterPredicate() { offer.getPaymentMethod().equals(selectedPaymentMethod); boolean notMyOfferOrShowMyOffersActivated = !isMyOffer(offerBookListItem.getOffer()) || preferences.isShowOwnOffersInOfferBook(); return directionResult && currencyResult && paymentMethodResult && notMyOfferOrShowMyOffersActivated; - }); + }; + } + + private Predicate getOffersMatchingMyAccountsPredicate() { + // This code duplicates code in the view at the button column. We need there the different results for + // display in popups so we cannot replace that with the predicate. Any change need to be applied in both + // places. + return offerBookListItem -> { + Offer offer = offerBookListItem.getOffer(); + boolean isPaymentAccountValidForOffer = isAnyPaymentAccountValidForOffer(offer); + boolean isInsufficientCounterpartyTradeLimit = isInsufficientCounterpartyTradeLimit(offer); + boolean hasSameProtocolVersion = hasSameProtocolVersion(offer); + boolean isIgnored = isIgnored(offer); + boolean isOfferBanned = isOfferBanned(offer); + boolean isCurrencyBanned = isCurrencyBanned(offer); + boolean isPaymentMethodBanned = isPaymentMethodBanned(offer); + boolean isNodeAddressBanned = isNodeAddressBanned(offer); + boolean requireUpdateToNewVersion = requireUpdateToNewVersion(); + boolean isMyInsufficientTradeLimit = isMyInsufficientTradeLimit(offer); + boolean isTradable = isPaymentAccountValidForOffer && + !isInsufficientCounterpartyTradeLimit && + hasSameProtocolVersion && + !isIgnored && + !isOfferBanned && + !isCurrencyBanned && + !isPaymentMethodBanned && + !isNodeAddressBanned && + !requireUpdateToNewVersion && + !isMyInsufficientTradeLimit; + return isTradable; + }; } boolean isIgnored(Offer offer) { @@ -599,13 +662,28 @@ boolean requireUpdateToNewVersion() { return filterManager.requireUpdateToNewVersionForTrading(); } + // This call is a bit expensive so we cache results boolean isInsufficientCounterpartyTradeLimit(Offer offer) { - return CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()) && - !accountAgeWitnessService.verifyPeersTradeAmount(offer, offer.getAmount(), errorMessage -> { - }); + String offerId = offer.getId(); + if (insufficientCounterpartyTradeLimitCache.containsKey(offerId)) { + return insufficientCounterpartyTradeLimitCache.get(offerId); + } + + boolean result = CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()) && + !accountAgeWitnessService.verifyPeersTradeAmount(offer, offer.getAmount(), + errorMessage -> { + }); + insufficientCounterpartyTradeLimitCache.put(offerId, result); + return result; } + // This call is a bit expensive so we cache results boolean isMyInsufficientTradeLimit(Offer offer) { + String offerId = offer.getId(); + if (myInsufficientTradeLimitCache.containsKey(offerId)) { + return myInsufficientTradeLimitCache.get(offerId); + } + Optional accountOptional = getMostMaturePaymentAccountForOffer(offer); long myTradeLimit = accountOptional .map(paymentAccount -> accountAgeWitnessService.getMyTradeLimit(paymentAccount, @@ -616,9 +694,11 @@ boolean isMyInsufficientTradeLimit(Offer offer) { accountOptional.isPresent() ? accountOptional.get().getAccountName() : "null", Coin.valueOf(myTradeLimit).toFriendlyString(), Coin.valueOf(offerMinAmount).toFriendlyString()); - return CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()) && + boolean result = CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()) && accountOptional.isPresent() && myTradeLimit < offerMinAmount; + myInsufficientTradeLimitCache.put(offerId, result); + return result; } boolean hasSameProtocolVersion(Offer offer) { diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 90633ee502b..e207fcf8552 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -1615,6 +1615,7 @@ message PreferencesPayload { repeated AutoConfirmSettings auto_confirm_settings = 56; double bsq_average_trim_threshold = 57; bool hide_non_account_payment_methods = 58; + bool show_offers_matching_my_accounts = 59; } message AutoConfirmSettings { From b0f54d4453d34fd0a24ae6ecbbf1395b901a9e9c Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sat, 2 Jan 2021 19:22:29 -0500 Subject: [PATCH 14/31] Add null checks --- .../main/offer/offerbook/OfferBookViewModel.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java index 0e9f42b738d..2a69f863fca 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java @@ -215,8 +215,10 @@ public OfferBookViewModel(User user, }; // If our accounts have changed we reset our myInsufficientTradeLimitCache as it depends on account data - user.getPaymentAccountsAsObservable().addListener((SetChangeListener) c -> - myInsufficientTradeLimitCache.clear()); + if (user != null) { + user.getPaymentAccountsAsObservable().addListener((SetChangeListener) c -> + myInsufficientTradeLimitCache.clear()); + } } @Override @@ -234,7 +236,9 @@ protected void activate() { } tradeCurrencyCode.set(selectedTradeCurrency.getCode()); - disableMatchToggle.set(user.getPaymentAccounts() == null || user.getPaymentAccounts().isEmpty()); + if (user != null) { + disableMatchToggle.set(user.getPaymentAccounts() == null || user.getPaymentAccounts().isEmpty()); + } useOffersMatchingMyAccountsFilter = !disableMatchToggle.get() && isShowOffersMatchingMyAccounts(); fillAllTradeCurrencies(); From 9a47a83b6ce51f8911b3a86707982b7a652fffc1 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 3 Jan 2021 13:40:54 -0500 Subject: [PATCH 15/31] Add denyApiTaker entry to extra field map. We cannot add a field to OfferPayload as we use the hash for signing and any change would break compatibility between diff. versions. We also cannot rename one of the un-used fields as we use json for creating the contract hash and that would fail in the trade then. --- core/src/main/java/bisq/core/offer/Offer.java | 14 ++++++++++++++ .../main/java/bisq/core/offer/OfferPayload.java | 1 + 2 files changed, 15 insertions(+) diff --git a/core/src/main/java/bisq/core/offer/Offer.java b/core/src/main/java/bisq/core/offer/Offer.java index 963009a0d2b..038919d6be4 100644 --- a/core/src/main/java/bisq/core/offer/Offer.java +++ b/core/src/main/java/bisq/core/offer/Offer.java @@ -372,6 +372,20 @@ public String getF2FExtraInfo() { return ""; } + public boolean getDenyApiTaker() { + if (getExtraDataMap() != null && getExtraDataMap().containsKey(OfferPayload.DENY_API_TAKER)) { + return getExtraDataMap().get(OfferPayload.DENY_API_TAKER).equals("1"); + } else { + return false; + } + } + + public void setDenyApiTaker(boolean value) { + if (getExtraDataMap() != null) { + getExtraDataMap().put(OfferPayload.DENY_API_TAKER, value ? "1" : "0"); + } + } + public String getPaymentMethodNameWithCountryCode() { String method = this.getPaymentMethod().getShortName(); String methodCountryCode = this.getCountryCode(); diff --git a/core/src/main/java/bisq/core/offer/OfferPayload.java b/core/src/main/java/bisq/core/offer/OfferPayload.java index 88b1690c5f1..154d9d85032 100644 --- a/core/src/main/java/bisq/core/offer/OfferPayload.java +++ b/core/src/main/java/bisq/core/offer/OfferPayload.java @@ -88,6 +88,7 @@ public static protobuf.OfferPayload.Direction toProtoMessage(Direction direction // If maker is seller and has xmrAutoConf enabled it is set to "1" otherwise it is not set public static final String XMR_AUTO_CONF = "xmrAutoConf"; public static final String XMR_AUTO_CONF_ENABLED_VALUE = "1"; + public static final String DENY_API_TAKER = "denyApiTaker"; // Use boolean value as "0"/"1" /////////////////////////////////////////////////////////////////////////////////////////// From 7a8e863e26a3523384ce5eacdf03bf8656c66789 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 3 Jan 2021 14:46:09 -0500 Subject: [PATCH 16/31] Add denyApiTaker to Preferences --- core/src/main/java/bisq/core/user/Preferences.java | 7 +++++++ core/src/main/java/bisq/core/user/PreferencesPayload.java | 7 +++++-- proto/src/main/proto/pb.proto | 1 + 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/bisq/core/user/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java index c7080614830..ee276d0ef03 100644 --- a/core/src/main/java/bisq/core/user/Preferences.java +++ b/core/src/main/java/bisq/core/user/Preferences.java @@ -777,6 +777,11 @@ public void setShowOffersMatchingMyAccounts(boolean value) { requestPersistence(); } + public void setDenyApiTaker(boolean value) { + prefPayload.setDenyApiTaker(value); + requestPersistence(); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Getter @@ -1088,5 +1093,7 @@ private interface ExcludesDelegateMethods { void setHideNonAccountPaymentMethods(boolean hideNonAccountPaymentMethods); void setShowOffersMatchingMyAccounts(boolean value); + + void setDenyApiTaker(boolean value); } } diff --git a/core/src/main/java/bisq/core/user/PreferencesPayload.java b/core/src/main/java/bisq/core/user/PreferencesPayload.java index 03628791f36..57e00d7e83b 100644 --- a/core/src/main/java/bisq/core/user/PreferencesPayload.java +++ b/core/src/main/java/bisq/core/user/PreferencesPayload.java @@ -133,6 +133,7 @@ public final class PreferencesPayload implements PersistableEnvelope { private boolean hideNonAccountPaymentMethods; private boolean showOffersMatchingMyAccounts; + private boolean denyApiTaker; /////////////////////////////////////////////////////////////////////////////////////////// // Constructor @@ -198,7 +199,8 @@ public Message toProtoMessage() { .map(autoConfirmSettings -> ((protobuf.AutoConfirmSettings) autoConfirmSettings.toProtoMessage())) .collect(Collectors.toList())) .setHideNonAccountPaymentMethods(hideNonAccountPaymentMethods) - .setShowOffersMatchingMyAccounts(showOffersMatchingMyAccounts); + .setShowOffersMatchingMyAccounts(showOffersMatchingMyAccounts) + .setDenyApiTaker(denyApiTaker); Optional.ofNullable(backupDirectory).ifPresent(builder::setBackupDirectory); Optional.ofNullable(preferredTradeCurrency).ifPresent(e -> builder.setPreferredTradeCurrency((protobuf.TradeCurrency) e.toProtoMessage())); @@ -294,7 +296,8 @@ public static PreferencesPayload fromProto(protobuf.PreferencesPayload proto, Co .map(AutoConfirmSettings::fromProto) .collect(Collectors.toList())), proto.getHideNonAccountPaymentMethods(), - proto.getShowOffersMatchingMyAccounts() + proto.getShowOffersMatchingMyAccounts(), + proto.getDenyApiTaker() ); } } diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index e207fcf8552..8d848c83b09 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -1616,6 +1616,7 @@ message PreferencesPayload { double bsq_average_trim_threshold = 57; bool hide_non_account_payment_methods = 58; bool show_offers_matching_my_accounts = 59; + bool deny_api_taker = 60; } message AutoConfirmSettings { From aaef64be85e0e8fc57aa9913bbd60b800ce82a5c Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 3 Jan 2021 15:16:48 -0500 Subject: [PATCH 17/31] Add denyApiTaker toggle to PreferencesView --- core/src/main/resources/i18n/displayStrings.properties | 1 + .../main/settings/preferences/PreferencesView.java | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index ed0e92ed395..4f872ea254a 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -1221,6 +1221,7 @@ setting.preferences.useAnimations=Use animations setting.preferences.useDarkMode=Use dark mode setting.preferences.sortWithNumOffers=Sort market lists with no. of offers/trades setting.preferences.onlyShowPaymentMethodsFromAccount=Hide non-supported payment methods +setting.preferences.denyApiTaker=Deny takers using the API setting.preferences.resetAllFlags=Reset all \"Don't show again\" flags settings.preferences.languageChange=To apply the language change to all screens requires a restart. settings.preferences.supportLanguageWarning=In case of a dispute, please note that mediation is handled in {0} and arbitration in {1}. diff --git a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java index 79870291161..19f4b736071 100644 --- a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java +++ b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java @@ -120,7 +120,7 @@ public class PreferencesView extends ActivatableViewAndModel preferredTradeCurrencyComboBox; private ToggleButton showOwnOffersInOfferBook, useAnimations, useDarkMode, sortMarketCurrenciesNumerically, - avoidStandbyMode, useCustomFee, autoConfirmXmrToggle, hideNonAccountPaymentMethodsToggle; + avoidStandbyMode, useCustomFee, autoConfirmXmrToggle, hideNonAccountPaymentMethodsToggle, denyApiTakerToggle; private int gridRow = 0; private int displayCurrenciesGridRowIndex = 0; private InputTextField transactionFeeInputTextField, ignoreTradersListInputTextField, ignoreDustThresholdInputTextField, @@ -603,7 +603,7 @@ public CryptoCurrency fromString(String s) { } private void initializeDisplayOptions() { - TitledGroupBg titledGroupBg = addTitledGroupBg(root, ++gridRow, 6, Res.get("setting.preferences.displayOptions"), Layout.GROUP_DISTANCE); + TitledGroupBg titledGroupBg = addTitledGroupBg(root, ++gridRow, 7, Res.get("setting.preferences.displayOptions"), Layout.GROUP_DISTANCE); GridPane.setColumnSpan(titledGroupBg, 1); showOwnOffersInOfferBook = addSlideToggleButton(root, gridRow, Res.get("setting.preferences.showOwnOffers"), Layout.FIRST_ROW_AND_GROUP_DISTANCE); @@ -611,6 +611,7 @@ private void initializeDisplayOptions() { useDarkMode = addSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.useDarkMode")); sortMarketCurrenciesNumerically = addSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.sortWithNumOffers")); hideNonAccountPaymentMethodsToggle = addSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.onlyShowPaymentMethodsFromAccount")); + denyApiTakerToggle = addSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.denyApiTaker")); resetDontShowAgainButton = addButton(root, ++gridRow, Res.get("setting.preferences.resetAllFlags"), 0); resetDontShowAgainButton.getStyleClass().add("compact-button"); resetDontShowAgainButton.setMaxWidth(Double.MAX_VALUE); @@ -950,6 +951,9 @@ private void activateDisplayPreferences() { hideNonAccountPaymentMethodsToggle.setOnAction(e -> preferences.setHideNonAccountPaymentMethods(hideNonAccountPaymentMethodsToggle.isSelected())); hideNonAccountPaymentMethodsToggle.setDisable(disableToggle); + denyApiTakerToggle.setSelected(preferences.isDenyApiTaker()); + denyApiTakerToggle.setOnAction(e -> preferences.setDenyApiTaker(denyApiTakerToggle.isSelected())); + resetDontShowAgainButton.setOnAction(e -> preferences.resetDontShowAgain()); editCustomBtcExplorer.setOnAction(e -> { @@ -1128,6 +1132,7 @@ private void deactivateDisplayPreferences() { useDarkMode.setOnAction(null); sortMarketCurrenciesNumerically.setOnAction(null); hideNonAccountPaymentMethodsToggle.setOnAction(null); + denyApiTakerToggle.setOnAction(null); showOwnOffersInOfferBook.setOnAction(null); resetDontShowAgainButton.setOnAction(null); if (displayStandbyModeFeature) { From 78282af10afcb14130d09665cf0dca95e93eceec Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 3 Jan 2021 15:32:05 -0500 Subject: [PATCH 18/31] Add isApiUser to OfferAvailabilityRequest --- .../core/offer/messages/OfferAvailabilityRequest.java | 11 +++++++++-- proto/src/main/proto/pb.proto | 1 + 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityRequest.java b/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityRequest.java index a3cbd1c9d06..d75d28aaaae 100644 --- a/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityRequest.java +++ b/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityRequest.java @@ -42,13 +42,16 @@ public final class OfferAvailabilityRequest extends OfferMessage implements Supp private final long takersTradePrice; @Nullable private final Capabilities supportedCapabilities; + private final boolean isApiUser; public OfferAvailabilityRequest(String offerId, PubKeyRing pubKeyRing, - long takersTradePrice) { + long takersTradePrice, + boolean isApiUser) { this(offerId, pubKeyRing, takersTradePrice, + isApiUser, Capabilities.app, Version.getP2PMessageVersion(), UUID.randomUUID().toString()); @@ -62,12 +65,14 @@ public OfferAvailabilityRequest(String offerId, private OfferAvailabilityRequest(String offerId, PubKeyRing pubKeyRing, long takersTradePrice, + boolean isApiUser, @Nullable Capabilities supportedCapabilities, int messageVersion, @Nullable String uid) { super(messageVersion, offerId, uid); this.pubKeyRing = pubKeyRing; this.takersTradePrice = takersTradePrice; + this.isApiUser = isApiUser; this.supportedCapabilities = supportedCapabilities; } @@ -76,7 +81,8 @@ public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { final protobuf.OfferAvailabilityRequest.Builder builder = protobuf.OfferAvailabilityRequest.newBuilder() .setOfferId(offerId) .setPubKeyRing(pubKeyRing.toProtoMessage()) - .setTakersTradePrice(takersTradePrice); + .setTakersTradePrice(takersTradePrice) + .setIsApiUser(isApiUser); Optional.ofNullable(supportedCapabilities).ifPresent(e -> builder.addAllSupportedCapabilities(Capabilities.toIntList(supportedCapabilities))); Optional.ofNullable(uid).ifPresent(e -> builder.setUid(uid)); @@ -90,6 +96,7 @@ public static OfferAvailabilityRequest fromProto(protobuf.OfferAvailabilityReque return new OfferAvailabilityRequest(proto.getOfferId(), PubKeyRing.fromProto(proto.getPubKeyRing()), proto.getTakersTradePrice(), + proto.getIsApiUser(), Capabilities.fromIntList(proto.getSupportedCapabilitiesList()), messageVersion, proto.getUid().isEmpty() ? null : proto.getUid()); diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 8d848c83b09..3c5d18dfde2 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -158,6 +158,7 @@ message OfferAvailabilityRequest { int64 takers_trade_price = 3; repeated int32 supported_capabilities = 4; string uid = 5; + bool is_api_user = 6; } message OfferAvailabilityResponse { From 0ae5006c08a0b9c28bfb0522adb22aa89dcefd3f Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 3 Jan 2021 15:40:03 -0500 Subject: [PATCH 19/31] Add UNCONF_TX_LIMIT_HIT to AvailabilityResult --- core/src/main/java/bisq/core/offer/AvailabilityResult.java | 3 ++- core/src/main/java/bisq/core/offer/OpenOfferManager.java | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/bisq/core/offer/AvailabilityResult.java b/core/src/main/java/bisq/core/offer/AvailabilityResult.java index 2d3d749ff24..9238b4050f9 100644 --- a/core/src/main/java/bisq/core/offer/AvailabilityResult.java +++ b/core/src/main/java/bisq/core/offer/AvailabilityResult.java @@ -27,5 +27,6 @@ public enum AvailabilityResult { NO_MEDIATORS, USER_IGNORED, MISSING_MANDATORY_CAPABILITY, - NO_REFUND_AGENTS + NO_REFUND_AGENTS, + UNCONF_TX_LIMIT_HIT } diff --git a/core/src/main/java/bisq/core/offer/OpenOfferManager.java b/core/src/main/java/bisq/core/offer/OpenOfferManager.java index 3e16c784024..6620dd2f749 100644 --- a/core/src/main/java/bisq/core/offer/OpenOfferManager.java +++ b/core/src/main/java/bisq/core/offer/OpenOfferManager.java @@ -637,8 +637,6 @@ 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()))) { - availabilityResult = AvailabilityResult.AVAILABLE; - mediatorNodeAddress = DisputeAgentSelection.getLeastUsedMediator(tradeStatisticsManager, mediatorManager).getNodeAddress(); openOffer.setMediatorNodeAddress(mediatorNodeAddress); @@ -650,6 +648,7 @@ private void handleOfferAvailabilityRequest(OfferAvailabilityRequest request, No // in trade price between the peers. Also here poor connectivity might cause market price API connection // losses and therefore an outdated market price. offer.checkTradePriceTolerance(request.getTakersTradePrice()); + availabilityResult = AvailabilityResult.AVAILABLE; } catch (TradePriceOutOfToleranceException e) { log.warn("Trade price check failed because takers price is outside out tolerance."); availabilityResult = AvailabilityResult.PRICE_OUT_OF_TOLERANCE; @@ -674,7 +673,7 @@ private void handleOfferAvailabilityRequest(OfferAvailabilityRequest request, No if (btcWalletService.isUnconfirmedTransactionsLimitHit() || bsqWalletService.isUnconfirmedTransactionsLimitHit()) { errorMessage = Res.get("shared.unconfirmedTransactionsLimitReached"); log.warn(errorMessage); - availabilityResult = AvailabilityResult.UNKNOWN_FAILURE; + availabilityResult = AvailabilityResult.UNCONF_TX_LIMIT_HIT; } OfferAvailabilityResponse offerAvailabilityResponse = new OfferAvailabilityResponse(request.offerId, From 18e4a39023502ebaa6f1e0d89d2e6cb57ee80ecd Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 3 Jan 2021 15:59:26 -0500 Subject: [PATCH 20/31] Add support for isApiUser and check in OpenOfferManager Calls from the API set the flag to true, from the desktop its false. If offer has denyApiUser enabled the offer availibility check fails. @ghubstan: I set the value in CoreApi and not directly in the CoreTradesService, so keep it more open that those sub domain classes can be used from desktop as well. Not sure if is not intended/permitted for some reason but I though that way we have more flexibility. Feel free to remove the param and set it directly in CoreTradesService if that class will never be used from non-api code. --- core/src/main/java/bisq/core/api/CoreApi.java | 1 + .../java/bisq/core/api/CoreTradesService.java | 2 + .../bisq/core/offer/AvailabilityResult.java | 3 +- .../bisq/core/offer/OpenOfferManager.java | 62 +++++++++++-------- .../availability/OfferAvailabilityModel.java | 7 ++- .../tasks/SendOfferAvailabilityRequest.java | 3 +- .../java/bisq/core/trade/TradeManager.java | 11 ++-- .../offer/takeoffer/TakeOfferDataModel.java | 4 +- 8 files changed, 58 insertions(+), 35 deletions(-) diff --git a/core/src/main/java/bisq/core/api/CoreApi.java b/core/src/main/java/bisq/core/api/CoreApi.java index 4dffaee9760..fdf8c05842b 100644 --- a/core/src/main/java/bisq/core/api/CoreApi.java +++ b/core/src/main/java/bisq/core/api/CoreApi.java @@ -202,6 +202,7 @@ public void takeOffer(String offerId, coreTradesService.takeOffer(offer, paymentAccountId, takerFeeCurrencyCode, + true, resultHandler); } diff --git a/core/src/main/java/bisq/core/api/CoreTradesService.java b/core/src/main/java/bisq/core/api/CoreTradesService.java index 10d21d6415d..17fab7f5868 100644 --- a/core/src/main/java/bisq/core/api/CoreTradesService.java +++ b/core/src/main/java/bisq/core/api/CoreTradesService.java @@ -82,6 +82,7 @@ public CoreTradesService(CoreWalletsService coreWalletsService, void takeOffer(Offer offer, String paymentAccountId, String takerFeeCurrencyCode, + boolean isApiUser, Consumer resultHandler) { coreWalletsService.verifyWalletsAreAvailable(); coreWalletsService.verifyEncryptedWalletIsUnlocked(); @@ -108,6 +109,7 @@ void takeOffer(Offer offer, offer, paymentAccountId, useSavingsWallet, + isApiUser, resultHandler::accept, errorMessage -> { log.error(errorMessage); diff --git a/core/src/main/java/bisq/core/offer/AvailabilityResult.java b/core/src/main/java/bisq/core/offer/AvailabilityResult.java index 9238b4050f9..18e877c8306 100644 --- a/core/src/main/java/bisq/core/offer/AvailabilityResult.java +++ b/core/src/main/java/bisq/core/offer/AvailabilityResult.java @@ -28,5 +28,6 @@ public enum AvailabilityResult { USER_IGNORED, MISSING_MANDATORY_CAPABILITY, NO_REFUND_AGENTS, - UNCONF_TX_LIMIT_HIT + UNCONF_TX_LIMIT_HIT, + MAKER_DENIED_API_USER } diff --git a/core/src/main/java/bisq/core/offer/OpenOfferManager.java b/core/src/main/java/bisq/core/offer/OpenOfferManager.java index 6620dd2f749..ebffbc2ce09 100644 --- a/core/src/main/java/bisq/core/offer/OpenOfferManager.java +++ b/core/src/main/java/bisq/core/offer/OpenOfferManager.java @@ -634,39 +634,43 @@ private void handleOfferAvailabilityRequest(OfferAvailabilityRequest request, No NodeAddress refundAgentNodeAddress = null; if (openOfferOptional.isPresent()) { OpenOffer openOffer = openOfferOptional.get(); - 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(); - openOffer.setMediatorNodeAddress(mediatorNodeAddress); - - refundAgentNodeAddress = DisputeAgentSelection.getLeastUsedRefundAgent(tradeStatisticsManager, refundAgentManager).getNodeAddress(); - openOffer.setRefundAgentNodeAddress(refundAgentNodeAddress); - - try { - // Check also tradePrice to avoid failures after taker fee is paid caused by a too big difference - // in trade price between the peers. Also here poor connectivity might cause market price API connection - // losses and therefore an outdated market price. - offer.checkTradePriceTolerance(request.getTakersTradePrice()); - availabilityResult = AvailabilityResult.AVAILABLE; - } catch (TradePriceOutOfToleranceException e) { - log.warn("Trade price check failed because takers price is outside out tolerance."); - availabilityResult = AvailabilityResult.PRICE_OUT_OF_TOLERANCE; - } catch (MarketPriceNotAvailableException e) { - log.warn(e.getMessage()); - availabilityResult = AvailabilityResult.MARKET_PRICE_NOT_AVAILABLE; - } catch (Throwable e) { - log.warn("Trade price check failed. " + e.getMessage()); - availabilityResult = AvailabilityResult.UNKNOWN_FAILURE; + if (!apiUserDeniedByOffer(request, openOffer)) { + 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(); + openOffer.setMediatorNodeAddress(mediatorNodeAddress); + + refundAgentNodeAddress = DisputeAgentSelection.getLeastUsedRefundAgent(tradeStatisticsManager, refundAgentManager).getNodeAddress(); + openOffer.setRefundAgentNodeAddress(refundAgentNodeAddress); + + try { + // Check also tradePrice to avoid failures after taker fee is paid caused by a too big difference + // in trade price between the peers. Also here poor connectivity might cause market price API connection + // losses and therefore an outdated market price. + offer.checkTradePriceTolerance(request.getTakersTradePrice()); + availabilityResult = AvailabilityResult.AVAILABLE; + } catch (TradePriceOutOfToleranceException e) { + log.warn("Trade price check failed because takers price is outside out tolerance."); + availabilityResult = AvailabilityResult.PRICE_OUT_OF_TOLERANCE; + } catch (MarketPriceNotAvailableException e) { + log.warn(e.getMessage()); + availabilityResult = AvailabilityResult.MARKET_PRICE_NOT_AVAILABLE; + } catch (Throwable e) { + log.warn("Trade price check failed. " + e.getMessage()); + availabilityResult = AvailabilityResult.UNKNOWN_FAILURE; + } + } else { + availabilityResult = AvailabilityResult.USER_IGNORED; } } else { - availabilityResult = AvailabilityResult.USER_IGNORED; + availabilityResult = AvailabilityResult.OFFER_TAKEN; } } else { - availabilityResult = AvailabilityResult.OFFER_TAKEN; + availabilityResult = AvailabilityResult.MAKER_DENIED_API_USER; } } else { - log.warn("handleOfferAvailabilityRequest: openOffer not found. That should never happen."); + log.warn("handleOfferAvailabilityRequest: openOffer not found."); availabilityResult = AvailabilityResult.OFFER_TAKEN; } @@ -715,6 +719,10 @@ public void onFault(String errorMessage) { } } + private boolean apiUserDeniedByOffer(OfferAvailabilityRequest request, OpenOffer openOffer) { + return openOffer.getOffer().getDenyApiTaker() && request.isApiUser(); + } + private void sendAckMessage(OfferAvailabilityRequest message, NodeAddress sender, boolean result, diff --git a/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java b/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java index 8d183d40857..5e01665a1cf 100644 --- a/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java +++ b/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java @@ -66,19 +66,24 @@ public class OfferAvailabilityModel implements Model { @Getter private NodeAddress selectedRefundAgent; + // Added in v1.5.5 + @Getter + private final boolean isApiUser; public OfferAvailabilityModel(Offer offer, PubKeyRing pubKeyRing, P2PService p2PService, User user, MediatorManager mediatorManager, - TradeStatisticsManager tradeStatisticsManager) { + TradeStatisticsManager tradeStatisticsManager, + boolean isApiUser) { this.offer = offer; this.pubKeyRing = pubKeyRing; this.p2PService = p2PService; this.user = user; this.mediatorManager = mediatorManager; this.tradeStatisticsManager = tradeStatisticsManager; + this.isApiUser = isApiUser; } public NodeAddress getPeerNodeAddress() { diff --git a/core/src/main/java/bisq/core/offer/availability/tasks/SendOfferAvailabilityRequest.java b/core/src/main/java/bisq/core/offer/availability/tasks/SendOfferAvailabilityRequest.java index 3ee88536c64..d42828e9fc1 100644 --- a/core/src/main/java/bisq/core/offer/availability/tasks/SendOfferAvailabilityRequest.java +++ b/core/src/main/java/bisq/core/offer/availability/tasks/SendOfferAvailabilityRequest.java @@ -39,7 +39,8 @@ protected void run() { try { runInterceptHook(); - OfferAvailabilityRequest message = new OfferAvailabilityRequest(model.getOffer().getId(), model.getPubKeyRing(), model.getTakersTradePrice()); + OfferAvailabilityRequest message = new OfferAvailabilityRequest(model.getOffer().getId(), + model.getPubKeyRing(), model.getTakersTradePrice(), model.isApiUser()); log.info("Send {} with offerId {} and uid {} to peer {}", message.getClass().getSimpleName(), message.getOfferId(), message.getUid(), model.getPeerNodeAddress()); diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index dc87d4a98cc..5ec3a387d2b 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -373,6 +373,7 @@ public void requestPersistence() { /////////////////////////////////////////////////////////////////////////////////////////// public void checkOfferAvailability(Offer offer, + boolean isApiUser, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { if (btcWalletService.isUnconfirmedTransactionsLimitHit() || @@ -383,7 +384,7 @@ public void checkOfferAvailability(Offer offer, return; } - offer.checkOfferAvailability(getOfferAvailabilityModel(offer), resultHandler, errorMessageHandler); + offer.checkOfferAvailability(getOfferAvailabilityModel(offer, isApiUser), resultHandler, errorMessageHandler); } // First we check if offer is still available then we create the trade with the protocol @@ -396,12 +397,13 @@ public void onTakeOffer(Coin amount, Offer offer, String paymentAccountId, boolean useSavingsWallet, + boolean isApiUser, TradeResultHandler tradeResultHandler, ErrorMessageHandler errorMessageHandler) { checkArgument(!wasOfferAlreadyUsedInTrade(offer.getId())); - OfferAvailabilityModel model = getOfferAvailabilityModel(offer); + OfferAvailabilityModel model = getOfferAvailabilityModel(offer, isApiUser); offer.checkOfferAvailability(model, () -> { if (offer.getState() == Offer.State.AVAILABLE) { @@ -464,14 +466,15 @@ private ProcessModel getNewProcessModel(Offer offer) { processModelServiceProvider.getKeyRing().getPubKeyRing()); } - private OfferAvailabilityModel getOfferAvailabilityModel(Offer offer) { + private OfferAvailabilityModel getOfferAvailabilityModel(Offer offer, boolean isApiUser) { return new OfferAvailabilityModel( offer, keyRing.getPubKeyRing(), p2PService, user, mediatorManager, - tradeStatisticsManager); + tradeStatisticsManager, + isApiUser); } diff --git a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java index dc90ce04559..9c3eacd3308 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java @@ -171,6 +171,7 @@ protected void activate() { if (canTakeOffer()) { tradeManager.checkOfferAvailability(offer, + false, () -> { }, errorMessage -> new Popup().warning(errorMessage).show()); @@ -319,7 +320,8 @@ void onTakeOffer(TradeResultHandler tradeResultHandler) { offer, paymentAccount.getId(), useSavingsWallet, - tradeResultHandler::handleResult, + false, + tradeResultHandler, errorMessage -> { log.warn(errorMessage); new Popup().warning(errorMessage).show(); From 400a7362468eba02bd404ef4539487ae2d9b5e77 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 3 Jan 2021 18:03:36 -0500 Subject: [PATCH 21/31] Refactor checks for take-able offers Extract to OfferFilter method. Use enums for return values so the view can show the matching popups. --- .../java/bisq/core/offer/OfferFilter.java | 209 ++++++++++++++++++ .../main/offer/offerbook/OfferBookView.java | 176 +++++++-------- .../offer/offerbook/OfferBookViewModel.java | 115 +--------- .../offerbook/OfferBookViewModelTest.java | 22 +- 4 files changed, 300 insertions(+), 222 deletions(-) create mode 100644 core/src/main/java/bisq/core/offer/OfferFilter.java diff --git a/core/src/main/java/bisq/core/offer/OfferFilter.java b/core/src/main/java/bisq/core/offer/OfferFilter.java new file mode 100644 index 00000000000..a372a5d94fa --- /dev/null +++ b/core/src/main/java/bisq/core/offer/OfferFilter.java @@ -0,0 +1,209 @@ +/* + * 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.offer; + +import bisq.core.account.witness.AccountAgeWitnessService; +import bisq.core.filter.FilterManager; +import bisq.core.locale.CurrencyUtil; +import bisq.core.payment.PaymentAccount; +import bisq.core.payment.PaymentAccountUtil; +import bisq.core.user.Preferences; +import bisq.core.user.User; + +import bisq.common.app.Version; + +import org.bitcoinj.core.Coin; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import javafx.collections.SetChangeListener; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Singleton +public class OfferFilter { + private final User user; + private final Preferences preferences; + private final FilterManager filterManager; + private final AccountAgeWitnessService accountAgeWitnessService; + private final Map insufficientCounterpartyTradeLimitCache = new HashMap<>(); + private final Map myInsufficientTradeLimitCache = new HashMap<>(); + + @Inject + public OfferFilter(User user, + Preferences preferences, + FilterManager filterManager, + AccountAgeWitnessService accountAgeWitnessService) { + this.user = user; + this.preferences = preferences; + this.filterManager = filterManager; + this.accountAgeWitnessService = accountAgeWitnessService; + + if (user != null) { + // If our accounts have changed we reset our myInsufficientTradeLimitCache as it depends on account data + user.getPaymentAccountsAsObservable().addListener((SetChangeListener) c -> + myInsufficientTradeLimitCache.clear()); + } + } + + public enum Result { + VALID(true), + DENIED_API_TAKER, + HAS_NO_PAYMENT_ACCOUNT_VALID_FOR_OFFER, + HAS_NOT_SAME_PROTOCOL_VERSION, + IS_IGNORED, + IS_OFFER_BANNED, + IS_CURRENCY_BANNED, + IS_PAYMENT_METHOD_BANNED, + IS_NODE_ADDRESS_BANNED, + REQUIRE_UPDATE_TO_NEW_VERSION, + IS_INSUFFICIENT_COUNTERPARTY_TRADE_LIMIT, + IS_MY_INSUFFICIENT_TRADE_LIMIT; + + @Getter + private final boolean isValid; + + Result(boolean isValid) { + this.isValid = isValid; + } + + Result() { + this(false); + } + } + + public Result canTakeOffer(Offer offer, boolean isApiUser) { + if (isApiUser && offer.getDenyApiTaker()) { + return Result.DENIED_API_TAKER; + } + if (!isAnyPaymentAccountValidForOffer(offer)) { + return Result.HAS_NO_PAYMENT_ACCOUNT_VALID_FOR_OFFER; + } + if (!hasSameProtocolVersion(offer)) { + return Result.HAS_NOT_SAME_PROTOCOL_VERSION; + } + if (isIgnored(offer)) { + return Result.IS_IGNORED; + } + if (isOfferBanned(offer)) { + return Result.IS_OFFER_BANNED; + } + if (isCurrencyBanned(offer)) { + return Result.IS_CURRENCY_BANNED; + } + if (isPaymentMethodBanned(offer)) { + return Result.IS_PAYMENT_METHOD_BANNED; + } + if (isNodeAddressBanned(offer)) { + return Result.IS_NODE_ADDRESS_BANNED; + } + if (requireUpdateToNewVersion()) { + return Result.REQUIRE_UPDATE_TO_NEW_VERSION; + } + if (isInsufficientCounterpartyTradeLimit(offer)) { + return Result.IS_INSUFFICIENT_COUNTERPARTY_TRADE_LIMIT; + } + if (isMyInsufficientTradeLimit(offer)) { + return Result.IS_MY_INSUFFICIENT_TRADE_LIMIT; + } + + return Result.VALID; + } + + public boolean isAnyPaymentAccountValidForOffer(Offer offer) { + return user.getPaymentAccounts() != null && + PaymentAccountUtil.isAnyTakerPaymentAccountValidForOffer(offer, user.getPaymentAccounts()); + } + + public boolean hasSameProtocolVersion(Offer offer) { + return offer.getProtocolVersion() == Version.TRADE_PROTOCOL_VERSION; + } + + public boolean isIgnored(Offer offer) { + return preferences.getIgnoreTradersList().stream() + .anyMatch(i -> i.equals(offer.getMakerNodeAddress().getFullAddress())); + } + + public boolean isOfferBanned(Offer offer) { + return filterManager.isOfferIdBanned(offer.getId()); + } + + public boolean isCurrencyBanned(Offer offer) { + return filterManager.isCurrencyBanned(offer.getCurrencyCode()); + } + + public boolean isPaymentMethodBanned(Offer offer) { + return filterManager.isPaymentMethodBanned(offer.getPaymentMethod()); + } + + public boolean isNodeAddressBanned(Offer offer) { + return filterManager.isNodeAddressBanned(offer.getMakerNodeAddress()); + } + + public boolean requireUpdateToNewVersion() { + return filterManager.requireUpdateToNewVersionForTrading(); + } + + // This call is a bit expensive so we cache results + public boolean isInsufficientCounterpartyTradeLimit(Offer offer) { + String offerId = offer.getId(); + if (insufficientCounterpartyTradeLimitCache.containsKey(offerId)) { + return insufficientCounterpartyTradeLimitCache.get(offerId); + } + + boolean result = CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()) && + !accountAgeWitnessService.verifyPeersTradeAmount(offer, offer.getAmount(), + errorMessage -> { + }); + insufficientCounterpartyTradeLimitCache.put(offerId, result); + return result; + } + + // This call is a bit expensive so we cache results + public boolean isMyInsufficientTradeLimit(Offer offer) { + String offerId = offer.getId(); + if (myInsufficientTradeLimitCache.containsKey(offerId)) { + return myInsufficientTradeLimitCache.get(offerId); + } + + Optional accountOptional = PaymentAccountUtil.getMostMaturePaymentAccountForOffer(offer, + user.getPaymentAccounts(), + accountAgeWitnessService); + long myTradeLimit = accountOptional + .map(paymentAccount -> accountAgeWitnessService.getMyTradeLimit(paymentAccount, + offer.getCurrencyCode(), offer.getMirroredDirection())) + .orElse(0L); + long offerMinAmount = offer.getMinAmount().value; + log.debug("isInsufficientTradeLimit accountOptional={}, myTradeLimit={}, offerMinAmount={}, ", + accountOptional.isPresent() ? accountOptional.get().getAccountName() : "null", + Coin.valueOf(myTradeLimit).toFriendlyString(), + Coin.valueOf(offerMinAmount).toFriendlyString()); + boolean result = CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()) && + accountOptional.isPresent() && + myTradeLimit < offerMinAmount; + myInsufficientTradeLimitCache.put(offerId, result); + return result; + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java index 6c3fa3d6c24..bc382f44232 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java @@ -52,6 +52,7 @@ import bisq.core.locale.TradeCurrency; import bisq.core.monetary.Price; import bisq.core.offer.Offer; +import bisq.core.offer.OfferFilter; import bisq.core.offer.OfferPayload; import bisq.core.offer.OfferRestrictions; import bisq.core.payment.PaymentAccount; @@ -62,6 +63,7 @@ import bisq.network.p2p.NodeAddress; +import bisq.common.app.DevEnv; import bisq.common.config.Config; import bisq.common.util.Tuple3; @@ -617,51 +619,61 @@ private void onCreateOffer() { } } - private void onShowInfo(Offer offer, - boolean isPaymentAccountValidForOffer, - boolean isInsufficientCounterpartyTradeLimit, - boolean hasSameProtocolVersion, - boolean isIgnored, - boolean isOfferBanned, - boolean isCurrencyBanned, - boolean isPaymentMethodBanned, - boolean isNodeAddressBanned, - boolean requireUpdateToNewVersion, - boolean isInsufficientTradeLimit) { - if (!isPaymentAccountValidForOffer) { - openPopupForMissingAccountSetup(Res.get("offerbook.warning.noMatchingAccount.headline"), - Res.get("offerbook.warning.noMatchingAccount.msg"), - FiatAccountsView.class, - "navigation.account"); - } else if (isInsufficientCounterpartyTradeLimit) { - new Popup().warning(Res.get("offerbook.warning.counterpartyTradeRestrictions")).show(); - } else if (!hasSameProtocolVersion) { - new Popup().warning(Res.get("offerbook.warning.wrongTradeProtocol")).show(); - } else if (isIgnored) { - new Popup().warning(Res.get("offerbook.warning.userIgnored")).show(); - } else if (isOfferBanned) { - new Popup().warning(Res.get("offerbook.warning.offerBlocked")).show(); - } else if (isCurrencyBanned) { - new Popup().warning(Res.get("offerbook.warning.currencyBanned")).show(); - } else if (isPaymentMethodBanned) { - new Popup().warning(Res.get("offerbook.warning.paymentMethodBanned")).show(); - } else if (isNodeAddressBanned) { - new Popup().warning(Res.get("offerbook.warning.nodeBlocked")).show(); - } else if (requireUpdateToNewVersion) { - new Popup().warning(Res.get("offerbook.warning.requireUpdateToNewVersion")).show(); - } else if (isInsufficientTradeLimit) { - final Optional account = model.getMostMaturePaymentAccountForOffer(offer); - if (account.isPresent()) { - final long tradeLimit = model.accountAgeWitnessService.getMyTradeLimit(account.get(), - offer.getCurrencyCode(), offer.getMirroredDirection()); - new Popup() - .warning(Res.get("popup.warning.tradeLimitDueAccountAgeRestriction.buyer", - formatter.formatCoinWithCode(Coin.valueOf(tradeLimit)), - Res.get("offerbook.warning.newVersionAnnouncement"))) - .show(); - } else { - log.warn("We don't found a payment account but got called the isInsufficientTradeLimit case. That must not happen."); - } + private void onShowInfo(Offer offer, OfferFilter.Result result) { + switch (result) { + case VALID: + break; + case DENIED_API_TAKER: + DevEnv.logErrorAndThrowIfDevMode("We are in desktop and in the taker position " + + "viewing offers, so it cannot be that we got denied for the offer as we are not an API user."); + break; + case HAS_NO_PAYMENT_ACCOUNT_VALID_FOR_OFFER: + openPopupForMissingAccountSetup(Res.get("offerbook.warning.noMatchingAccount.headline"), + Res.get("offerbook.warning.noMatchingAccount.msg"), + FiatAccountsView.class, + "navigation.account"); + break; + case HAS_NOT_SAME_PROTOCOL_VERSION: + new Popup().warning(Res.get("offerbook.warning.wrongTradeProtocol")).show(); + break; + case IS_IGNORED: + new Popup().warning(Res.get("offerbook.warning.userIgnored")).show(); + break; + case IS_OFFER_BANNED: + new Popup().warning(Res.get("offerbook.warning.offerBlocked")).show(); + break; + case IS_CURRENCY_BANNED: + new Popup().warning(Res.get("offerbook.warning.currencyBanned")).show(); + break; + case IS_PAYMENT_METHOD_BANNED: + new Popup().warning(Res.get("offerbook.warning.paymentMethodBanned")).show(); + break; + case IS_NODE_ADDRESS_BANNED: + new Popup().warning(Res.get("offerbook.warning.nodeBlocked")).show(); + break; + case REQUIRE_UPDATE_TO_NEW_VERSION: + new Popup().warning(Res.get("offerbook.warning.requireUpdateToNewVersion")).show(); + break; + case IS_INSUFFICIENT_COUNTERPARTY_TRADE_LIMIT: + new Popup().warning(Res.get("offerbook.warning.counterpartyTradeRestrictions")).show(); + break; + case IS_MY_INSUFFICIENT_TRADE_LIMIT: + Optional account = model.getMostMaturePaymentAccountForOffer(offer); + if (account.isPresent()) { + long tradeLimit = model.accountAgeWitnessService.getMyTradeLimit(account.get(), + offer.getCurrencyCode(), offer.getMirroredDirection()); + new Popup() + .warning(Res.get("popup.warning.tradeLimitDueAccountAgeRestriction.buyer", + formatter.formatCoinWithCode(Coin.valueOf(tradeLimit)), + Res.get("offerbook.warning.newVersionAnnouncement"))) + .show(); + } else { + DevEnv.logErrorAndThrowIfDevMode("We don't found a payment account but got called the " + + "isInsufficientTradeLimit case."); + } + break; + default: + break; } } @@ -1015,11 +1027,7 @@ public TableCell call(TableColumn() { final ImageView iconView = new ImageView(); final AutoTooltipButton button = new AutoTooltipButton(); - boolean isTradable, isPaymentAccountValidForOffer, - isInsufficientCounterpartyTradeLimit, - hasSameProtocolVersion, isIgnored, isOfferBanned, isCurrencyBanned, - isPaymentMethodBanned, isNodeAddressBanned, isMyInsufficientTradeLimit, - requireUpdateToNewVersion; + OfferFilter.Result canTakeOfferResult = null; { button.setGraphic(iconView); @@ -1034,37 +1042,14 @@ public void updateItem(final OfferBookListItem item, boolean empty) { TableRow tableRow = getTableRow(); if (item != null && !empty) { - final Offer offer = item.getOffer(); + Offer offer = item.getOffer(); boolean myOffer = model.isMyOffer(offer); + if (tableRow != null) { - // this code is duplicated in model.getOffersMatchingMyAccountsPredicate but as - // we want to pass the results for displaying relevant info in popups we - // cannot simply replace it with the predicate. If there are any changes we - // need to maintain both. - isPaymentAccountValidForOffer = model.isAnyPaymentAccountValidForOffer(offer); - isInsufficientCounterpartyTradeLimit = model.isInsufficientCounterpartyTradeLimit(offer); - hasSameProtocolVersion = model.hasSameProtocolVersion(offer); - isIgnored = model.isIgnored(offer); - isOfferBanned = model.isOfferBanned(offer); - isCurrencyBanned = model.isCurrencyBanned(offer); - isPaymentMethodBanned = model.isPaymentMethodBanned(offer); - isNodeAddressBanned = model.isNodeAddressBanned(offer); - requireUpdateToNewVersion = model.requireUpdateToNewVersion(); - isMyInsufficientTradeLimit = model.isMyInsufficientTradeLimit(offer); - isTradable = isPaymentAccountValidForOffer && - !isInsufficientCounterpartyTradeLimit && - hasSameProtocolVersion && - !isIgnored && - !isOfferBanned && - !isCurrencyBanned && - !isPaymentMethodBanned && - !isNodeAddressBanned && - !requireUpdateToNewVersion && - !isMyInsufficientTradeLimit; - - tableRow.setOpacity(isTradable || myOffer ? 1 : 0.4); - - if (isTradable) { + canTakeOfferResult = model.offerFilter.canTakeOffer(offer, false); + tableRow.setOpacity(canTakeOfferResult.isValid() || myOffer ? 1 : 0.4); + + if (canTakeOfferResult.isValid()) { // set first row button as default button.setDefaultButton(getIndex() == 0); tableRow.setOnMousePressed(null); @@ -1073,17 +1058,7 @@ public void updateItem(final OfferBookListItem item, boolean empty) { tableRow.setOnMousePressed(e -> { // ugly hack to get the icon clickable when deactivated if (!(e.getTarget() instanceof ImageView || e.getTarget() instanceof Canvas)) - onShowInfo(offer, - isPaymentAccountValidForOffer, - isInsufficientCounterpartyTradeLimit, - hasSameProtocolVersion, - isIgnored, - isOfferBanned, - isCurrencyBanned, - isPaymentMethodBanned, - isNodeAddressBanned, - requireUpdateToNewVersion, - isMyInsufficientTradeLimit); + onShowInfo(offer, canTakeOfferResult); }); } } @@ -1113,18 +1088,15 @@ public void updateItem(final OfferBookListItem item, boolean empty) { button.setOnAction(e -> onTakeOffer(offer)); } - if (!myOffer && !isTradable) - button.setOnAction(e -> onShowInfo(offer, - isPaymentAccountValidForOffer, - isInsufficientCounterpartyTradeLimit, - hasSameProtocolVersion, - isIgnored, - isOfferBanned, - isCurrencyBanned, - isPaymentMethodBanned, - isNodeAddressBanned, - requireUpdateToNewVersion, - isMyInsufficientTradeLimit)); + if (!myOffer) { + if (canTakeOfferResult == null) { + canTakeOfferResult = model.offerFilter.canTakeOffer(offer, false); + } + + if (!canTakeOfferResult.isValid()) { + button.setOnAction(e -> onShowInfo(offer, canTakeOfferResult)); + } + } button.updateText(title); setPadding(new Insets(0, 15, 0, 0)); diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java index 2a69f863fca..6e0ddd0fa3d 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java @@ -28,7 +28,6 @@ import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.btc.setup.WalletsSetup; -import bisq.core.filter.FilterManager; import bisq.core.locale.BankUtil; import bisq.core.locale.CountryUtil; import bisq.core.locale.CryptoCurrency; @@ -39,6 +38,7 @@ import bisq.core.monetary.Price; import bisq.core.monetary.Volume; import bisq.core.offer.Offer; +import bisq.core.offer.OfferFilter; import bisq.core.offer.OfferPayload; import bisq.core.offer.OpenOfferManager; import bisq.core.payment.PaymentAccount; @@ -56,7 +56,6 @@ import bisq.network.p2p.NodeAddress; import bisq.network.p2p.P2PService; -import bisq.common.app.Version; import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ResultHandler; @@ -78,14 +77,12 @@ import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; -import javafx.collections.SetChangeListener; import javafx.collections.transformation.FilteredList; import javafx.collections.transformation.SortedList; import java.text.DecimalFormat; import java.util.Comparator; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -105,10 +102,10 @@ class OfferBookViewModel extends ActivatableViewModel { private final P2PService p2PService; final PriceFeedService priceFeedService; private final ClosedTradableManager closedTradableManager; - private final FilterManager filterManager; final AccountAgeWitnessService accountAgeWitnessService; private final Navigation navigation; private final PriceUtil priceUtil; + final OfferFilter offerFilter; private final CoinFormatter btcFormatter; private final BsqFormatter bsqFormatter; @@ -136,8 +133,6 @@ class OfferBookViewModel extends ActivatableViewModel { final IntegerProperty maxPlacesForMarketPriceMargin = new SimpleIntegerProperty(); boolean showAllPaymentMethods = true; boolean useOffersMatchingMyAccountsFilter; - private final Map myInsufficientTradeLimitCache = new HashMap<>(); - private final Map insufficientCounterpartyTradeLimitCache = new HashMap<>(); /////////////////////////////////////////////////////////////////////////////////////////// @@ -153,10 +148,10 @@ public OfferBookViewModel(User user, P2PService p2PService, PriceFeedService priceFeedService, ClosedTradableManager closedTradableManager, - FilterManager filterManager, AccountAgeWitnessService accountAgeWitnessService, Navigation navigation, PriceUtil priceUtil, + OfferFilter offerFilter, @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter, BsqFormatter bsqFormatter) { super(); @@ -169,10 +164,10 @@ public OfferBookViewModel(User user, this.p2PService = p2PService; this.priceFeedService = priceFeedService; this.closedTradableManager = closedTradableManager; - this.filterManager = filterManager; this.accountAgeWitnessService = accountAgeWitnessService; this.navigation = navigation; this.priceUtil = priceUtil; + this.offerFilter = offerFilter; this.btcFormatter = btcFormatter; this.bsqFormatter = bsqFormatter; @@ -213,12 +208,6 @@ public OfferBookViewModel(User user, highestMarketPriceMarginOffer.ifPresent(offerBookListItem -> maxPlacesForMarketPriceMargin.set(formatMarketPriceMargin(offerBookListItem.getOffer(), false).length())); }; - - // If our accounts have changed we reset our myInsufficientTradeLimitCache as it depends on account data - if (user != null) { - user.getPaymentAccountsAsObservable().addListener((SetChangeListener) c -> - myInsufficientTradeLimitCache.clear()); - } } @Override @@ -568,11 +557,6 @@ private void fillAllTradeCurrencies() { // Checks /////////////////////////////////////////////////////////////////////////////////////////// - boolean isAnyPaymentAccountValidForOffer(Offer offer) { - return user.getPaymentAccounts() != null && - PaymentAccountUtil.isAnyTakerPaymentAccountValidForOffer(offer, user.getPaymentAccounts()); - } - boolean hasPaymentAccountForCurrency() { return (showAllTradeCurrenciesProperty.get() && user.getPaymentAccounts() != null && @@ -615,98 +599,11 @@ private Predicate getOffersMatchingMyAccountsPredicate() { // This code duplicates code in the view at the button column. We need there the different results for // display in popups so we cannot replace that with the predicate. Any change need to be applied in both // places. - return offerBookListItem -> { - Offer offer = offerBookListItem.getOffer(); - boolean isPaymentAccountValidForOffer = isAnyPaymentAccountValidForOffer(offer); - boolean isInsufficientCounterpartyTradeLimit = isInsufficientCounterpartyTradeLimit(offer); - boolean hasSameProtocolVersion = hasSameProtocolVersion(offer); - boolean isIgnored = isIgnored(offer); - boolean isOfferBanned = isOfferBanned(offer); - boolean isCurrencyBanned = isCurrencyBanned(offer); - boolean isPaymentMethodBanned = isPaymentMethodBanned(offer); - boolean isNodeAddressBanned = isNodeAddressBanned(offer); - boolean requireUpdateToNewVersion = requireUpdateToNewVersion(); - boolean isMyInsufficientTradeLimit = isMyInsufficientTradeLimit(offer); - boolean isTradable = isPaymentAccountValidForOffer && - !isInsufficientCounterpartyTradeLimit && - hasSameProtocolVersion && - !isIgnored && - !isOfferBanned && - !isCurrencyBanned && - !isPaymentMethodBanned && - !isNodeAddressBanned && - !requireUpdateToNewVersion && - !isMyInsufficientTradeLimit; - return isTradable; - }; - } - - boolean isIgnored(Offer offer) { - return preferences.getIgnoreTradersList().stream() - .anyMatch(i -> i.equals(offer.getMakerNodeAddress().getFullAddress())); + return offerBookListItem -> offerFilter.canTakeOffer(offerBookListItem.getOffer(), false).isValid(); } boolean isOfferBanned(Offer offer) { - return filterManager.isOfferIdBanned(offer.getId()); - } - - boolean isCurrencyBanned(Offer offer) { - return filterManager.isCurrencyBanned(offer.getCurrencyCode()); - } - - boolean isPaymentMethodBanned(Offer offer) { - return filterManager.isPaymentMethodBanned(offer.getPaymentMethod()); - } - - boolean isNodeAddressBanned(Offer offer) { - return filterManager.isNodeAddressBanned(offer.getMakerNodeAddress()); - } - - boolean requireUpdateToNewVersion() { - return filterManager.requireUpdateToNewVersionForTrading(); - } - - // This call is a bit expensive so we cache results - boolean isInsufficientCounterpartyTradeLimit(Offer offer) { - String offerId = offer.getId(); - if (insufficientCounterpartyTradeLimitCache.containsKey(offerId)) { - return insufficientCounterpartyTradeLimitCache.get(offerId); - } - - boolean result = CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()) && - !accountAgeWitnessService.verifyPeersTradeAmount(offer, offer.getAmount(), - errorMessage -> { - }); - insufficientCounterpartyTradeLimitCache.put(offerId, result); - return result; - } - - // This call is a bit expensive so we cache results - boolean isMyInsufficientTradeLimit(Offer offer) { - String offerId = offer.getId(); - if (myInsufficientTradeLimitCache.containsKey(offerId)) { - return myInsufficientTradeLimitCache.get(offerId); - } - - Optional accountOptional = getMostMaturePaymentAccountForOffer(offer); - long myTradeLimit = accountOptional - .map(paymentAccount -> accountAgeWitnessService.getMyTradeLimit(paymentAccount, - offer.getCurrencyCode(), offer.getMirroredDirection())) - .orElse(0L); - long offerMinAmount = offer.getMinAmount().value; - log.debug("isInsufficientTradeLimit accountOptional={}, myTradeLimit={}, offerMinAmount={}, ", - accountOptional.isPresent() ? accountOptional.get().getAccountName() : "null", - Coin.valueOf(myTradeLimit).toFriendlyString(), - Coin.valueOf(offerMinAmount).toFriendlyString()); - boolean result = CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()) && - accountOptional.isPresent() && - myTradeLimit < offerMinAmount; - myInsufficientTradeLimitCache.put(offerId, result); - return result; - } - - boolean hasSameProtocolVersion(Offer offer) { - return offer.getProtocolVersion() == Version.TRADE_PROTOCOL_VERSION; + return offerFilter.isOfferBanned(offer); } private boolean isShowAllEntry(String id) { diff --git a/desktop/src/test/java/bisq/desktop/main/offer/offerbook/OfferBookViewModelTest.java b/desktop/src/test/java/bisq/desktop/main/offer/offerbook/OfferBookViewModelTest.java index 39044f8d526..dc48c8c5459 100644 --- a/desktop/src/test/java/bisq/desktop/main/offer/offerbook/OfferBookViewModelTest.java +++ b/desktop/src/test/java/bisq/desktop/main/offer/offerbook/OfferBookViewModelTest.java @@ -239,7 +239,7 @@ public void testMaxCharactersForAmountWithNoOffes() { when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems); final OfferBookViewModel model = new OfferBookViewModel(null, null, offerBook, empty, null, null, null, - null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter()); + null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter()); assertEquals(0, model.maxPlacesForAmount.intValue()); } @@ -253,7 +253,7 @@ public void testMaxCharactersForAmount() { when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems); final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null, - null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter()); + null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter()); model.activate(); assertEquals(6, model.maxPlacesForAmount.intValue()); @@ -271,7 +271,7 @@ public void testMaxCharactersForAmountRange() { when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems); final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null, - null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter()); + null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter()); model.activate(); assertEquals(15, model.maxPlacesForAmount.intValue()); @@ -290,7 +290,7 @@ public void testMaxCharactersForVolumeWithNoOffes() { when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems); final OfferBookViewModel model = new OfferBookViewModel(null, null, offerBook, empty, null, null, null, - null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter()); + null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter()); assertEquals(0, model.maxPlacesForVolume.intValue()); } @@ -304,7 +304,7 @@ public void testMaxCharactersForVolume() { when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems); final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null, - null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter()); + null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter()); model.activate(); assertEquals(5, model.maxPlacesForVolume.intValue()); @@ -322,7 +322,7 @@ public void testMaxCharactersForVolumeRange() { when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems); final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null, - null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter()); + null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter()); model.activate(); assertEquals(9, model.maxPlacesForVolume.intValue()); @@ -341,7 +341,7 @@ public void testMaxCharactersForPriceWithNoOffers() { when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems); final OfferBookViewModel model = new OfferBookViewModel(null, null, offerBook, empty, null, null, null, - null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter()); + null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter()); assertEquals(0, model.maxPlacesForPrice.intValue()); } @@ -355,7 +355,7 @@ public void testMaxCharactersForPrice() { when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems); final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null, - null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter()); + null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter()); model.activate(); assertEquals(7, model.maxPlacesForPrice.intValue()); @@ -373,7 +373,7 @@ public void testMaxCharactersForPriceDistanceWithNoOffers() { when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems); final OfferBookViewModel model = new OfferBookViewModel(null, null, offerBook, empty, null, null, null, - null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter()); + null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter()); assertEquals(0, model.maxPlacesForMarketPriceMargin.intValue()); } @@ -401,7 +401,7 @@ public void testMaxCharactersForPriceDistance() { offerBookListItems.addAll(item1, item2); final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, priceFeedService, - null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter()); + null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter()); model.activate(); assertEquals(8, model.maxPlacesForMarketPriceMargin.intValue()); //" (1.97%)" @@ -422,7 +422,7 @@ public void testGetPrice() { when(priceFeedService.getMarketPrice(anyString())).thenReturn(new MarketPrice("USD", 12684.0450, Instant.now().getEpochSecond(), true)); final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null, - null, null, null, null, getPriceUtil(), coinFormatter, new BsqFormatter()); + null, null, null, getPriceUtil(), null, coinFormatter, new BsqFormatter()); final OfferBookListItem item = make(btcBuyItem.but( with(useMarketBasedPrice, true), From 2a9481055b7a30018366caaebe9bc426d643d3b4 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 3 Jan 2021 18:06:47 -0500 Subject: [PATCH 22/31] Add `getOffersAvailableForTaker` method @ghubstan: If the `offerFilter.canTakeOffer` call can be included into `getOffers(String direction, String currencyCode)` and maybe also in `getOffer(String id)` we can remove the `getOffersAvailableForTaker` method. --- core/src/main/java/bisq/core/api/CoreApi.java | 9 +++++++++ .../java/bisq/core/api/CoreOffersService.java | 15 ++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/bisq/core/api/CoreApi.java b/core/src/main/java/bisq/core/api/CoreApi.java index fdf8c05842b..caa177664ca 100644 --- a/core/src/main/java/bisq/core/api/CoreApi.java +++ b/core/src/main/java/bisq/core/api/CoreApi.java @@ -112,6 +112,15 @@ public List getOffers(String direction, String currencyCode) { return coreOffersService.getOffers(direction, currencyCode); } + /** + * @param direction The offer direction + * @param currencyCode The offer currency + * @return Returns the offers which can be taken + */ + List getOffersAvailableForTaker(String direction, String currencyCode) { + return coreOffersService.getOffersAvailableForTaker(direction, currencyCode, true); + } + public void createAnPlaceOffer(String currencyCode, String directionAsString, String priceAsString, diff --git a/core/src/main/java/bisq/core/api/CoreOffersService.java b/core/src/main/java/bisq/core/api/CoreOffersService.java index 0764d33f078..4858c96f47b 100644 --- a/core/src/main/java/bisq/core/api/CoreOffersService.java +++ b/core/src/main/java/bisq/core/api/CoreOffersService.java @@ -22,6 +22,7 @@ import bisq.core.offer.CreateOfferService; import bisq.core.offer.Offer; import bisq.core.offer.OfferBookService; +import bisq.core.offer.OfferFilter; import bisq.core.offer.OfferUtil; import bisq.core.offer.OpenOfferManager; import bisq.core.payment.PaymentAccount; @@ -58,20 +59,24 @@ class CoreOffersService { private final OpenOfferManager openOfferManager; private final OfferUtil offerUtil; private final User user; + private final OfferFilter offerFilter; @Inject public CoreOffersService(CreateOfferService createOfferService, OfferBookService offerBookService, OpenOfferManager openOfferManager, OfferUtil offerUtil, - User user) { + User user, + OfferFilter offerFilter) { this.createOfferService = createOfferService; this.offerBookService = offerBookService; this.openOfferManager = openOfferManager; this.offerUtil = offerUtil; this.user = user; + this.offerFilter = offerFilter; } + // TODO should we add a check for offerFilter.canTakeOffer? Offer getOffer(String id) { return offerBookService.getOffers().stream() .filter(o -> o.getId().equals(id)) @@ -79,6 +84,8 @@ Offer getOffer(String id) { new IllegalStateException(format("offer with id '%s' not found", id))); } + // TODO returns all offers also those which cannot be taken. Should we use the filter from + // getOffersAvailableForTaker here and remove the getOffersAvailableForTaker method? List getOffers(String direction, String currencyCode) { List offers = offerBookService.getOffers().stream() .filter(o -> { @@ -99,6 +106,12 @@ List getOffers(String direction, String currencyCode) { return offers; } + List getOffersAvailableForTaker(String direction, String currencyCode, boolean isApiUser) { + return getOffers(direction, currencyCode).stream() + .filter(offer -> offerFilter.canTakeOffer(offer, isApiUser).isValid()) + .collect(Collectors.toList()); + } + // Create and place new offer. void createAndPlaceOffer(String currencyCode, String directionAsString, From dacdd8fa6ab89cc30bce7fa46c638bede32bcb08 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 3 Jan 2021 18:45:09 -0500 Subject: [PATCH 23/31] Add disableApi field to filter --- .../main/java/bisq/core/filter/Filter.java | 24 +++++++++++++------ .../resources/i18n/displayStrings.properties | 1 + .../main/overlays/windows/FilterWindow.java | 6 ++++- proto/src/main/proto/pb.proto | 1 + 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/bisq/core/filter/Filter.java b/core/src/main/java/bisq/core/filter/Filter.java index dfcbe3f5f0d..5f2f40ee9b6 100644 --- a/core/src/main/java/bisq/core/filter/Filter.java +++ b/core/src/main/java/bisq/core/filter/Filter.java @@ -99,6 +99,7 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload { // In contrast to bannedNodeAddress those addresses cannot access the network but are rejected immediately. // Should be only used in case of dos attacks private final Set nodeAddressesBannedFromNetwork; + private final boolean disableApi; // After we have created the signature from the filter data we clone it and apply the signature static Filter cloneWithSig(Filter filter, String signatureAsBase64) { @@ -127,7 +128,8 @@ static Filter cloneWithSig(Filter filter, String signatureAsBase64) { filter.getBannedPrivilegedDevPubKeys(), filter.isDisableAutoConf(), filter.getBannedAutoConfExplorers(), - filter.getNodeAddressesBannedFromNetwork()); + filter.getNodeAddressesBannedFromNetwork(), + filter.isDisableApi()); } // Used for signature verification as we created the sig without the signatureAsBase64 field we set it to null again @@ -157,7 +159,8 @@ static Filter cloneWithoutSig(Filter filter) { filter.getBannedPrivilegedDevPubKeys(), filter.isDisableAutoConf(), filter.getBannedAutoConfExplorers(), - filter.getNodeAddressesBannedFromNetwork()); + filter.getNodeAddressesBannedFromNetwork(), + filter.isDisableApi()); } public Filter(List bannedOfferIds, @@ -182,7 +185,8 @@ public Filter(List bannedOfferIds, List bannedPrivilegedDevPubKeys, boolean disableAutoConf, List bannedAutoConfExplorers, - Set nodeAddressesBannedFromNetwork) { + Set nodeAddressesBannedFromNetwork, + boolean disableApi) { this(bannedOfferIds, bannedNodeAddress, bannedPaymentAccounts, @@ -208,7 +212,8 @@ public Filter(List bannedOfferIds, bannedPrivilegedDevPubKeys, disableAutoConf, bannedAutoConfExplorers, - nodeAddressesBannedFromNetwork); + nodeAddressesBannedFromNetwork, + disableApi); } @@ -242,7 +247,8 @@ public Filter(List bannedOfferIds, List bannedPrivilegedDevPubKeys, boolean disableAutoConf, List bannedAutoConfExplorers, - Set nodeAddressesBannedFromNetwork) { + Set nodeAddressesBannedFromNetwork, + boolean disableApi) { this.bannedOfferIds = bannedOfferIds; this.bannedNodeAddress = bannedNodeAddress; this.bannedPaymentAccounts = bannedPaymentAccounts; @@ -269,6 +275,7 @@ public Filter(List bannedOfferIds, this.disableAutoConf = disableAutoConf; this.bannedAutoConfExplorers = bannedAutoConfExplorers; this.nodeAddressesBannedFromNetwork = nodeAddressesBannedFromNetwork; + this.disableApi = disableApi; // ownerPubKeyBytes can be null when called from tests if (ownerPubKeyBytes != null) { @@ -307,7 +314,8 @@ public protobuf.StoragePayload toProtoMessage() { .addAllBannedPrivilegedDevPubKeys(bannedPrivilegedDevPubKeys) .setDisableAutoConf(disableAutoConf) .addAllBannedAutoConfExplorers(bannedAutoConfExplorers) - .addAllNodeAddressesBannedFromNetwork(nodeAddressesBannedFromNetwork); + .addAllNodeAddressesBannedFromNetwork(nodeAddressesBannedFromNetwork) + .setDisableApi(disableApi); Optional.ofNullable(signatureAsBase64).ifPresent(builder::setSignatureAsBase64); Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData); @@ -346,7 +354,8 @@ public static Filter fromProto(protobuf.Filter proto) { ProtoUtil.protocolStringListToList(proto.getBannedPrivilegedDevPubKeysList()), proto.getDisableAutoConf(), ProtoUtil.protocolStringListToList(proto.getBannedAutoConfExplorersList()), - ProtoUtil.protocolStringListToSet(proto.getNodeAddressesBannedFromNetworkList()) + ProtoUtil.protocolStringListToSet(proto.getNodeAddressesBannedFromNetworkList()), + proto.getDisableApi() ); } @@ -390,6 +399,7 @@ public String toString() { ",\n ownerPubKey=" + ownerPubKey + ",\n disableAutoConf=" + disableAutoConf + ",\n nodeAddressesBannedFromNetwork=" + nodeAddressesBannedFromNetwork + + ",\n disableApi=" + disableApi + "\n}"; } } diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 4f872ea254a..2f8366f4268 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -2619,6 +2619,7 @@ filterWindow.disableTradeBelowVersion=Min. version required for trading filterWindow.add=Add filter filterWindow.remove=Remove filter filterWindow.btcFeeReceiverAddresses=BTC fee receiver addresses +filterWindow.disableApi=Disable API 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 97efe076b14..1157d848dfb 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 @@ -169,6 +169,8 @@ private void addContent() { Res.get("filterWindow.bannedPrivilegedDevPubKeys")).second; InputTextField autoConfExplorersTF = addTopLabelInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.autoConfExplorers")).second; + CheckBox disableApiCheckBox = addLabelCheckBox(gridPane, ++rowIndex, + Res.get("filterWindow.disableApi")); Filter filter = filterManager.getDevFilter(); if (filter != null) { @@ -194,6 +196,7 @@ private void addContent() { disableAutoConfCheckBox.setSelected(filter.isDisableAutoConf()); disableDaoBelowVersionTF.setText(filter.getDisableDaoBelowVersion()); disableTradeBelowVersionTF.setText(filter.getDisableTradeBelowVersion()); + disableApiCheckBox.setSelected(filter.isDisableApi()); } Button removeFilterMessageButton = new AutoTooltipButton(Res.get("filterWindow.remove")); @@ -227,7 +230,8 @@ private void addContent() { readAsList(bannedPrivilegedDevPubKeysTF), disableAutoConfCheckBox.isSelected(), readAsList(autoConfExplorersTF), - new HashSet<>(readAsList(bannedFromNetworkTF)) + new HashSet<>(readAsList(bannedFromNetworkTF)), + disableApiCheckBox.isSelected() ); // We remove first the old filter diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 3c5d18dfde2..8d1228cb058 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -679,6 +679,7 @@ message Filter { bool disable_auto_conf = 24; repeated string banned_auto_conf_explorers = 25; repeated string node_addresses_banned_from_network = 26; + bool disable_api = 27; } // Deprecated From 7a760357f02e091dfd413becc1065c36105c2bb4 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 3 Jan 2021 18:48:07 -0500 Subject: [PATCH 24/31] Add comment, remove pointless code. --- .../bisq/network/p2p/peers/getdata/RequestDataHandler.java | 3 +++ p2p/src/test/java/bisq/network/p2p/DummySeedNode.java | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/p2p/src/main/java/bisq/network/p2p/peers/getdata/RequestDataHandler.java b/p2p/src/main/java/bisq/network/p2p/peers/getdata/RequestDataHandler.java index 37cd40ce90a..999c750c176 100644 --- a/p2p/src/main/java/bisq/network/p2p/peers/getdata/RequestDataHandler.java +++ b/p2p/src/main/java/bisq/network/p2p/peers/getdata/RequestDataHandler.java @@ -262,6 +262,9 @@ private void addDetails(Map> numPay numPayloadsByClassName.putIfAbsent(className, new Tuple2<>(new AtomicInteger(0), new AtomicInteger(0))); numPayloadsByClassName.get(className).first.getAndIncrement(); + // toProtoMessage().getSerializedSize() is not very cheap. For about 1500 objects it takes about 20 ms + // I think its justified to get accurate metrics but if it turns out to be a performance issue we might need + // to remove it and use some more rough estimation by taking only the size of one data type and multiply it. numPayloadsByClassName.get(className).second.getAndAdd(networkPayload.toProtoMessage().getSerializedSize()); } diff --git a/p2p/src/test/java/bisq/network/p2p/DummySeedNode.java b/p2p/src/test/java/bisq/network/p2p/DummySeedNode.java index 405b7fd5fc9..6bd65596c6c 100644 --- a/p2p/src/test/java/bisq/network/p2p/DummySeedNode.java +++ b/p2p/src/test/java/bisq/network/p2p/DummySeedNode.java @@ -135,11 +135,9 @@ public void processArgs(String[] args) { checkArgument(arg.contains(":") && arg.split(":").length > 1 && arg.split(":")[1].length() > 3, "Wrong program argument " + arg); List list = Arrays.asList(arg.split(",")); - Set bannedPeers = new HashSet<>(); list.forEach(e -> { checkArgument(e.contains(":") && e.split(":").length == 2 && e.split(":")[1].length() == 4, "Wrong program argument " + e); - bannedPeers.add(new NodeAddress(e)); }); log.debug("From processArgs: ignoreList=" + list); } else if (arg.startsWith(HELP)) { From e2d386e402cb38e2c27bf0b968f4c5ff33be1bb1 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 3 Jan 2021 18:53:15 -0500 Subject: [PATCH 25/31] Rename bannedNodeAddress to nodeAddressesBannedFromTrading --- .../main/java/bisq/core/filter/Filter.java | 25 ++++++++----------- .../java/bisq/core/filter/FilterManager.java | 2 +- .../main/overlays/windows/FilterWindow.java | 8 +++--- proto/src/main/proto/pb.proto | 2 +- 4 files changed, 16 insertions(+), 21 deletions(-) diff --git a/core/src/main/java/bisq/core/filter/Filter.java b/core/src/main/java/bisq/core/filter/Filter.java index 5f2f40ee9b6..b433de9476e 100644 --- a/core/src/main/java/bisq/core/filter/Filter.java +++ b/core/src/main/java/bisq/core/filter/Filter.java @@ -48,10 +48,7 @@ @Value public final class Filter implements ProtectedStoragePayload, ExpirablePayload { private final List bannedOfferIds; - - // Those banned nodes cannot trade but still can access the network - private final List bannedNodeAddress; - + private final List nodeAddressesBannedFromTrading; private final List bannedAutoConfExplorers; private final List bannedPaymentAccounts; private final List bannedCurrencies; @@ -96,15 +93,13 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload { private final boolean disableAutoConf; // added at v1.5.5 - // In contrast to bannedNodeAddress those addresses cannot access the network but are rejected immediately. - // Should be only used in case of dos attacks private final Set nodeAddressesBannedFromNetwork; private final boolean disableApi; // After we have created the signature from the filter data we clone it and apply the signature static Filter cloneWithSig(Filter filter, String signatureAsBase64) { return new Filter(filter.getBannedOfferIds(), - filter.getBannedNodeAddress(), + filter.getNodeAddressesBannedFromTrading(), filter.getBannedPaymentAccounts(), filter.getBannedCurrencies(), filter.getBannedPaymentMethods(), @@ -135,7 +130,7 @@ static Filter cloneWithSig(Filter filter, String signatureAsBase64) { // Used for signature verification as we created the sig without the signatureAsBase64 field we set it to null again static Filter cloneWithoutSig(Filter filter) { return new Filter(filter.getBannedOfferIds(), - filter.getBannedNodeAddress(), + filter.getNodeAddressesBannedFromTrading(), filter.getBannedPaymentAccounts(), filter.getBannedCurrencies(), filter.getBannedPaymentMethods(), @@ -164,7 +159,7 @@ static Filter cloneWithoutSig(Filter filter) { } public Filter(List bannedOfferIds, - List bannedNodeAddress, + List nodeAddressesBannedFromTrading, List bannedPaymentAccounts, List bannedCurrencies, List bannedPaymentMethods, @@ -188,7 +183,7 @@ public Filter(List bannedOfferIds, Set nodeAddressesBannedFromNetwork, boolean disableApi) { this(bannedOfferIds, - bannedNodeAddress, + nodeAddressesBannedFromTrading, bannedPaymentAccounts, bannedCurrencies, bannedPaymentMethods, @@ -223,7 +218,7 @@ public Filter(List bannedOfferIds, @VisibleForTesting public Filter(List bannedOfferIds, - List bannedNodeAddress, + List nodeAddressesBannedFromTrading, List bannedPaymentAccounts, List bannedCurrencies, List bannedPaymentMethods, @@ -250,7 +245,7 @@ public Filter(List bannedOfferIds, Set nodeAddressesBannedFromNetwork, boolean disableApi) { this.bannedOfferIds = bannedOfferIds; - this.bannedNodeAddress = bannedNodeAddress; + this.nodeAddressesBannedFromTrading = nodeAddressesBannedFromTrading; this.bannedPaymentAccounts = bannedPaymentAccounts; this.bannedCurrencies = bannedCurrencies; this.bannedPaymentMethods = bannedPaymentMethods; @@ -292,7 +287,7 @@ public protobuf.StoragePayload toProtoMessage() { .collect(Collectors.toList()); protobuf.Filter.Builder builder = protobuf.Filter.newBuilder().addAllBannedOfferIds(bannedOfferIds) - .addAllBannedNodeAddress(bannedNodeAddress) + .addAllNodeAddressesBannedFromTrading(nodeAddressesBannedFromTrading) .addAllBannedPaymentAccounts(paymentAccountFilterList) .addAllBannedCurrencies(bannedCurrencies) .addAllBannedPaymentMethods(bannedPaymentMethods) @@ -330,7 +325,7 @@ public static Filter fromProto(protobuf.Filter proto) { return new Filter(ProtoUtil.protocolStringListToList(proto.getBannedOfferIdsList()), - ProtoUtil.protocolStringListToList(proto.getBannedNodeAddressList()), + ProtoUtil.protocolStringListToList(proto.getNodeAddressesBannedFromTradingList()), bannedPaymentAccountsList, ProtoUtil.protocolStringListToList(proto.getBannedCurrenciesList()), ProtoUtil.protocolStringListToList(proto.getBannedPaymentMethodsList()), @@ -373,7 +368,7 @@ public long getTTL() { public String toString() { return "Filter{" + "\n bannedOfferIds=" + bannedOfferIds + - ",\n bannedNodeAddress=" + bannedNodeAddress + + ",\n nodeAddressesBannedFromTrading=" + nodeAddressesBannedFromTrading + ",\n bannedAutoConfExplorers=" + bannedAutoConfExplorers + ",\n bannedPaymentAccounts=" + bannedPaymentAccounts + ",\n bannedCurrencies=" + bannedCurrencies + diff --git a/core/src/main/java/bisq/core/filter/FilterManager.java b/core/src/main/java/bisq/core/filter/FilterManager.java index e2d99082fd2..0dd35518c23 100644 --- a/core/src/main/java/bisq/core/filter/FilterManager.java +++ b/core/src/main/java/bisq/core/filter/FilterManager.java @@ -397,7 +397,7 @@ public boolean isOfferIdBanned(String offerId) { public boolean isNodeAddressBanned(NodeAddress nodeAddress) { return getFilter() != null && - getFilter().getBannedNodeAddress().stream() + getFilter().getNodeAddressesBannedFromTrading().stream() .anyMatch(e -> e.equals(nodeAddress.getFullAddress())); } 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 1157d848dfb..5ff3f216eab 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 @@ -124,11 +124,11 @@ private void addContent() { InputTextField offerIdsTF = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.offers")); - InputTextField nodesTF = addTopLabelInputTextField(gridPane, ++rowIndex, + InputTextField bannedFromTradingTF = addTopLabelInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.onions")).second; InputTextField bannedFromNetworkTF = addTopLabelInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.bannedFromNetwork")).second; - nodesTF.setPromptText("E.g. zqnzx6o3nifef5df.onion:9999"); // Do not translate + bannedFromTradingTF.setPromptText("E.g. zqnzx6o3nifef5df.onion:9999"); // Do not translate InputTextField paymentAccountFilterTF = addTopLabelInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.accounts")).second; GridPane.setHalignment(paymentAccountFilterTF, HPos.RIGHT); @@ -175,7 +175,7 @@ private void addContent() { Filter filter = filterManager.getDevFilter(); if (filter != null) { setupFieldFromList(offerIdsTF, filter.getBannedOfferIds()); - setupFieldFromList(nodesTF, filter.getBannedNodeAddress()); + setupFieldFromList(bannedFromTradingTF, filter.getNodeAddressesBannedFromTrading()); setupFieldFromList(bannedFromNetworkTF, filter.getNodeAddressesBannedFromNetwork()); setupFieldFromPaymentAccountFiltersList(paymentAccountFilterTF, filter.getBannedPaymentAccounts()); setupFieldFromList(bannedCurrenciesTF, filter.getBannedCurrencies()); @@ -209,7 +209,7 @@ private void addContent() { String signerPubKeyAsHex = filterManager.getSignerPubKeyAsHex(privKeyString); Filter newFilter = new Filter( readAsList(offerIdsTF), - readAsList(nodesTF), + readAsList(bannedFromTradingTF), readAsPaymentAccountFiltersList(paymentAccountFilterTF), readAsList(bannedCurrenciesTF), readAsList(bannedPaymentMethodsTF), diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 8d1228cb058..afcd90d210d 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -653,7 +653,7 @@ message RefundAgent { } message Filter { - repeated string banned_node_address = 1; + repeated string node_addresses_banned_from_trading = 1; repeated string banned_offer_ids = 2; repeated PaymentAccountFilter banned_payment_accounts = 3; string signature_as_base64 = 4; From 9d3fd033591a159f1c48aa136360525d8069ee12 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 3 Jan 2021 19:03:36 -0500 Subject: [PATCH 26/31] Rename isApiUser to isTakerApiUser --- .../main/java/bisq/core/api/CoreOffersService.java | 4 ++-- .../main/java/bisq/core/api/CoreTradesService.java | 4 ++-- .../java/bisq/core/offer/OpenOfferManager.java | 2 +- .../offer/availability/OfferAvailabilityModel.java | 6 +++--- .../tasks/SendOfferAvailabilityRequest.java | 2 +- .../offer/messages/OfferAvailabilityRequest.java | 14 +++++++------- .../main/java/bisq/core/trade/TradeManager.java | 12 ++++++------ proto/src/main/proto/pb.proto | 2 +- 8 files changed, 23 insertions(+), 23 deletions(-) diff --git a/core/src/main/java/bisq/core/api/CoreOffersService.java b/core/src/main/java/bisq/core/api/CoreOffersService.java index 4858c96f47b..b8a4ef8758e 100644 --- a/core/src/main/java/bisq/core/api/CoreOffersService.java +++ b/core/src/main/java/bisq/core/api/CoreOffersService.java @@ -106,9 +106,9 @@ List getOffers(String direction, String currencyCode) { return offers; } - List getOffersAvailableForTaker(String direction, String currencyCode, boolean isApiUser) { + List getOffersAvailableForTaker(String direction, String currencyCode, boolean isTakerApiUser) { return getOffers(direction, currencyCode).stream() - .filter(offer -> offerFilter.canTakeOffer(offer, isApiUser).isValid()) + .filter(offer -> offerFilter.canTakeOffer(offer, isTakerApiUser).isValid()) .collect(Collectors.toList()); } diff --git a/core/src/main/java/bisq/core/api/CoreTradesService.java b/core/src/main/java/bisq/core/api/CoreTradesService.java index 17fab7f5868..b4d7fcef188 100644 --- a/core/src/main/java/bisq/core/api/CoreTradesService.java +++ b/core/src/main/java/bisq/core/api/CoreTradesService.java @@ -82,7 +82,7 @@ public CoreTradesService(CoreWalletsService coreWalletsService, void takeOffer(Offer offer, String paymentAccountId, String takerFeeCurrencyCode, - boolean isApiUser, + boolean isTakerApiUser, Consumer resultHandler) { coreWalletsService.verifyWalletsAreAvailable(); coreWalletsService.verifyEncryptedWalletIsUnlocked(); @@ -109,7 +109,7 @@ void takeOffer(Offer offer, offer, paymentAccountId, useSavingsWallet, - isApiUser, + isTakerApiUser, resultHandler::accept, errorMessage -> { log.error(errorMessage); diff --git a/core/src/main/java/bisq/core/offer/OpenOfferManager.java b/core/src/main/java/bisq/core/offer/OpenOfferManager.java index ebffbc2ce09..d714a09fa58 100644 --- a/core/src/main/java/bisq/core/offer/OpenOfferManager.java +++ b/core/src/main/java/bisq/core/offer/OpenOfferManager.java @@ -720,7 +720,7 @@ public void onFault(String errorMessage) { } private boolean apiUserDeniedByOffer(OfferAvailabilityRequest request, OpenOffer openOffer) { - return openOffer.getOffer().getDenyApiTaker() && request.isApiUser(); + return openOffer.getOffer().getDenyApiTaker() && request.isTakerApiUser(); } private void sendAckMessage(OfferAvailabilityRequest message, diff --git a/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java b/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java index 5e01665a1cf..c1559cec8d3 100644 --- a/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java +++ b/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java @@ -68,7 +68,7 @@ public class OfferAvailabilityModel implements Model { // Added in v1.5.5 @Getter - private final boolean isApiUser; + private final boolean isTakerApiUser; public OfferAvailabilityModel(Offer offer, PubKeyRing pubKeyRing, @@ -76,14 +76,14 @@ public OfferAvailabilityModel(Offer offer, User user, MediatorManager mediatorManager, TradeStatisticsManager tradeStatisticsManager, - boolean isApiUser) { + boolean isTakerApiUser) { this.offer = offer; this.pubKeyRing = pubKeyRing; this.p2PService = p2PService; this.user = user; this.mediatorManager = mediatorManager; this.tradeStatisticsManager = tradeStatisticsManager; - this.isApiUser = isApiUser; + this.isTakerApiUser = isTakerApiUser; } public NodeAddress getPeerNodeAddress() { diff --git a/core/src/main/java/bisq/core/offer/availability/tasks/SendOfferAvailabilityRequest.java b/core/src/main/java/bisq/core/offer/availability/tasks/SendOfferAvailabilityRequest.java index d42828e9fc1..0dbc8e69ea4 100644 --- a/core/src/main/java/bisq/core/offer/availability/tasks/SendOfferAvailabilityRequest.java +++ b/core/src/main/java/bisq/core/offer/availability/tasks/SendOfferAvailabilityRequest.java @@ -40,7 +40,7 @@ protected void run() { runInterceptHook(); OfferAvailabilityRequest message = new OfferAvailabilityRequest(model.getOffer().getId(), - model.getPubKeyRing(), model.getTakersTradePrice(), model.isApiUser()); + model.getPubKeyRing(), model.getTakersTradePrice(), model.isTakerApiUser()); log.info("Send {} with offerId {} and uid {} to peer {}", message.getClass().getSimpleName(), message.getOfferId(), message.getUid(), model.getPeerNodeAddress()); diff --git a/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityRequest.java b/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityRequest.java index d75d28aaaae..6d9d14eaf61 100644 --- a/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityRequest.java +++ b/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityRequest.java @@ -42,16 +42,16 @@ public final class OfferAvailabilityRequest extends OfferMessage implements Supp private final long takersTradePrice; @Nullable private final Capabilities supportedCapabilities; - private final boolean isApiUser; + private final boolean isTakerApiUser; public OfferAvailabilityRequest(String offerId, PubKeyRing pubKeyRing, long takersTradePrice, - boolean isApiUser) { + boolean isTakerApiUser) { this(offerId, pubKeyRing, takersTradePrice, - isApiUser, + isTakerApiUser, Capabilities.app, Version.getP2PMessageVersion(), UUID.randomUUID().toString()); @@ -65,14 +65,14 @@ public OfferAvailabilityRequest(String offerId, private OfferAvailabilityRequest(String offerId, PubKeyRing pubKeyRing, long takersTradePrice, - boolean isApiUser, + boolean isTakerApiUser, @Nullable Capabilities supportedCapabilities, int messageVersion, @Nullable String uid) { super(messageVersion, offerId, uid); this.pubKeyRing = pubKeyRing; this.takersTradePrice = takersTradePrice; - this.isApiUser = isApiUser; + this.isTakerApiUser = isTakerApiUser; this.supportedCapabilities = supportedCapabilities; } @@ -82,7 +82,7 @@ public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { .setOfferId(offerId) .setPubKeyRing(pubKeyRing.toProtoMessage()) .setTakersTradePrice(takersTradePrice) - .setIsApiUser(isApiUser); + .setIsTakerApiUser(isTakerApiUser); Optional.ofNullable(supportedCapabilities).ifPresent(e -> builder.addAllSupportedCapabilities(Capabilities.toIntList(supportedCapabilities))); Optional.ofNullable(uid).ifPresent(e -> builder.setUid(uid)); @@ -96,7 +96,7 @@ public static OfferAvailabilityRequest fromProto(protobuf.OfferAvailabilityReque return new OfferAvailabilityRequest(proto.getOfferId(), PubKeyRing.fromProto(proto.getPubKeyRing()), proto.getTakersTradePrice(), - proto.getIsApiUser(), + proto.getIsTakerApiUser(), Capabilities.fromIntList(proto.getSupportedCapabilitiesList()), messageVersion, proto.getUid().isEmpty() ? null : proto.getUid()); diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index 5ec3a387d2b..6707fde8ea7 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -373,7 +373,7 @@ public void requestPersistence() { /////////////////////////////////////////////////////////////////////////////////////////// public void checkOfferAvailability(Offer offer, - boolean isApiUser, + boolean isTakerApiUser, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { if (btcWalletService.isUnconfirmedTransactionsLimitHit() || @@ -384,7 +384,7 @@ public void checkOfferAvailability(Offer offer, return; } - offer.checkOfferAvailability(getOfferAvailabilityModel(offer, isApiUser), resultHandler, errorMessageHandler); + offer.checkOfferAvailability(getOfferAvailabilityModel(offer, isTakerApiUser), resultHandler, errorMessageHandler); } // First we check if offer is still available then we create the trade with the protocol @@ -397,13 +397,13 @@ public void onTakeOffer(Coin amount, Offer offer, String paymentAccountId, boolean useSavingsWallet, - boolean isApiUser, + boolean isTakerApiUser, TradeResultHandler tradeResultHandler, ErrorMessageHandler errorMessageHandler) { checkArgument(!wasOfferAlreadyUsedInTrade(offer.getId())); - OfferAvailabilityModel model = getOfferAvailabilityModel(offer, isApiUser); + OfferAvailabilityModel model = getOfferAvailabilityModel(offer, isTakerApiUser); offer.checkOfferAvailability(model, () -> { if (offer.getState() == Offer.State.AVAILABLE) { @@ -466,7 +466,7 @@ private ProcessModel getNewProcessModel(Offer offer) { processModelServiceProvider.getKeyRing().getPubKeyRing()); } - private OfferAvailabilityModel getOfferAvailabilityModel(Offer offer, boolean isApiUser) { + private OfferAvailabilityModel getOfferAvailabilityModel(Offer offer, boolean isTakerApiUser) { return new OfferAvailabilityModel( offer, keyRing.getPubKeyRing(), @@ -474,7 +474,7 @@ private OfferAvailabilityModel getOfferAvailabilityModel(Offer offer, boolean is user, mediatorManager, tradeStatisticsManager, - isApiUser); + isTakerApiUser); } diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index afcd90d210d..55b963de56a 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -158,7 +158,7 @@ message OfferAvailabilityRequest { int64 takers_trade_price = 3; repeated int32 supported_capabilities = 4; string uid = 5; - bool is_api_user = 6; + bool is_taker_api_user = 6; } message OfferAvailabilityResponse { From a49505d0d159d3f6a3954a4d9906a07001af3b24 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 3 Jan 2021 19:03:53 -0500 Subject: [PATCH 27/31] Add Result.API_DISABLED --- core/src/main/java/bisq/core/offer/OfferFilter.java | 8 ++++++-- .../bisq/desktop/main/offer/offerbook/OfferBookView.java | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/bisq/core/offer/OfferFilter.java b/core/src/main/java/bisq/core/offer/OfferFilter.java index a372a5d94fa..cc8451cd84a 100644 --- a/core/src/main/java/bisq/core/offer/OfferFilter.java +++ b/core/src/main/java/bisq/core/offer/OfferFilter.java @@ -70,6 +70,7 @@ public OfferFilter(User user, public enum Result { VALID(true), + API_DISABLED, DENIED_API_TAKER, HAS_NO_PAYMENT_ACCOUNT_VALID_FOR_OFFER, HAS_NOT_SAME_PROTOCOL_VERSION, @@ -94,8 +95,11 @@ public enum Result { } } - public Result canTakeOffer(Offer offer, boolean isApiUser) { - if (isApiUser && offer.getDenyApiTaker()) { + public Result canTakeOffer(Offer offer, boolean isTakerApiUser) { + if (isTakerApiUser && filterManager.getFilter() != null && filterManager.getFilter().isDisableApi()) { + return Result.API_DISABLED; + } + if (isTakerApiUser && offer.getDenyApiTaker()) { return Result.DENIED_API_TAKER; } if (!isAnyPaymentAccountValidForOffer(offer)) { diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java index bc382f44232..276f9d2a538 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java @@ -623,6 +623,7 @@ private void onShowInfo(Offer offer, OfferFilter.Result result) { switch (result) { case VALID: break; + case API_DISABLED: case DENIED_API_TAKER: DevEnv.logErrorAndThrowIfDevMode("We are in desktop and in the taker position " + "viewing offers, so it cannot be that we got denied for the offer as we are not an API user."); From 76022c0d8e7ad267d52abaca73283ebf74187cb1 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 3 Jan 2021 19:27:42 -0500 Subject: [PATCH 28/31] Add missing enum entries to protobuf --- proto/src/main/proto/pb.proto | 2 ++ 1 file changed, 2 insertions(+) diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 55b963de56a..49681858e1b 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -915,6 +915,8 @@ enum AvailabilityResult { USER_IGNORED = 8; MISSING_MANDATORY_CAPABILITY = 9; NO_REFUND_AGENTS = 10; + UNCONF_TX_LIMIT_HIT = 11; + MAKER_DENIED_API_USER = 12; } /////////////////////////////////////////////////////////////////////////////////////////// From 26954153b50458066b3cd5619065cfc5b10234ef Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 3 Jan 2021 19:29:11 -0500 Subject: [PATCH 29/31] Remove OfferPayload.DENY_API_TAKER and use preferences flag instead. If we would store the flag in the offer we would need to update all offers if the preferences flag changes. --- core/src/main/java/bisq/core/offer/Offer.java | 14 -------------- .../src/main/java/bisq/core/offer/OfferFilter.java | 2 +- .../main/java/bisq/core/offer/OfferPayload.java | 1 - .../java/bisq/core/offer/OpenOfferManager.java | 6 +++--- 4 files changed, 4 insertions(+), 19 deletions(-) diff --git a/core/src/main/java/bisq/core/offer/Offer.java b/core/src/main/java/bisq/core/offer/Offer.java index 038919d6be4..963009a0d2b 100644 --- a/core/src/main/java/bisq/core/offer/Offer.java +++ b/core/src/main/java/bisq/core/offer/Offer.java @@ -372,20 +372,6 @@ public String getF2FExtraInfo() { return ""; } - public boolean getDenyApiTaker() { - if (getExtraDataMap() != null && getExtraDataMap().containsKey(OfferPayload.DENY_API_TAKER)) { - return getExtraDataMap().get(OfferPayload.DENY_API_TAKER).equals("1"); - } else { - return false; - } - } - - public void setDenyApiTaker(boolean value) { - if (getExtraDataMap() != null) { - getExtraDataMap().put(OfferPayload.DENY_API_TAKER, value ? "1" : "0"); - } - } - public String getPaymentMethodNameWithCountryCode() { String method = this.getPaymentMethod().getShortName(); String methodCountryCode = this.getCountryCode(); diff --git a/core/src/main/java/bisq/core/offer/OfferFilter.java b/core/src/main/java/bisq/core/offer/OfferFilter.java index cc8451cd84a..2edbfac45bf 100644 --- a/core/src/main/java/bisq/core/offer/OfferFilter.java +++ b/core/src/main/java/bisq/core/offer/OfferFilter.java @@ -99,7 +99,7 @@ public Result canTakeOffer(Offer offer, boolean isTakerApiUser) { if (isTakerApiUser && filterManager.getFilter() != null && filterManager.getFilter().isDisableApi()) { return Result.API_DISABLED; } - if (isTakerApiUser && offer.getDenyApiTaker()) { + if (isTakerApiUser && preferences.isDenyApiTaker()) { return Result.DENIED_API_TAKER; } if (!isAnyPaymentAccountValidForOffer(offer)) { diff --git a/core/src/main/java/bisq/core/offer/OfferPayload.java b/core/src/main/java/bisq/core/offer/OfferPayload.java index 154d9d85032..88b1690c5f1 100644 --- a/core/src/main/java/bisq/core/offer/OfferPayload.java +++ b/core/src/main/java/bisq/core/offer/OfferPayload.java @@ -88,7 +88,6 @@ public static protobuf.OfferPayload.Direction toProtoMessage(Direction direction // If maker is seller and has xmrAutoConf enabled it is set to "1" otherwise it is not set public static final String XMR_AUTO_CONF = "xmrAutoConf"; public static final String XMR_AUTO_CONF_ENABLED_VALUE = "1"; - public static final String DENY_API_TAKER = "denyApiTaker"; // Use boolean value as "0"/"1" /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/offer/OpenOfferManager.java b/core/src/main/java/bisq/core/offer/OpenOfferManager.java index d714a09fa58..3f07eaa1f45 100644 --- a/core/src/main/java/bisq/core/offer/OpenOfferManager.java +++ b/core/src/main/java/bisq/core/offer/OpenOfferManager.java @@ -634,7 +634,7 @@ private void handleOfferAvailabilityRequest(OfferAvailabilityRequest request, No NodeAddress refundAgentNodeAddress = null; if (openOfferOptional.isPresent()) { OpenOffer openOffer = openOfferOptional.get(); - if (!apiUserDeniedByOffer(request, openOffer)) { + if (!apiUserDeniedByOffer(request)) { if (openOffer.getState() == OpenOffer.State.AVAILABLE) { Offer offer = openOffer.getOffer(); if (preferences.getIgnoreTradersList().stream().noneMatch(fullAddress -> fullAddress.equals(peer.getFullAddress()))) { @@ -719,8 +719,8 @@ public void onFault(String errorMessage) { } } - private boolean apiUserDeniedByOffer(OfferAvailabilityRequest request, OpenOffer openOffer) { - return openOffer.getOffer().getDenyApiTaker() && request.isTakerApiUser(); + private boolean apiUserDeniedByOffer(OfferAvailabilityRequest request) { + return preferences.isDenyApiTaker() && request.isTakerApiUser(); } private void sendAckMessage(OfferAvailabilityRequest message, From d4024967e143a928738acbb487e8d4175212bac4 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 3 Jan 2021 19:40:42 -0500 Subject: [PATCH 30/31] Update devMode price (BTC increased 3 time since last update!) --- .../java/bisq/desktop/main/offer/MutableOfferViewModel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java index bdbb89cb331..8d435ede855 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java @@ -236,7 +236,7 @@ public void activate() { if (DevEnv.isDevMode()) { UserThread.runAfter(() -> { amount.set("0.001"); - price.set("70000"); + price.set("210000"); minAmount.set(amount.get()); onFocusOutPriceAsPercentageTextField(true, false); applyMakerFee(); From c68c0edc1aaf252ed1feddf4fa67a44846d27508 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 3 Jan 2021 19:56:08 -0500 Subject: [PATCH 31/31] Remove DENIED_API_TAKER enum. I would not make sense that the API user has set the preferences to not accept API taker for their offers. This was from the older version where we used the offer to carry the flag. @ghubstan Api user cannot see if an maker has set the flag but will receive an error at offer availibility check. --- core/src/main/java/bisq/core/offer/OfferFilter.java | 4 ---- core/src/main/java/bisq/core/user/PreferencesPayload.java | 1 - .../java/bisq/desktop/main/offer/offerbook/OfferBookView.java | 3 +-- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/core/src/main/java/bisq/core/offer/OfferFilter.java b/core/src/main/java/bisq/core/offer/OfferFilter.java index 2edbfac45bf..c22231de5bb 100644 --- a/core/src/main/java/bisq/core/offer/OfferFilter.java +++ b/core/src/main/java/bisq/core/offer/OfferFilter.java @@ -71,7 +71,6 @@ public OfferFilter(User user, public enum Result { VALID(true), API_DISABLED, - DENIED_API_TAKER, HAS_NO_PAYMENT_ACCOUNT_VALID_FOR_OFFER, HAS_NOT_SAME_PROTOCOL_VERSION, IS_IGNORED, @@ -99,9 +98,6 @@ public Result canTakeOffer(Offer offer, boolean isTakerApiUser) { if (isTakerApiUser && filterManager.getFilter() != null && filterManager.getFilter().isDisableApi()) { return Result.API_DISABLED; } - if (isTakerApiUser && preferences.isDenyApiTaker()) { - return Result.DENIED_API_TAKER; - } if (!isAnyPaymentAccountValidForOffer(offer)) { return Result.HAS_NO_PAYMENT_ACCOUNT_VALID_FOR_OFFER; } diff --git a/core/src/main/java/bisq/core/user/PreferencesPayload.java b/core/src/main/java/bisq/core/user/PreferencesPayload.java index 57e00d7e83b..b8991f8cbae 100644 --- a/core/src/main/java/bisq/core/user/PreferencesPayload.java +++ b/core/src/main/java/bisq/core/user/PreferencesPayload.java @@ -132,7 +132,6 @@ public final class PreferencesPayload implements PersistableEnvelope { // Added in 1.5.5 private boolean hideNonAccountPaymentMethods; private boolean showOffersMatchingMyAccounts; - private boolean denyApiTaker; /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java index 276f9d2a538..caa01deea4a 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java @@ -624,9 +624,8 @@ private void onShowInfo(Offer offer, OfferFilter.Result result) { case VALID: break; case API_DISABLED: - case DENIED_API_TAKER: DevEnv.logErrorAndThrowIfDevMode("We are in desktop and in the taker position " + - "viewing offers, so it cannot be that we got denied for the offer as we are not an API user."); + "viewing offers, so it cannot be that we got that result as we are not an API user."); break; case HAS_NO_PAYMENT_ACCOUNT_VALID_FOR_OFFER: openPopupForMissingAccountSetup(Res.get("offerbook.warning.noMatchingAccount.headline"),