diff --git a/core/src/main/java/bisq/core/app/BisqSetup.java b/core/src/main/java/bisq/core/app/BisqSetup.java index b904609a038..42316e88726 100644 --- a/core/src/main/java/bisq/core/app/BisqSetup.java +++ b/core/src/main/java/bisq/core/app/BisqSetup.java @@ -76,7 +76,6 @@ import bisq.common.UserThread; import bisq.common.app.DevEnv; import bisq.common.app.Log; -import bisq.common.config.BaseCurrencyNetwork; import bisq.common.config.Config; import bisq.common.crypto.CryptoException; import bisq.common.crypto.KeyRing; @@ -193,7 +192,7 @@ default void onRequestWalletPassword() { @Setter @Nullable - private Consumer displayTacHandler; + private Consumer displayTacHandler, displayLocalNodeMisconfigurationHandler; @Setter @Nullable private Consumer cryptoSetupFailedHandler, chainFileLockedExceptionHandler, @@ -348,7 +347,7 @@ public void start() { } private void step2() { - detectLocalBitcoinNode(this::step3); + maybeCheckLocalBitcoinNode(this::step3); } private void step3() { @@ -482,14 +481,24 @@ private void maybeShowTac() { } } - private void detectLocalBitcoinNode(Runnable nextStep) { - BaseCurrencyNetwork baseCurrencyNetwork = config.baseCurrencyNetwork; - if (config.ignoreLocalBtcNode || baseCurrencyNetwork.isDaoRegTest() || baseCurrencyNetwork.isDaoTestNet()) { + private void maybeCheckLocalBitcoinNode(Runnable nextStep) { + if (localBitcoinNode.shouldBeIgnored()) { nextStep.run(); return; } - localBitcoinNode.detectAndRun(nextStep); + // Here we only want to provide the user with a choice (in a popup) in case a + // local node is detected, but badly configured. + if (localBitcoinNode.isDetectedButMisconfigured()) { + if (displayLocalNodeMisconfigurationHandler != null) { + displayLocalNodeMisconfigurationHandler.accept(nextStep); + return; + } else { + log.error("displayLocalNodeMisconfigurationHandler undefined", new RuntimeException()); + } + } + + nextStep.run(); } private void readMapsFromResources(Runnable nextStep) { @@ -559,7 +568,8 @@ else if (displayTorNetworkSettingsHandler != null) // We only init wallet service here if not using Tor for bitcoinj. // When using Tor, wallet init must be deferred until Tor is ready. - if (!preferences.getUseTorForBitcoinJ() || localBitcoinNode.isDetected()) { + // TODO encapsulate below conditional inside getUseTorForBitcoinJ + if (!preferences.getUseTorForBitcoinJ() || localBitcoinNode.shouldBeUsed()) { initWallet(); } @@ -862,7 +872,7 @@ private void maybeShowSecurityRecommendation() { } private void maybeShowLocalhostRunningInfo() { - maybeTriggerDisplayHandler("bitcoinLocalhostNode", displayLocalhostHandler, localBitcoinNode.isDetected()); + maybeTriggerDisplayHandler("bitcoinLocalhostNode", displayLocalhostHandler, localBitcoinNode.shouldBeUsed()); } private void maybeShowAccountSigningStateInfo() { diff --git a/core/src/main/java/bisq/core/app/CoreModule.java b/core/src/main/java/bisq/core/app/CoreModule.java index 33e94eebd07..1f058e123a7 100644 --- a/core/src/main/java/bisq/core/app/CoreModule.java +++ b/core/src/main/java/bisq/core/app/CoreModule.java @@ -47,7 +47,6 @@ import java.io.File; import static bisq.common.config.Config.*; -import static bisq.core.btc.nodes.LocalBitcoinNode.LOCAL_BITCOIN_NODE_PORT; import static com.google.inject.name.Names.named; public class CoreModule extends AppModule { @@ -78,10 +77,6 @@ protected void configure() { bindConstant().annotatedWith(named(USE_DEV_MODE)).to(config.useDevMode); bindConstant().annotatedWith(named(REFERRAL_ID)).to(config.referralId); - bindConstant().annotatedWith(named(LOCAL_BITCOIN_NODE_PORT)) - .to(config.baseCurrencyNetworkParameters.getPort()); - - // ordering is used for shut down sequence install(new TradeModule(config)); install(new EncryptionServiceModule(config)); 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 f95d8548e52..aa76144c0df 100644 --- a/core/src/main/java/bisq/core/app/misc/ModuleForAppWithP2p.java +++ b/core/src/main/java/bisq/core/app/misc/ModuleForAppWithP2p.java @@ -50,7 +50,6 @@ import java.io.File; import static bisq.common.config.Config.*; -import static bisq.core.btc.nodes.LocalBitcoinNode.LOCAL_BITCOIN_NODE_PORT; import static com.google.inject.name.Names.named; public class ModuleForAppWithP2p extends AppModule { @@ -82,9 +81,6 @@ protected void configure() { bindConstant().annotatedWith(named(USE_DEV_MODE)).to(config.useDevMode); bindConstant().annotatedWith(named(REFERRAL_ID)).to(config.referralId); - bindConstant().annotatedWith(named(LOCAL_BITCOIN_NODE_PORT)) - .to(config.baseCurrencyNetworkParameters.getPort()); - // ordering is used for shut down sequence install(new TradeModule(config)); install(new EncryptionServiceModule(config)); diff --git a/core/src/main/java/bisq/core/btc/nodes/LocalBitcoinNode.java b/core/src/main/java/bisq/core/btc/nodes/LocalBitcoinNode.java index 7de869c8f8b..fcb250a07ae 100644 --- a/core/src/main/java/bisq/core/btc/nodes/LocalBitcoinNode.java +++ b/core/src/main/java/bisq/core/btc/nodes/LocalBitcoinNode.java @@ -1,63 +1,351 @@ package bisq.core.btc.nodes; +import bisq.common.config.BaseCurrencyNetwork; +import bisq.common.config.Config; + +import org.bitcoinj.core.Context; +import org.bitcoinj.core.Peer; +import org.bitcoinj.core.PeerAddress; +import org.bitcoinj.core.VersionMessage; +import org.bitcoinj.core.listeners.PeerDisconnectedEventListener; +import org.bitcoinj.net.NioClient; +import org.bitcoinj.net.NioClientManager; + import javax.inject.Inject; -import javax.inject.Named; import javax.inject.Singleton; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.SettableFuture; + +import java.net.InetAddress; import java.net.InetSocketAddress; -import java.net.Socket; +import java.net.UnknownHostException; import java.io.IOException; +import java.util.Optional; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ch.qos.logback.classic.Level; + +import org.jetbrains.annotations.NotNull; + /** - * Detects whether a Bitcoin node is running on localhost. + * Detects whether a Bitcoin node is running on localhost and whether it is well + * configured (meaning it's not pruning and has bloom filters enabled). The public + * methods automatically trigger detection and (if detected) configuration checks, + * and cache the results, and subsequent queries to {@link LocalBitcoinNode} will always + * return the cached results. * @see bisq.common.config.Config#ignoreLocalBtcNode */ @Singleton public class LocalBitcoinNode { - public static final String LOCAL_BITCOIN_NODE_PORT = "localBitcoinNodePort"; - private static final Logger log = LoggerFactory.getLogger(LocalBitcoinNode.class); private static final int CONNECTION_TIMEOUT = 5000; + private final Config config; private final int port; - private boolean detected = false; + + private Boolean detected; + private Boolean wellConfigured; @Inject - public LocalBitcoinNode(@Named(LOCAL_BITCOIN_NODE_PORT) int port) { - this.port = port; + public LocalBitcoinNode(Config config) { + this.config = config; + this.port = config.baseCurrencyNetworkParameters.getPort(); } /** - * Detect whether a Bitcoin node is running on localhost by attempting to connect - * to the node's port and run the given callback regardless of whether the connection - * was successful. If the connection is successful, subsequent calls to - * {@link #isDetected()} will return {@code true}. - * @param callback code to run after detecting whether the node is running + * Returns whether Bisq should use a local Bitcoin node, meaning that a usable node + * was detected and conditions under which it should be ignored have not been met. If + * the local node should be ignored, a call to this method will not trigger an + * unnecessary detection attempt. */ - public void detectAndRun(Runnable callback) { - try (Socket socket = new Socket()) { - socket.connect(new InetSocketAddress("127.0.0.1", port), CONNECTION_TIMEOUT); - log.info("Local Bitcoin node detected on port {}", port); + public boolean shouldBeUsed() { + return !shouldBeIgnored() && isUsable(); + } + + /** + * Returns whether Bisq will ignore a local Bitcoin node even if it is usable. + */ + public boolean shouldBeIgnored() { + BaseCurrencyNetwork baseCurrencyNetwork = config.baseCurrencyNetwork; + + // For dao testnet (server side regtest) we disable the use of local bitcoin node + // to avoid confusion if local btc node is not synced with our dao testnet master + // node. Note: above comment was previously in WalletConfig::createPeerGroup. + return config.ignoreLocalBtcNode || + baseCurrencyNetwork.isDaoRegTest() || + baseCurrencyNetwork.isDaoTestNet(); + } + + /** + * Returns whether or not a local Bitcoin node was detected and was well-configured + * at the time the checks were performed. All checks are triggered in case they have + * not been performed. + */ + private boolean isUsable() { + // If a node is found to be well configured, it implies that it was also detected, + // so this is query is enough to show if the relevant checks were performed and if + // their results are positive. + return isWellConfigured(); + } + + /** + * Returns whether a local node was detected but misconfigured. + */ + public boolean isDetectedButMisconfigured() { + return isDetected() && !isWellConfigured(); + } + + /** + * Returns whether a local Bitcoin node was detected. All checks are triggered in case + * they have not been performed. No further monitoring is performed, so if the node + * goes up or down in the meantime, this method will continue to return its original + * value. See {@code MainViewModel#setupBtcNumPeersWatcher} to understand how + * disconnection and reconnection of the local Bitcoin node is actually handled. + */ + private boolean isDetected() { + if (detected == null) { + performChecks(); + } + return detected; + } + + /** + * Returns whether the local node's configuration satisfied our checks at the time + * they were performed. All checks are triggered in case they have not been performed. + * We check if the local node is not pruning and has bloom filters enabled. + */ + private boolean isWellConfigured() { + if (wellConfigured == null) { + performChecks(); + } + return wellConfigured; + } + + /** + * Performs checks that the query methods might be interested in. + */ + private void performChecks() { + checkUsable(); + } + + /** + * Initiates detection and configuration checks. The results are cached so that the + * {@link #isUsable()}, {@link #isDetected()} et al don't trigger a recheck. + */ + private void checkUsable() { + var optionalVersionMessage = attemptHandshakeForVersionMessage(); + handleHandshakeAttempt(optionalVersionMessage); + } + + private void handleHandshakeAttempt(Optional optionalVersionMessage) { + if (!optionalVersionMessage.isPresent()) { + detected = false; + wellConfigured = false; + log.info("No local Bitcoin node detected on port {}, or the connection was prematurely closed" + + " (before a version messages could be coerced)", port); + } else { detected = true; + log.info("Local Bitcoin node detected on port {}", port); + + var versionMessage = optionalVersionMessage.get(); + var configurationCheckResult = checkWellConfigured(versionMessage); + + if (configurationCheckResult) { + wellConfigured = true; + log.info("Local Bitcoin node found to be well configured (not pruning and allows bloom filters)"); + } else { + wellConfigured = false; + log.info("Local Bitcoin node badly configured (it is pruning and/or bloom filters are disabled)"); + } + } + } + + private static boolean checkWellConfigured(VersionMessage versionMessage) { + var notPruning = versionMessage.hasBlockChain(); + var supportsAndAllowsBloomFilters = + isBloomFilteringSupportedAndEnabled(versionMessage); + return notPruning && supportsAndAllowsBloomFilters; + } + + /** + * Method backported from upstream bitcoinj: at the time of writing, our version is + * not BIP111-aware. Source routines and data can be found in bitcoinj under: + * core/src/main/java/org/bitcoinj/core/VersionMessage.java + * and core/src/main/java/org/bitcoinj/core/NetworkParameters.java + */ + @SuppressWarnings("UnnecessaryLocalVariable") + private static boolean isBloomFilteringSupportedAndEnabled(VersionMessage versionMessage) { + // A service bit that denotes whether the peer supports BIP37 bloom filters or + // not. The service bit is defined in BIP111. + int NODE_BLOOM = 1 << 2; + + int BLOOM_FILTERS_BIP37_PROTOCOL_VERSION = 70000; + var whenBloomFiltersWereIntroduced = BLOOM_FILTERS_BIP37_PROTOCOL_VERSION; + + int BLOOM_FILTERS_BIP111_PROTOCOL_VERSION = 70011; + var whenBloomFiltersWereDisabledByDefault = BLOOM_FILTERS_BIP111_PROTOCOL_VERSION; + + int clientVersion = versionMessage.clientVersion; + long localServices = versionMessage.localServices; + + if (clientVersion >= whenBloomFiltersWereIntroduced + && clientVersion < whenBloomFiltersWereDisabledByDefault) + return true; + + return (localServices & NODE_BLOOM) == NODE_BLOOM; + } + + /** + * Performs a blocking Bitcoin protocol handshake, which includes exchanging version + * messages and acks. Its purpose is to check if a local Bitcoin node is running, + * and, if it is, check its advertised configuration. The returned Optional is empty, + * if a local peer wasn't found, or if handshake failed for some reason. This method + * could be noticably simplified, by turning connection failure callback into a + * future and using a first-future-to-complete type of construct, but I couldn't find + * a ready-made implementation. + */ + private Optional attemptHandshakeForVersionMessage() { + Peer peer; + try { + peer = createLocalPeer(port); + } catch (UnknownHostException ex) { + log.error("Local bitcoin node handshake attempt was unexpectedly interrupted", ex); + return Optional.empty(); + } + + // We temporarily silence BitcoinJ NioClient's and NioClientManager's loggers, + // because when a local Bitcoin node is not found they pollute console output + // with "connection refused" error messages. + var originalNioClientLoggerLevel = silence(NioClient.class); + var originalNioClientManagerLoggerLevel = silence(NioClientManager.class); + + NioClient client; + try { + log.info("Initiating attempt to connect to and handshake with a local " + + "Bitcoin node (which may or may not be running) on port {}.", port); + client = createClient(peer, port, CONNECTION_TIMEOUT); } catch (IOException ex) { - log.info("No local Bitcoin node detected on port {}.", port); + log.error("Local bitcoin node handshake attempt was unexpectedly interrupted", ex); + return Optional.empty(); } - callback.run(); + + ListenableFuture peerVersionMessageFuture = getVersionMessage(peer); + Optional optionalPeerVersionMessage; + + // block for VersionMessage or cancellation (in case of connection failure) + try { + var peerVersionMessage = peerVersionMessageFuture.get(); + optionalPeerVersionMessage = Optional.of(peerVersionMessage); + } catch (ExecutionException | InterruptedException | CancellationException ex) { + optionalPeerVersionMessage = Optional.empty(); + } + + peer.close(); + client.closeConnection(); + + restoreLoggerLevel(NioClient.class, originalNioClientLoggerLevel); + restoreLoggerLevel(NioClientManager.class, originalNioClientManagerLoggerLevel); + + return optionalPeerVersionMessage; } /** - * Returns whether or not a Bitcoin node was running on localhost at the time - * {@link #detectAndRun(Runnable)} was called. No further monitoring is performed, so - * if the node goes up or down in the meantime, this method will continue to return - * its original value. See {@code MainViewModel#setupBtcNumPeersWatcher} to understand - * how disconnection and reconnection of the local Bitcoin node is actually handled. + * Creates a Peer that is expected to only be used to coerce a VersionMessage out of a + * local Bitcoin node and be closed right after. */ - public boolean isDetected() { - return detected; + private Peer createLocalPeer(int port) throws UnknownHostException { + var networkParameters = config.baseCurrencyNetwork.getParameters(); + + // We must construct a BitcoinJ Context before using BitcoinJ. We don't keep a + // reference, because it's automatically kept in a thread local storage. + new Context(networkParameters); + + var ourVersionMessage = new VersionMessage(networkParameters, 0); + + var localPeerAddress = new PeerAddress(InetAddress.getLocalHost(), port); + + return new Peer(networkParameters, ourVersionMessage, localPeerAddress, null); + } + + /** + * Creates an NioClient that is expected to only be used to coerce a VersionMessage + * out of a local Bitcoin node and be closed right after. + */ + private static NioClient createClient(Peer peer, int port, int connectionTimeout) throws IOException { + InetSocketAddress serverAddress = new InetSocketAddress(InetAddress.getLocalHost(), port); + + // This initiates the handshake procedure, which, if successful, will complete + // the peerVersionMessageFuture, or be cancelled, in case of failure. + return new NioClient(serverAddress, peer, connectionTimeout); + } + + private static Level silence(Class klass) { + var logger = getLogger(klass); + var originalLevel = logger.getLevel(); + logger.setLevel(Level.OFF); + return originalLevel; + } + + private static void restoreLoggerLevel(Class klass, Level originalLevel) { + getLogger(klass).setLevel(originalLevel); + } + + private static ch.qos.logback.classic.Logger getLogger(Class klass) { + return (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(klass); + } + + private ListenableFuture getVersionMessage(Peer peer) { + SettableFuture peerVersionMessageFuture = SettableFuture.create(); + + var versionHandshakeDone = peer.getVersionHandshakeFuture(); + FutureCallback fetchPeerVersionMessage = new FutureCallback<>() { + public void onSuccess(Peer peer) { + peerVersionMessageFuture.set(peer.getPeerVersionMessage()); + } + + public void onFailure(@NotNull Throwable thr) { + // No action + } + }; + Futures.addCallback(versionHandshakeDone, fetchPeerVersionMessage); + + PeerDisconnectedEventListener cancelIfConnectionFails = + (Peer disconnectedPeer, int peerCount) -> { + var peerVersionMessageAlreadyReceived = + peerVersionMessageFuture.isDone(); + if (peerVersionMessageAlreadyReceived) { + // This method is called whether or not the handshake was + // successful. In case it was successful, we don't want to do + // anything here. + return; + } + // In some cases Peer will self-disconnect after receiving + // node's VersionMessage, but before completing the handshake. + // In such a case, we want to retrieve the VersionMessage. + var peerVersionMessage = disconnectedPeer.getPeerVersionMessage(); + if (peerVersionMessage != null) { + log.info("Handshake attempt was interrupted; " + + "however, the local node's version message was coerced."); + peerVersionMessageFuture.set(peerVersionMessage); + } else { + log.info("Handshake attempt did not result in a version message exchange."); + peerVersionMessageFuture.cancel(true); + } + }; + + // Cancel peerVersionMessageFuture if connection failed + peer.addDisconnectedEventListener(cancelIfConnectionFails); + + return peerVersionMessageFuture; } } diff --git a/core/src/main/java/bisq/core/btc/setup/WalletConfig.java b/core/src/main/java/bisq/core/btc/setup/WalletConfig.java index 237d8709b97..5c105f9ed6e 100644 --- a/core/src/main/java/bisq/core/btc/setup/WalletConfig.java +++ b/core/src/main/java/bisq/core/btc/setup/WalletConfig.java @@ -239,11 +239,7 @@ private PeerGroup createPeerGroup() { peerGroup.setConnectTimeoutMillis(TOR_VERSION_EXCHANGE_TIMEOUT); } - // For dao testnet (server side regtest) we prevent to connect to a localhost node to avoid confusion - // if local btc node is not synced with our dao testnet master node. - if (Config.baseCurrencyNetwork().isDaoRegTest() || - Config.baseCurrencyNetwork().isDaoTestNet() || - !localBitcoinNode.isDetected()) + if (!localBitcoinNode.shouldBeUsed()) peerGroup.setUseLocalhostPeerWhenPossible(false); return peerGroup; diff --git a/core/src/main/java/bisq/core/btc/setup/WalletsSetup.java b/core/src/main/java/bisq/core/btc/setup/WalletsSetup.java index 8c420366c2a..722799f451a 100644 --- a/core/src/main/java/bisq/core/btc/setup/WalletsSetup.java +++ b/core/src/main/java/bisq/core/btc/setup/WalletsSetup.java @@ -278,7 +278,7 @@ protected void onSetupCompleted() { return; } } - } else if (localBitcoinNode.isDetected()) { + } else if (localBitcoinNode.shouldBeUsed()) { walletConfig.setMinBroadcastConnections(1); walletConfig.setPeerNodesForLocalHost(); } else { diff --git a/core/src/main/java/bisq/core/user/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java index 08d9e0edbaa..077d221bc6c 100644 --- a/core/src/main/java/bisq/core/user/Preferences.java +++ b/core/src/main/java/bisq/core/user/Preferences.java @@ -736,11 +736,13 @@ public boolean showAgain(String key) { } public boolean getUseTorForBitcoinJ() { - // We override the useTorForBitcoinJ and set it to false if we detected a localhost node or if we are not on mainnet, - // unless the useTorForBtc parameter is explicitly provided. - // On testnet there are very few Bitcoin tor nodes and we don't provide tor nodes. + // We override the useTorForBitcoinJ and set it to false if we will use a + // localhost Bitcoin node or if we are not on mainnet, unless the useTorForBtc + // parameter is explicitly provided. On testnet there are very few Bitcoin tor + // nodes and we don't provide tor nodes. + if ((!Config.baseCurrencyNetwork().isMainnet() - || localBitcoinNode.isDetected()) + || localBitcoinNode.shouldBeUsed()) && !config.useTorForBtcOptionSetExplicitly) return false; else diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 863975684a2..c3ed45a1cc5 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -2662,9 +2662,13 @@ popup.privateNotification.headline=Important private notification! popup.securityRecommendation.headline=Important security recommendation popup.securityRecommendation.msg=We would like to remind you to consider using password protection for your wallet if you have not already enabled that.\n\nIt is also highly recommended to write down the wallet seed words. Those seed words are like a master password for recovering your Bitcoin wallet.\nAt the \"Wallet Seed\" section you find more information.\n\nAdditionally you should backup the complete application data folder at the \"Backup\" section. -popup.bitcoinLocalhostNode.msg=Bisq detected a locally running Bitcoin Core node (at localhost).\n\ - Please make sure that this node is fully synced before you start Bisq and that it is not running in pruned mode. -popup.bitcoinLocalhostNode.additionalRequirements=\n\nStarting with Bitcoin Core v0.19 you have to manually enable bloom filters by setting peerbloomfilters=1 in your bitcoin.conf configuration file. +popup.warning.localNodeMisconfigured.explanation=Bisq detected a locally running Bitcoin Core node (at localhost); however, its configuration is incompatible with Bisq.\n\n\ +For Bisq to use a local Bitcoin node, the node has to have pruning disabled and bloom filters enabled. Starting with Bitcoin Core v0.19 you have to manually enable bloom filters by setting peerbloomfilters=1 in your bitcoin.conf configuration file. +popup.warning.localNodeMisconfigured.continueWithoutLocalNode=Continue without using local node + +popup.bitcoinLocalhostNode.msg=Bisq detected a locally running Bitcoin Core node (at localhost) and is using it.\n\ + Please make sure that this node is fully synced before you start Bisq. +popup.bitcoinLocalhostNode.additionalRequirements=\n\nThe node was found to be well configured. For reference, the requirements are for the node to have pruning disabled and bloom filters enabled. popup.shutDownInProgress.headline=Shut down in progress popup.shutDownInProgress.msg=Shutting down application can take a few seconds.\nPlease don't interrupt this process. diff --git a/core/src/test/java/bisq/core/btc/nodes/LocalBitcoinNodeTests.java b/core/src/test/java/bisq/core/btc/nodes/LocalBitcoinNodeTests.java deleted file mode 100644 index 9233263fd75..00000000000 --- a/core/src/test/java/bisq/core/btc/nodes/LocalBitcoinNodeTests.java +++ /dev/null @@ -1,45 +0,0 @@ -package bisq.core.btc.nodes; - -import java.net.ServerSocket; - -import java.io.IOException; - -import java.util.concurrent.atomic.AtomicBoolean; - -import org.junit.Before; -import org.junit.Test; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -public class LocalBitcoinNodeTests { - - private AtomicBoolean called = new AtomicBoolean(false); - private Runnable callback = () -> called.set(true); - private ServerSocket socket; - private LocalBitcoinNode localBitcoinNode; - - @Before - public void setUp() throws IOException { - // Bind to and listen on an available port - socket = new ServerSocket(0); - localBitcoinNode = new LocalBitcoinNode(socket.getLocalPort()); - } - - @Test - public void whenLocalBitcoinNodeIsDetected_thenCallbackGetsRun_andIsDetectedReturnsTrue() { - // Continue listening on the port, indicating the local Bitcoin node is running - localBitcoinNode.detectAndRun(callback); - assertTrue(called.get()); - assertTrue(localBitcoinNode.isDetected()); - } - - @Test - public void whenLocalBitcoinNodeIsNotDetected_thenCallbackGetsRun_andIsDetectedReturnsFalse() throws IOException { - // Stop listening on the port, indicating the local Bitcoin node is not running - socket.close(); - localBitcoinNode.detectAndRun(callback); - assertTrue(called.get()); - assertFalse(localBitcoinNode.isDetected()); - } -} diff --git a/core/src/test/java/bisq/core/user/PreferencesTest.java b/core/src/test/java/bisq/core/user/PreferencesTest.java index 4c8f89b95de..b29ea9520c1 100644 --- a/core/src/test/java/bisq/core/user/PreferencesTest.java +++ b/core/src/test/java/bisq/core/user/PreferencesTest.java @@ -60,7 +60,7 @@ public void setUp() { storage = mock(Storage.class); Config config = new Config(); - LocalBitcoinNode localBitcoinNode = new LocalBitcoinNode(config.baseCurrencyNetworkParameters.getPort()); + LocalBitcoinNode localBitcoinNode = new LocalBitcoinNode(config); preferences = new Preferences( storage, config, localBitcoinNode, null, null, Config.DEFAULT_FULL_DAO_NODE, null, null, Config.UNSPECIFIED_PORT); diff --git a/desktop/src/main/java/bisq/desktop/main/MainViewModel.java b/desktop/src/main/java/bisq/desktop/main/MainViewModel.java index 25586aa284e..0766f96c43c 100644 --- a/desktop/src/main/java/bisq/desktop/main/MainViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/MainViewModel.java @@ -305,6 +305,16 @@ private void setupHandlers() { else torNetworkSettingsWindow.hide(); }); + bisqSetup.setDisplayLocalNodeMisconfigurationHandler( + (Runnable continueWithoutLocalNode) -> + new Popup() + .hideCloseButton() + .warning(Res.get("popup.warning.localNodeMisconfigured.explanation")) + .useShutDownButton() + .secondaryActionButtonText(Res.get("popup.warning.localNodeMisconfigured.continueWithoutLocalNode")) + .onSecondaryAction(continueWithoutLocalNode) + .show() + ); bisqSetup.setSpvFileCorruptedHandler(msg -> new Popup().warning(msg) .actionButtonText(Res.get("settings.net.reSyncSPVChainButton")) .onAction(() -> GUIUtil.reSyncSPVChain(preferences)) @@ -441,10 +451,14 @@ private void setupBtcNumPeersWatcher() { checkNumberOfBtcPeersTimer = UserThread.runAfter(() -> { // check again numPeers if (walletsSetup.numPeersProperty().get() == 0) { - if (localBitcoinNode.isDetected()) - getWalletServiceErrorMsg().set(Res.get("mainView.networkWarning.localhostBitcoinLost", Res.getBaseCurrencyName().toLowerCase())); + if (localBitcoinNode.shouldBeUsed()) + getWalletServiceErrorMsg().set( + Res.get("mainView.networkWarning.localhostBitcoinLost", + Res.getBaseCurrencyName().toLowerCase())); else - getWalletServiceErrorMsg().set(Res.get("mainView.networkWarning.allConnectionsLost", Res.getBaseCurrencyName().toLowerCase())); + getWalletServiceErrorMsg().set( + Res.get("mainView.networkWarning.allConnectionsLost", + Res.getBaseCurrencyName().toLowerCase())); } else { getWalletServiceErrorMsg().set(null); } diff --git a/desktop/src/main/java/bisq/desktop/main/settings/network/NetworkSettingsView.java b/desktop/src/main/java/bisq/desktop/main/settings/network/NetworkSettingsView.java index 5b18283d9e7..416edc6a02d 100644 --- a/desktop/src/main/java/bisq/desktop/main/settings/network/NetworkSettingsView.java +++ b/desktop/src/main/java/bisq/desktop/main/settings/network/NetworkSettingsView.java @@ -165,7 +165,7 @@ public void initialize() { bitcoinPeerSubVersionColumn.setGraphic(new AutoTooltipLabel(Res.get("settings.net.subVersionColumn"))); bitcoinPeerHeightColumn.setGraphic(new AutoTooltipLabel(Res.get("settings.net.heightColumn"))); localhostBtcNodeInfoLabel.setText(Res.get("settings.net.localhostBtcNodeInfo")); - if (!localBitcoinNode.isDetected()) { + if (localBitcoinNode.shouldBeIgnored()) { localhostBtcNodeInfoLabel.setVisible(false); } useProvidedNodesRadio.setText(Res.get("settings.net.useProvidedNodesRadio")); @@ -380,14 +380,14 @@ private void showShutDownPopup() { } private void onBitcoinPeersToggleSelected(boolean calledFromUser) { - boolean bitcoinLocalhostNodeRunning = localBitcoinNode.isDetected(); - useTorForBtcJCheckBox.setDisable(bitcoinLocalhostNodeRunning); - bitcoinNodesLabel.setDisable(bitcoinLocalhostNodeRunning); - btcNodesLabel.setDisable(bitcoinLocalhostNodeRunning); - btcNodesInputTextField.setDisable(bitcoinLocalhostNodeRunning); - useProvidedNodesRadio.setDisable(bitcoinLocalhostNodeRunning || !btcNodes.useProvidedBtcNodes()); - useCustomNodesRadio.setDisable(bitcoinLocalhostNodeRunning); - usePublicNodesRadio.setDisable(bitcoinLocalhostNodeRunning || isPreventPublicBtcNetwork()); + boolean localBitcoinNodeShouldBeUsed = localBitcoinNode.shouldBeUsed(); + useTorForBtcJCheckBox.setDisable(localBitcoinNodeShouldBeUsed); + bitcoinNodesLabel.setDisable(localBitcoinNodeShouldBeUsed); + btcNodesLabel.setDisable(localBitcoinNodeShouldBeUsed); + btcNodesInputTextField.setDisable(localBitcoinNodeShouldBeUsed); + useProvidedNodesRadio.setDisable(localBitcoinNodeShouldBeUsed || !btcNodes.useProvidedBtcNodes()); + useCustomNodesRadio.setDisable(localBitcoinNodeShouldBeUsed); + usePublicNodesRadio.setDisable(localBitcoinNodeShouldBeUsed || isPreventPublicBtcNetwork()); BtcNodes.BitcoinNodesOption currentBitcoinNodesOption = BtcNodes.BitcoinNodesOption.values()[preferences.getBitcoinNodesOptionOrdinal()]; @@ -454,7 +454,7 @@ private void onBitcoinPeersToggleSelected(boolean calledFromUser) { private void applyPreventPublicBtcNetwork() { final boolean preventPublicBtcNetwork = isPreventPublicBtcNetwork(); - usePublicNodesRadio.setDisable(localBitcoinNode.isDetected() || preventPublicBtcNetwork); + usePublicNodesRadio.setDisable(localBitcoinNode.shouldBeUsed() || preventPublicBtcNetwork); if (preventPublicBtcNetwork && selectedBitcoinNodesOption == BtcNodes.BitcoinNodesOption.PUBLIC) { selectedBitcoinNodesOption = BtcNodes.BitcoinNodesOption.PROVIDED; preferences.setBitcoinNodesOptionOrdinal(selectedBitcoinNodesOption.ordinal());