diff --git a/common/src/main/java/io/bitsquare/common/OptionKeys.java b/common/src/main/java/io/bitsquare/common/CommonOptionKeys.java similarity index 83% rename from common/src/main/java/io/bitsquare/common/OptionKeys.java rename to common/src/main/java/io/bitsquare/common/CommonOptionKeys.java index 50c66cd6b89..ff4e2c491c6 100644 --- a/common/src/main/java/io/bitsquare/common/OptionKeys.java +++ b/common/src/main/java/io/bitsquare/common/CommonOptionKeys.java @@ -1,6 +1,6 @@ package io.bitsquare.common; -public class OptionKeys { +public class CommonOptionKeys { public static final String LOG_LEVEL_KEY = "logLevel"; public static final String IGNORE_DEV_MSG_KEY = "ignoreDevMsg"; } diff --git a/core/src/main/java/io/bitsquare/alert/AlertManager.java b/core/src/main/java/io/bitsquare/alert/AlertManager.java index 6bcb59f4ca3..32a6dd3ef22 100644 --- a/core/src/main/java/io/bitsquare/alert/AlertManager.java +++ b/core/src/main/java/io/bitsquare/alert/AlertManager.java @@ -19,7 +19,7 @@ import com.google.inject.Inject; import com.google.inject.name.Named; -import io.bitsquare.common.OptionKeys; +import io.bitsquare.common.CommonOptionKeys; import io.bitsquare.common.crypto.KeyRing; import io.bitsquare.p2p.P2PService; import io.bitsquare.p2p.storage.HashMapChangedListener; @@ -56,7 +56,7 @@ public class AlertManager { /////////////////////////////////////////////////////////////////////////////////////////// @Inject - public AlertManager(P2PService p2PService, KeyRing keyRing, User user, @Named(OptionKeys.IGNORE_DEV_MSG_KEY) boolean ignoreDevMsg) { + public AlertManager(P2PService p2PService, KeyRing keyRing, User user, @Named(CommonOptionKeys.IGNORE_DEV_MSG_KEY) boolean ignoreDevMsg) { this.p2PService = p2PService; this.keyRing = keyRing; this.user = user; diff --git a/core/src/main/java/io/bitsquare/alert/PrivateNotificationManager.java b/core/src/main/java/io/bitsquare/alert/PrivateNotificationManager.java index d653bd2d763..ca8d73aa0a7 100644 --- a/core/src/main/java/io/bitsquare/alert/PrivateNotificationManager.java +++ b/core/src/main/java/io/bitsquare/alert/PrivateNotificationManager.java @@ -19,7 +19,7 @@ import com.google.inject.Inject; import com.google.inject.name.Named; -import io.bitsquare.common.OptionKeys; +import io.bitsquare.common.CommonOptionKeys; import io.bitsquare.common.crypto.KeyRing; import io.bitsquare.crypto.DecryptedMsgWithPubKey; import io.bitsquare.p2p.Message; @@ -58,7 +58,7 @@ public class PrivateNotificationManager { /////////////////////////////////////////////////////////////////////////////////////////// @Inject - public PrivateNotificationManager(P2PService p2PService, KeyRing keyRing, @Named(OptionKeys.IGNORE_DEV_MSG_KEY) boolean ignoreDevMsg) { + public PrivateNotificationManager(P2PService p2PService, KeyRing keyRing, @Named(CommonOptionKeys.IGNORE_DEV_MSG_KEY) boolean ignoreDevMsg) { this.p2PService = p2PService; this.keyRing = keyRing; diff --git a/core/src/main/java/io/bitsquare/app/BitsquareEnvironment.java b/core/src/main/java/io/bitsquare/app/BitsquareEnvironment.java index e00edd87fd8..fa1d91bb557 100644 --- a/core/src/main/java/io/bitsquare/app/BitsquareEnvironment.java +++ b/core/src/main/java/io/bitsquare/app/BitsquareEnvironment.java @@ -20,11 +20,13 @@ import ch.qos.logback.classic.Level; import io.bitsquare.BitsquareException; import io.bitsquare.btc.BitcoinNetwork; +import io.bitsquare.btc.BtcOptionKeys; import io.bitsquare.btc.UserAgent; import io.bitsquare.btc.WalletService; +import io.bitsquare.common.CommonOptionKeys; import io.bitsquare.common.crypto.KeyStorage; import io.bitsquare.common.util.Utilities; -import io.bitsquare.network.OptionKeys; +import io.bitsquare.network.NetworkOptionKeys; import io.bitsquare.storage.Storage; import io.bitsquare.util.spring.JOptCommandLinePropertySource; import joptsimple.OptionSet; @@ -82,8 +84,7 @@ public static void setDefaultAppName(String defaultAppName) { private final String btcNetworkDir; private final String logLevel; private BitcoinNetwork bitcoinNetwork; - private final String seedNodes, ignoreDevMsg; - private final String myAddress, banList; + private final String btcSeedNodes, seedNodes, ignoreDevMsg, useTorForBtc, myAddress, banList; public BitsquareEnvironment(OptionSet options) { this(new JOptCommandLinePropertySource(BITSQUARE_COMMANDLINE_PROPERTY_SOURCE_NAME, checkNotNull( @@ -132,25 +133,34 @@ protected BitsquareEnvironment(PropertySource commandLineProperties) { (String) commandLineProperties.getProperty(APP_DATA_DIR_KEY) : appDataDir(userDataDir, appName); - logLevel = commandLineProperties.containsProperty(io.bitsquare.common.OptionKeys.LOG_LEVEL_KEY) ? - (String) commandLineProperties.getProperty(io.bitsquare.common.OptionKeys.LOG_LEVEL_KEY) : + logLevel = commandLineProperties.containsProperty(CommonOptionKeys.LOG_LEVEL_KEY) ? + (String) commandLineProperties.getProperty(CommonOptionKeys.LOG_LEVEL_KEY) : LOG_LEVEL_DEFAULT; - seedNodes = commandLineProperties.containsProperty(OptionKeys.SEED_NODES_KEY) ? - (String) commandLineProperties.getProperty(OptionKeys.SEED_NODES_KEY) : + seedNodes = commandLineProperties.containsProperty(NetworkOptionKeys.SEED_NODES_KEY) ? + (String) commandLineProperties.getProperty(NetworkOptionKeys.SEED_NODES_KEY) : ""; - myAddress = commandLineProperties.containsProperty(OptionKeys.MY_ADDRESS) ? - (String) commandLineProperties.getProperty(OptionKeys.MY_ADDRESS) : + myAddress = commandLineProperties.containsProperty(NetworkOptionKeys.MY_ADDRESS) ? + (String) commandLineProperties.getProperty(NetworkOptionKeys.MY_ADDRESS) : ""; - banList = commandLineProperties.containsProperty(OptionKeys.BAN_LIST) ? - (String) commandLineProperties.getProperty(OptionKeys.BAN_LIST) : + banList = commandLineProperties.containsProperty(NetworkOptionKeys.BAN_LIST) ? + (String) commandLineProperties.getProperty(NetworkOptionKeys.BAN_LIST) : ""; - ignoreDevMsg = commandLineProperties.containsProperty(io.bitsquare.common.OptionKeys.IGNORE_DEV_MSG_KEY) ? - (String) commandLineProperties.getProperty(io.bitsquare.common.OptionKeys.IGNORE_DEV_MSG_KEY) : + ignoreDevMsg = commandLineProperties.containsProperty(CommonOptionKeys.IGNORE_DEV_MSG_KEY) ? + (String) commandLineProperties.getProperty(CommonOptionKeys.IGNORE_DEV_MSG_KEY) : ""; + btcSeedNodes = commandLineProperties.containsProperty(BtcOptionKeys.BTC_SEED_NODES) ? + (String) commandLineProperties.getProperty(BtcOptionKeys.BTC_SEED_NODES) : + ""; + + useTorForBtc = commandLineProperties.containsProperty(BtcOptionKeys.USE_TOR_FOR_BTC) ? + (String) commandLineProperties.getProperty(BtcOptionKeys.USE_TOR_FOR_BTC) : + ""; + + MutablePropertySources propertySources = this.getPropertySources(); propertySources.addFirst(commandLineProperties); try { @@ -206,12 +216,15 @@ private PropertySource defaultProperties() { { setProperty(APP_DATA_DIR_KEY, appDataDir); - setProperty(io.bitsquare.common.OptionKeys.LOG_LEVEL_KEY, logLevel); + setProperty(CommonOptionKeys.LOG_LEVEL_KEY, logLevel); + + setProperty(NetworkOptionKeys.SEED_NODES_KEY, seedNodes); + setProperty(NetworkOptionKeys.MY_ADDRESS, myAddress); + setProperty(NetworkOptionKeys.BAN_LIST, banList); + setProperty(CommonOptionKeys.IGNORE_DEV_MSG_KEY, ignoreDevMsg); - setProperty(OptionKeys.SEED_NODES_KEY, seedNodes); - setProperty(OptionKeys.MY_ADDRESS, myAddress); - setProperty(OptionKeys.BAN_LIST, banList); - setProperty(io.bitsquare.common.OptionKeys.IGNORE_DEV_MSG_KEY, ignoreDevMsg); + setProperty(BtcOptionKeys.BTC_SEED_NODES, btcSeedNodes); + setProperty(BtcOptionKeys.USE_TOR_FOR_BTC, useTorForBtc); setProperty(APP_NAME_KEY, appName); setProperty(USER_DATA_DIR_KEY, userDataDir); @@ -223,9 +236,9 @@ private PropertySource defaultProperties() { setProperty(Storage.DIR_KEY, Paths.get(btcNetworkDir, "db").toString()); setProperty(KeyStorage.DIR_KEY, Paths.get(btcNetworkDir, "keys").toString()); - setProperty(OptionKeys.TOR_DIR, Paths.get(btcNetworkDir, "tor").toString()); + setProperty(NetworkOptionKeys.TOR_DIR, Paths.get(btcNetworkDir, "tor").toString()); - setProperty(OptionKeys.NETWORK_ID, String.valueOf(bitcoinNetwork.ordinal())); + setProperty(NetworkOptionKeys.NETWORK_ID, String.valueOf(bitcoinNetwork.ordinal())); } }); } diff --git a/core/src/main/java/io/bitsquare/app/BitsquareExecutable.java b/core/src/main/java/io/bitsquare/app/BitsquareExecutable.java index dd5e75cceb8..8f46771cb78 100644 --- a/core/src/main/java/io/bitsquare/app/BitsquareExecutable.java +++ b/core/src/main/java/io/bitsquare/app/BitsquareExecutable.java @@ -19,8 +19,10 @@ import io.bitsquare.BitsquareException; import io.bitsquare.btc.BitcoinNetwork; +import io.bitsquare.btc.BtcOptionKeys; import io.bitsquare.btc.RegTestHost; -import io.bitsquare.network.OptionKeys; +import io.bitsquare.common.CommonOptionKeys; +import io.bitsquare.network.NetworkOptionKeys; import io.bitsquare.p2p.P2PService; import io.bitsquare.util.joptsimple.EnumValueConverter; import joptsimple.OptionException; @@ -74,29 +76,34 @@ protected void customizeOptionParsing(OptionParser parser) { .withRequiredArg(); parser.accepts(APP_DATA_DIR_KEY, description("Application data directory", DEFAULT_APP_DATA_DIR)) .withRequiredArg(); - parser.accepts(io.bitsquare.common.OptionKeys.LOG_LEVEL_KEY, description("Log level [OFF, ALL, ERROR, WARN, INFO, DEBUG, TRACE]", LOG_LEVEL_DEFAULT)) + parser.accepts(CommonOptionKeys.LOG_LEVEL_KEY, description("Log level [OFF, ALL, ERROR, WARN, INFO, DEBUG, TRACE]", LOG_LEVEL_DEFAULT)) .withRequiredArg(); - parser.accepts(OptionKeys.SEED_NODES_KEY, description("Override hard coded seed nodes as comma separated list: E.g. rxdkppp3vicnbgqt.onion:8002, mfla72c4igh5ta2t.onion:8002", "")) + parser.accepts(NetworkOptionKeys.SEED_NODES_KEY, description("Override hard coded seed nodes as comma separated list: E.g. rxdkppp3vicnbgqt.onion:8002, mfla72c4igh5ta2t.onion:8002", "")) .withRequiredArg(); - parser.accepts(OptionKeys.MY_ADDRESS, description("My own onion address (used for botstrap nodes to exclude itself)", "")) + parser.accepts(NetworkOptionKeys.MY_ADDRESS, description("My own onion address (used for botstrap nodes to exclude itself)", "")) .withRequiredArg(); - parser.accepts(OptionKeys.BAN_LIST, description("Nodes to exclude from network connections.", "")) + parser.accepts(NetworkOptionKeys.BAN_LIST, description("Nodes to exclude from network connections.", "")) .withRequiredArg(); - parser.accepts(io.bitsquare.common.OptionKeys.IGNORE_DEV_MSG_KEY, description("If set to true all signed messages from Bitsquare developers are ignored " + + parser.accepts(CommonOptionKeys.IGNORE_DEV_MSG_KEY, description("If set to true all signed messages from Bitsquare developers are ignored " + "(Global alert, Version update alert, Filters for offers, nodes or payment account data)", false)) .withRequiredArg() .ofType(boolean.class); + parser.accepts(BtcOptionKeys.BTC_SEED_NODES, description("Custom seed nodes used for BitcoinJ.", "")) + .withRequiredArg(); + parser.accepts(BtcOptionKeys.USE_TOR_FOR_BTC, description("If set to true BitcoinJ is routed over our native Tor instance.", "")) + .withRequiredArg(); + // use a fixed port as arbitrator use that for his ID - parser.accepts(OptionKeys.PORT_KEY, description("Port to listen on", 9999)) + parser.accepts(NetworkOptionKeys.PORT_KEY, description("Port to listen on", 9999)) .withRequiredArg() .ofType(int.class); - parser.accepts(OptionKeys.USE_LOCALHOST, description("Use localhost network for development", false)) + parser.accepts(NetworkOptionKeys.USE_LOCALHOST, description("Use localhost network for development", false)) .withRequiredArg() .ofType(boolean.class); - parser.accepts(OptionKeys.MAX_CONNECTIONS, description("Max. connections a peer will try to keep", P2PService.MAX_CONNECTIONS_DEFAULT)) + parser.accepts(NetworkOptionKeys.MAX_CONNECTIONS, description("Max. connections a peer will try to keep", P2PService.MAX_CONNECTIONS_DEFAULT)) .withRequiredArg() .ofType(int.class); parser.accepts(BitcoinNetwork.KEY, description("Bitcoin network", BitcoinNetwork.DEFAULT)) diff --git a/core/src/main/java/io/bitsquare/btc/BitcoinModule.java b/core/src/main/java/io/bitsquare/btc/BitcoinModule.java index 9342570216e..421e7bed084 100644 --- a/core/src/main/java/io/bitsquare/btc/BitcoinModule.java +++ b/core/src/main/java/io/bitsquare/btc/BitcoinModule.java @@ -48,6 +48,9 @@ protected void configure() { File walletDir = new File(env.getRequiredProperty(WalletService.DIR_KEY)); bind(File.class).annotatedWith(named(WalletService.DIR_KEY)).toInstance(walletDir); + bindConstant().annotatedWith(named(BtcOptionKeys.BTC_SEED_NODES)).to(env.getRequiredProperty(BtcOptionKeys.BTC_SEED_NODES)); + bindConstant().annotatedWith(named(BtcOptionKeys.USE_TOR_FOR_BTC)).to(env.getRequiredProperty(BtcOptionKeys.USE_TOR_FOR_BTC)); + bind(AddressEntryList.class).in(Singleton.class); bind(TradeWalletService.class).in(Singleton.class); bind(WalletService.class).in(Singleton.class); diff --git a/core/src/main/java/io/bitsquare/btc/BtcOptionKeys.java b/core/src/main/java/io/bitsquare/btc/BtcOptionKeys.java new file mode 100644 index 00000000000..04231ac4f70 --- /dev/null +++ b/core/src/main/java/io/bitsquare/btc/BtcOptionKeys.java @@ -0,0 +1,6 @@ +package io.bitsquare.btc; + +public class BtcOptionKeys { + public static final String BTC_SEED_NODES = "btcSeedNodes"; + public static final String USE_TOR_FOR_BTC = "useTorForBtc"; +} diff --git a/core/src/main/java/io/bitsquare/btc/ProxySocketFactory.java b/core/src/main/java/io/bitsquare/btc/ProxySocketFactory.java new file mode 100644 index 00000000000..0126530e6ea --- /dev/null +++ b/core/src/main/java/io/bitsquare/btc/ProxySocketFactory.java @@ -0,0 +1,94 @@ + +/** + * Copyright (C) 2010-2014 Leon Blakey + * + * This file is part of PircBotX. + * + * PircBotX is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * PircBotX 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * PircBotX. If not, see . + */ +package io.bitsquare.btc; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.Socket; +import java.net.UnknownHostException; +import javax.net.SocketFactory; + +/** + * A basic SocketFactory for creating sockets that connect through the specified + * proxy. + * + * @author Leon Blakey + */ +public class ProxySocketFactory extends SocketFactory { + protected final Proxy proxy; + + /** + * Create all sockets with the specified proxy. + * + * @param proxy An existing proxy + */ + public ProxySocketFactory(Proxy proxy) { + this.proxy = proxy; + } + + /** + * A convenience constructor for creating a proxy with the specified host + * and port. + * + * @param proxyType The type of proxy were connecting to + * @param hostname The hostname of the proxy server + * @param port The port of the proxy server + */ + public ProxySocketFactory(Proxy.Type proxyType, String hostname, int port) { + this.proxy = new Proxy(proxyType, new InetSocketAddress(hostname, port)); + } + + @Override + public Socket createSocket() throws IOException { + Socket socket = new Socket(proxy); + return socket; + } + + @Override + public Socket createSocket(String string, int i) throws IOException, UnknownHostException { + Socket socket = new Socket(proxy); + socket.connect(new InetSocketAddress(string, i)); + return socket; + } + + @Override + public Socket createSocket(String string, int i, InetAddress localAddress, int localPort) throws IOException, UnknownHostException { + Socket socket = new Socket(proxy); + socket.bind(new InetSocketAddress(localAddress, localPort)); + socket.connect(new InetSocketAddress(string, i)); + return socket; + } + + @Override + public Socket createSocket(InetAddress ia, int i) throws IOException { + Socket socket = new Socket(proxy); + socket.connect(new InetSocketAddress(ia, i)); + return socket; + } + + @Override + public Socket createSocket(InetAddress ia, int i, InetAddress localAddress, int localPort) throws IOException { + Socket socket = new Socket(proxy); + socket.bind(new InetSocketAddress(localAddress, localPort)); + socket.connect(new InetSocketAddress(ia, i)); + return socket; + } +} \ No newline at end of file diff --git a/core/src/main/java/io/bitsquare/btc/SeedPeersSocks5Dns.java b/core/src/main/java/io/bitsquare/btc/SeedPeersSocks5Dns.java new file mode 100644 index 00000000000..2cd292c1591 --- /dev/null +++ b/core/src/main/java/io/bitsquare/btc/SeedPeersSocks5Dns.java @@ -0,0 +1,178 @@ +/** + * Copyright 2011 Micheal Swiggs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.bitsquare.btc; + +import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy; +import com.runjva.sourceforge.jsocks.protocol.SocksSocket; + +import org.bitcoinj.net.*; +import org.bitcoinj.net.discovery.PeerDiscovery; +import org.bitcoinj.net.discovery.PeerDiscoveryException; + +import org.bitcoinj.core.NetworkParameters; +import javax.annotation.Nullable; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * SeedPeersSocks5Dns resolves peers via Proxy (Socks5) remote DNS. + */ +public class SeedPeersSocks5Dns implements PeerDiscovery { + private Socks5Proxy proxy; + private NetworkParameters params; + private InetSocketAddress[] seedAddrs; + private InetSocketAddress[] seedAddrsIP; + private int pnseedIndex; + + private InetSocketAddress[] seedAddrsResolved; + + private static final Logger log = LoggerFactory.getLogger(SeedPeersSocks5Dns.class); + + /** + * Supports finding peers by hostname over a socks5 proxy. + * + * @param Socks5Proxy proxy the socks5 proxy to connect over. + * @param NetworkParameters param to be used for seed and port information. + */ + public SeedPeersSocks5Dns(Socks5Proxy proxy, NetworkParameters params) { + + this.proxy = proxy; + this.params = params; + this.seedAddrs = convertAddrsString( params.getDnsSeeds(), params.getPort() ); + + if( false ) { + // This is an example of how .onion servers could be used. Unfortunately there is presently no way + // to hand the onion address (or a connected socket) back to bitcoinj without it crashing in PeerAddress. + // note: the onion addresses should be added into bitcoinj NetworkParameters classes, eg for mainnet, testnet + // not here! + this.seedAddrs = new InetSocketAddress[] { InetSocketAddress.createUnresolved( "cajrifqkvalh2ooa.onion", 8333 ), + InetSocketAddress.createUnresolved( "bk7yp6epnmcllq72.onion", 8333 ) + }; + } + + seedAddrsResolved = new InetSocketAddress[seedAddrs.length]; + for(int idx = seedAddrs.length; idx < seedAddrsResolved.length; idx ++) { + seedAddrsResolved[idx] = seedAddrsIP[idx-seedAddrs.length]; + } + } + + /** + * Acts as an iterator, returning the address of each node in the list sequentially. + * Once all the list has been iterated, null will be returned for each subsequent query. + * + * @return InetSocketAddress - The address/port of the next node. + * @throws PeerDiscoveryException + */ + @Nullable + public InetSocketAddress getPeer() throws PeerDiscoveryException { + try { + return nextPeer(); + } catch (UnknownHostException e) { + throw new PeerDiscoveryException(e); + } + } + + /** + * worker for getPeer() + */ + @Nullable + private InetSocketAddress nextPeer() throws UnknownHostException, PeerDiscoveryException { + if (seedAddrs == null || seedAddrs.length == 0) { + throw new PeerDiscoveryException("No IP address seeds configured; unable to find any peers"); + } + + if (pnseedIndex >= seedAddrsResolved.length) { + return null; + } + if( seedAddrsResolved[pnseedIndex] == null ) { + seedAddrsResolved[pnseedIndex] = lookup( proxy, seedAddrs[pnseedIndex] ); + } + log.error("SeedPeersSocks5Dns::nextPeer: " + seedAddrsResolved[pnseedIndex] ); + + return seedAddrsResolved[pnseedIndex++]; + } + + /** + * Returns an array containing all the Bitcoin nodes within the list. + */ + @Override + public InetSocketAddress[] getPeers(long timeoutValue, TimeUnit timeoutUnit) throws PeerDiscoveryException { + try { + return allPeers(); + } catch (UnknownHostException e) { + throw new PeerDiscoveryException(e); + } + } + + /** + * returns all seed peers, performs hostname lookups if necessary. + */ + private InetSocketAddress[] allPeers() throws UnknownHostException { + for (int i = 0; i < seedAddrsResolved.length; ++i) { + if( seedAddrsResolved[i] == null ) { + seedAddrsResolved[i] = lookup( proxy, seedAddrs[i] ); + } + } + return seedAddrsResolved; + } + + /** + * Resolves a hostname via remote DNS over socks5 proxy. + */ + public static InetSocketAddress lookup( Socks5Proxy proxy, InetSocketAddress addr ) { + if( !addr.isUnresolved() ) { + return addr; + } + try { + SocksSocket proxySocket = new SocksSocket( proxy, addr.getHostString(), addr.getPort() ); + InetAddress addrResolved = proxySocket.getInetAddress(); + proxySocket.close(); + if( addrResolved != null ) { + log.info("Resolved " + addr.getHostString() + " to " + addrResolved.getHostAddress() ); + return new InetSocketAddress(addrResolved, addr.getPort() ); + } + else { + // note: .onion nodes fall in here when proxy is Tor. But they have no IP address. + // Unfortunately bitcoinj crashes in PeerAddress if it finds an unresolved address. + log.error("Connected to " + addr.getHostString() + ". But did not resolve to address." ); + } + } catch (Exception e) { + log.error("Error resolving " + addr.getHostString() + ". Exception:\n" + e.toString() ); + } + return null; + } + + /** + * Converts an array of hostnames to array of unresolved InetSocketAddress + */ + private InetSocketAddress[] convertAddrsString(String[] addrs, int port) { + InetSocketAddress[] list = new InetSocketAddress[addrs.length]; + for( int i = 0; i < addrs.length; i++) { + list[i] = InetSocketAddress.createUnresolved(addrs[i], port); + } + return list; + } + + @Override + public void shutdown() { + } +} diff --git a/core/src/main/java/io/bitsquare/btc/WalletAppKitBitSquare.java b/core/src/main/java/io/bitsquare/btc/WalletAppKitBitSquare.java new file mode 100644 index 00000000000..ce79460221f --- /dev/null +++ b/core/src/main/java/io/bitsquare/btc/WalletAppKitBitSquare.java @@ -0,0 +1,74 @@ +/* + * This file is part of Bitsquare. + * + * Bitsquare 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. + * + * Bitsquare 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 Bitsquare. If not, see . + */ + +package io.bitsquare.btc; + +import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy; + +import org.bitcoinj.core.NetworkParameters; +import org.bitcoinj.kits.WalletAppKit; +import org.bitcoinj.net.BlockingClientManager; +import org.bitcoinj.core.PeerGroup; + +import java.io.File; +import java.net.Proxy; +import java.net.InetSocketAddress; +import java.util.concurrent.TimeoutException; + +public class WalletAppKitBitSquare extends WalletAppKit { + private Socks5Proxy socks5Proxy; + + /** + * Creates a new WalletAppKit, with a newly created {@link Context}. Files will be stored in the given directory. + */ + public WalletAppKitBitSquare(NetworkParameters params, Socks5Proxy socks5Proxy, File directory, String filePrefix) { + super(params, directory, filePrefix); + this.socks5Proxy = socks5Proxy; + } + + public Socks5Proxy getProxy() { + return socks5Proxy; + } + + protected PeerGroup createPeerGroup() throws TimeoutException { + + // no proxy case. + if(socks5Proxy == null) { + return super.createPeerGroup(); + } + + // proxy case. + Proxy proxy = new Proxy ( Proxy.Type.SOCKS, + new InetSocketAddress(socks5Proxy.getInetAddress().getHostName(), + socks5Proxy.getPort() ) ); + + int CONNECT_TIMEOUT_MSEC = 60 * 1000; // same value used in bitcoinj. + ProxySocketFactory proxySocketFactory = new ProxySocketFactory(proxy); + BlockingClientManager mgr = new BlockingClientManager(proxySocketFactory); + PeerGroup peerGroup = new PeerGroup(params, vChain, mgr); + + mgr.setConnectTimeoutMillis(CONNECT_TIMEOUT_MSEC); + peerGroup.setConnectTimeoutMillis(CONNECT_TIMEOUT_MSEC); + + // This enables remote DNS lookup of peers over socks5 proxy. + // It is slower, but more private. + // This could be turned into a user option. + this.setDiscovery( new SeedPeersSocks5Dns(socks5Proxy, params) ); + + return peerGroup; + } +} diff --git a/core/src/main/java/io/bitsquare/btc/WalletService.java b/core/src/main/java/io/bitsquare/btc/WalletService.java index 1c338326dbf..e27c2e0887e 100644 --- a/core/src/main/java/io/bitsquare/btc/WalletService.java +++ b/core/src/main/java/io/bitsquare/btc/WalletService.java @@ -22,6 +22,7 @@ import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Service; +import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy; import io.bitsquare.btc.listeners.AddressConfidenceListener; import io.bitsquare.btc.listeners.BalanceListener; import io.bitsquare.btc.listeners.TxConfidenceListener; @@ -30,6 +31,7 @@ import io.bitsquare.common.handlers.ErrorMessageHandler; import io.bitsquare.common.handlers.ExceptionHandler; import io.bitsquare.common.handlers.ResultHandler; +import io.bitsquare.p2p.NodeAddress; import io.bitsquare.storage.FileUtil; import io.bitsquare.storage.Storage; import io.bitsquare.user.Preferences; @@ -54,6 +56,7 @@ import java.io.File; import java.io.IOException; import java.net.InetAddress; +import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.nio.file.Paths; import java.util.*; @@ -85,12 +88,13 @@ public class WalletService { private final RegTestHost regTestHost; private final TradeWalletService tradeWalletService; private final AddressEntryList addressEntryList; + private final String seedNodes; private final NetworkParameters params; private final File walletDir; private final UserAgent userAgent; private final boolean useTor; - private WalletAppKit walletAppKit; + private WalletAppKitBitSquare walletAppKit; private Wallet wallet; private final IntegerProperty numPeers = new SimpleIntegerProperty(0); private final ObjectProperty> connectedPeers = new SimpleObjectProperty<>(); @@ -105,14 +109,30 @@ public class WalletService { /////////////////////////////////////////////////////////////////////////////////////////// @Inject - public WalletService(RegTestHost regTestHost, TradeWalletService tradeWalletService, AddressEntryList addressEntryList, UserAgent userAgent, - @Named(DIR_KEY) File appDir, Preferences preferences) { + public WalletService(RegTestHost regTestHost, + TradeWalletService tradeWalletService, + AddressEntryList addressEntryList, + UserAgent userAgent, + @Named(DIR_KEY) File appDir, + Preferences preferences, + @Named(BtcOptionKeys.BTC_SEED_NODES) String seedNodes, + @Named(BtcOptionKeys.USE_TOR_FOR_BTC) String useTorFlagFromOptions) { this.regTestHost = regTestHost; this.tradeWalletService = tradeWalletService; this.addressEntryList = addressEntryList; + this.seedNodes = seedNodes; this.params = preferences.getBitcoinNetwork().getParameters(); this.walletDir = new File(appDir, "bitcoin"); this.userAgent = userAgent; + + // We support a checkbox in the settings to set the use tor flag. + // If we get the options set we override that setting. + if (useTorFlagFromOptions != null && !useTorFlagFromOptions.isEmpty()) { + if (useTorFlagFromOptions.equals("false")) + preferences.setUseTorForBitcoinJ(false); + else if (useTorFlagFromOptions.equals("true")) + preferences.setUseTorForBitcoinJ(true); + } useTor = preferences.getUseTorForBitcoinJ(); storage = new Storage<>(walletDir); @@ -130,7 +150,7 @@ public WalletService(RegTestHost regTestHost, TradeWalletService tradeWalletServ // Public Methods /////////////////////////////////////////////////////////////////////////////////////////// - public void initialize(@Nullable DeterministicSeed seed, ResultHandler resultHandler, ExceptionHandler exceptionHandler) { + public void initialize(@Nullable DeterministicSeed seed, Socks5Proxy proxy, ResultHandler resultHandler, ExceptionHandler exceptionHandler) { // Tell bitcoinj to execute event handlers on the JavaFX UI thread. This keeps things simple and means // we cannot forget to switch threads when adding event handlers. Unfortunately, the DownloadListener // we give to the app kit is currently an exception and runs on a library thread. It'll get fixed in @@ -146,7 +166,7 @@ public void initialize(@Nullable DeterministicSeed seed, ResultHandler resultHan backupWallet(); // If seed is non-null it means we are restoring from backup. - walletAppKit = new WalletAppKit(params, walletDir, "Bitsquare") { + walletAppKit = new WalletAppKitBitSquare(params, proxy, walletDir, "Bitsquare") { @Override protected void onSetupCompleted() { // Don't make the user wait for confirmations for now, as the intention is they're sending it @@ -247,11 +267,52 @@ public List getData(Peer peer, GetDataMessage m) { // 1333 / (2800 + 1333) = 0.32 -> 32 % probability that a pub key is in our wallet walletAppKit.setBloomFilterFalsePositiveRate(0.00005); + log.debug( "seedNodes: " + seedNodes.toString() ); + + // Pass custom seed nodes if set in options + if (seedNodes != null && !seedNodes.isEmpty()) { + + // todo: this parsing should be more robust, + // give validation error if needed. + // also: it seems the peer nodes can be overridden in the case + // of regtest mode below. is that wanted? + String[] nodes = seedNodes.split(","); + List peerAddressList = new ArrayList(); + for(String node : nodes) { + String[] parts = node.split(":"); + if( parts.length == 2 ) { + // note: this will cause a DNS request if hostname used. + // note: DNS requests are routed over socks5 proxy, if used. + // fixme: .onion hostnames will fail! see comments in SeedPeersSocks5Dns + InetSocketAddress addr; + if( proxy != null ) { + InetSocketAddress unresolved = InetSocketAddress.createUnresolved(parts[0], Integer.parseInt(parts[1])); + // proxy remote DNS request happens here. + addr = SeedPeersSocks5Dns.lookup( proxy, unresolved ); + } + else { + // DNS request happens here. if it fails, addr.isUnresolved() == true. + addr = new InetSocketAddress( parts[0], Integer.parseInt(parts[1]) ); + } + // note: isUnresolved check should be removed once we fix PeerAddress + if( addr != null && !addr.isUnresolved() ) { + peerAddressList.add( new PeerAddress( addr.getAddress(), addr.getPort() )); + } + } + } + if(peerAddressList.size() > 0) { + PeerAddress peerAddressListFixed[] = new PeerAddress[peerAddressList.size()]; + log.debug( "seedNodes parsed: " + peerAddressListFixed.toString() ); + + walletAppKit.setPeerNodes(peerAddressList.toArray(peerAddressListFixed)); + } + } - // TODO Get bitcoinj running over our tor proxy. BlockingClientManager need to be used to use the socket - // from jtorproxy. To get supported it via nio / netty will be harder - if (useTor && params.getId().equals(NetworkParameters.ID_MAINNET)) - walletAppKit.useTor(); + // We do not call walletAppKit.useTor() anymore because that would turn + // on orchid Tor, which we do not want. Instead, we create a Tor proxy + // later. + // if (useTor && params.getId().equals(NetworkParameters.ID_MAINNET)) + // walletAppKit.useTor(); // Now configure and start the appkit. This will take a second or two - we could show a temporary splash screen // or progress widget to keep the user engaged whilst we initialise, but we don't. @@ -319,7 +380,7 @@ public void restoreSeedWords(DeterministicSeed seed, ResultHandler resultHandler Context.propagate(ctx); walletAppKit.stopAsync(); walletAppKit.awaitTerminated(); - initialize(seed, resultHandler, exceptionHandler); + initialize(seed, walletAppKit.getProxy(), resultHandler, exceptionHandler); } catch (Throwable t) { t.printStackTrace(); log.error("Executing task failed. " + t.getMessage()); diff --git a/core/src/main/java/io/bitsquare/filter/FilterManager.java b/core/src/main/java/io/bitsquare/filter/FilterManager.java index ee131d8c312..b7c72a24341 100644 --- a/core/src/main/java/io/bitsquare/filter/FilterManager.java +++ b/core/src/main/java/io/bitsquare/filter/FilterManager.java @@ -19,7 +19,7 @@ import com.google.inject.Inject; import com.google.inject.name.Named; -import io.bitsquare.common.OptionKeys; +import io.bitsquare.common.CommonOptionKeys; import io.bitsquare.common.crypto.KeyRing; import io.bitsquare.common.util.Tuple3; import io.bitsquare.common.util.Utilities; @@ -59,7 +59,7 @@ public class FilterManager { /////////////////////////////////////////////////////////////////////////////////////////// @Inject - public FilterManager(P2PService p2PService, KeyRing keyRing, User user, @Named(OptionKeys.IGNORE_DEV_MSG_KEY) boolean ignoreDevMsg) { + public FilterManager(P2PService p2PService, KeyRing keyRing, User user, @Named(CommonOptionKeys.IGNORE_DEV_MSG_KEY) boolean ignoreDevMsg) { this.p2PService = p2PService; this.keyRing = keyRing; this.user = user; diff --git a/core/src/main/java/io/bitsquare/filter/FilterModule.java b/core/src/main/java/io/bitsquare/filter/FilterModule.java index c7b90ddae4a..4d1acc5411f 100644 --- a/core/src/main/java/io/bitsquare/filter/FilterModule.java +++ b/core/src/main/java/io/bitsquare/filter/FilterModule.java @@ -19,7 +19,7 @@ import com.google.inject.Singleton; import io.bitsquare.app.AppModule; -import io.bitsquare.common.OptionKeys; +import io.bitsquare.common.CommonOptionKeys; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.env.Environment; @@ -36,6 +36,6 @@ public FilterModule(Environment env) { @Override protected final void configure() { bind(FilterManager.class).in(Singleton.class); - bindConstant().annotatedWith(named(OptionKeys.IGNORE_DEV_MSG_KEY)).to(env.getRequiredProperty(OptionKeys.IGNORE_DEV_MSG_KEY)); + bindConstant().annotatedWith(named(CommonOptionKeys.IGNORE_DEV_MSG_KEY)).to(env.getRequiredProperty(CommonOptionKeys.IGNORE_DEV_MSG_KEY)); } } diff --git a/core/src/main/java/io/bitsquare/user/Preferences.java b/core/src/main/java/io/bitsquare/user/Preferences.java index 141704b031f..94dbcb9c62c 100644 --- a/core/src/main/java/io/bitsquare/user/Preferences.java +++ b/core/src/main/java/io/bitsquare/user/Preferences.java @@ -111,8 +111,7 @@ public static TradeCurrency getDefaultTradeCurrency() { private boolean autoSelectArbitrators = true; private final Map dontShowAgainMap; private boolean tacAccepted; - // Don't remove as we don't want to break old serialized data - private boolean useTorForBitcoinJ = false; + private boolean useTorForBitcoinJ = true; private boolean showOwnOffersInOfferBook = true; private Locale preferredLocale; private TradeCurrency preferredTradeCurrency; @@ -185,8 +184,7 @@ public Preferences(Storage storage, BitsquareEnvironment bitsquareE defaultLocale = preferredLocale; preferredTradeCurrency = persisted.getPreferredTradeCurrency(); defaultTradeCurrency = preferredTradeCurrency; - // useTorForBitcoinJ = persisted.getUseTorForBitcoinJ(); - useTorForBitcoinJ = false; + useTorForBitcoinJ = persisted.getUseTorForBitcoinJ(); useStickyMarketPrice = persisted.getUseStickyMarketPrice(); usePercentageBasedPrice = persisted.getUsePercentageBasedPrice(); showOwnOffersInOfferBook = persisted.getShowOwnOffersInOfferBook(); @@ -372,10 +370,10 @@ public void setNonTradeTxFeePerKB(long nonTradeTxFeePerKB) throws Exception { storage.queueUpForSave(); } - /* public void setUseTorForBitcoinJ(boolean useTorForBitcoinJ) { + public void setUseTorForBitcoinJ(boolean useTorForBitcoinJ) { this.useTorForBitcoinJ = useTorForBitcoinJ; storage.queueUpForSave(); - }*/ + } public void setShowOwnOffersInOfferBook(boolean showOwnOffersInOfferBook) { this.showOwnOffersInOfferBook = showOwnOffersInOfferBook; diff --git a/gui/src/main/java/io/bitsquare/app/BitsquareApp.java b/gui/src/main/java/io/bitsquare/app/BitsquareApp.java index 92e37ccd383..927f977e38b 100644 --- a/gui/src/main/java/io/bitsquare/app/BitsquareApp.java +++ b/gui/src/main/java/io/bitsquare/app/BitsquareApp.java @@ -24,7 +24,7 @@ import io.bitsquare.alert.AlertManager; import io.bitsquare.arbitration.ArbitratorManager; import io.bitsquare.btc.WalletService; -import io.bitsquare.common.OptionKeys; +import io.bitsquare.common.CommonOptionKeys; import io.bitsquare.common.UserThread; import io.bitsquare.common.handlers.ResultHandler; import io.bitsquare.common.util.Utilities; @@ -109,7 +109,7 @@ public void start(Stage stage) throws IOException { log.info("Log files under: " + logPath); Version.printVersion(); Utilities.printSysInfo(); - Log.setLevel(Level.toLevel(env.getRequiredProperty(OptionKeys.LOG_LEVEL_KEY))); + Log.setLevel(Level.toLevel(env.getRequiredProperty(CommonOptionKeys.LOG_LEVEL_KEY))); UserThread.setExecutor(Platform::runLater); UserThread.setTimerClass(UITimer.class); @@ -346,20 +346,21 @@ private void showFPSWindow() { @Override public void stop() { - shutDownRequested = true; - - new Popup().headLine("Shut down in progress") - .backgroundInfo("Shutting down application can take a few seconds.\n" + - "Please don't interrupt that process.") - .hideCloseButton() - .useAnimation(false) - .show(); - UserThread.runAfter(() -> { - gracefulShutDown(() -> { - log.info("App shutdown complete"); - System.exit(0); - }); - }, 200, TimeUnit.MILLISECONDS); + if (!shutDownRequested) { + new Popup().headLine("Shut down in progress") + .backgroundInfo("Shutting down application can take a few seconds.\n" + + "Please don't interrupt that process.") + .hideCloseButton() + .useAnimation(false) + .show(); + UserThread.runAfter(() -> { + gracefulShutDown(() -> { + log.info("App shutdown complete"); + System.exit(0); + }); + }, 200, TimeUnit.MILLISECONDS); + shutDownRequested = true; + } } private void gracefulShutDown(ResultHandler resultHandler) { @@ -378,8 +379,8 @@ private void gracefulShutDown(ResultHandler resultHandler) { injector.getInstance(WalletService.class).shutDown(); }); }); - // we wait max 5 sec. - UserThread.runAfter(resultHandler::handleResult, 5); + // we wait max 20 sec. + UserThread.runAfter(resultHandler::handleResult, 20); } else { UserThread.runAfter(resultHandler::handleResult, 1); } diff --git a/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java b/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java index ec6973611e4..486eb8d374c 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java @@ -18,6 +18,7 @@ package io.bitsquare.gui.main; import com.google.inject.Inject; +import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy; import io.bitsquare.alert.Alert; import io.bitsquare.alert.AlertManager; import io.bitsquare.alert.PrivateNotification; @@ -55,6 +56,7 @@ import io.bitsquare.gui.util.BSFormatter; import io.bitsquare.locale.CurrencyUtil; import io.bitsquare.locale.TradeCurrency; +import io.bitsquare.p2p.NodeAddress; import io.bitsquare.p2p.P2PService; import io.bitsquare.p2p.P2PServiceListener; import io.bitsquare.p2p.network.CloseConnectionReason; @@ -234,8 +236,8 @@ public void initializeAllServices() { showStartupTimeoutPopup(); }, 4, TimeUnit.MINUTES); - walletInitialized = initBitcoinWallet(); p2pNetWorkReady = initP2PNetwork(); + walletInitialized = initBitcoinWallet(); // need to store it to not get garbage collected allServicesDone = EasyBind.combine(walletInitialized, p2pNetWorkReady, (a, b) -> a && b); @@ -348,6 +350,9 @@ public void onError(Throwable throwable) { public void onTorNodeReady() { bootstrapState.set("Tor node created"); p2PNetworkIconId.set("image-connection-tor"); + if( preferences.getUseTorForBitcoinJ() ) { + initWalletService(); + } } @Override @@ -422,6 +427,18 @@ public void onRequestCustomBridges(Runnable resultHandler) { } private BooleanProperty initBitcoinWallet() { + final BooleanProperty walletInitialized = new SimpleBooleanProperty(); + + // 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() ) { + initWalletService(); + } + + return walletInitialized; + } + + private void initWalletService() { ObjectProperty walletServiceException = new SimpleObjectProperty<>(); btcInfoBinding = EasyBind.combine(walletService.downloadPercentageProperty(), walletService.numPeersProperty(), walletServiceException, (downloadPercentage, numPeers, exception) -> { @@ -461,9 +478,26 @@ private BooleanProperty initBitcoinWallet() { btcInfoBinding.subscribe((observable, oldValue, newValue) -> { btcInfo.set(newValue); }); + + Socks5Proxy proxy = null; + + if( preferences.getUseTorForBitcoinJ() ) { + // Use p2p service + proxy = p2PService.getNetworkNode().getSocksProxy(); + } + +/** + * Uncomment this to wire up user specified proxy via program args or config file. + * Could be Tor, i2p, ssh, vpn, etc. + if( preferences.getBitcoinProxyHost() != null && + preferences.getBitcoinProxyPort() != null ) { + proxy = new Socks5Proxy( preferences.getBitcoinProxyHost(), + preferences.getBitcoinProxyPort() ); + } +*/ - final BooleanProperty walletInitialized = new SimpleBooleanProperty(); walletService.initialize(null, + proxy, () -> { numBtcPeers = walletService.numPeersProperty().get(); @@ -484,7 +518,6 @@ private BooleanProperty initBitcoinWallet() { } }, walletServiceException::set); - return walletInitialized; } private void onAllServicesInitialized() { diff --git a/gui/src/main/java/io/bitsquare/gui/main/settings/network/NetworkSettingsView.fxml b/gui/src/main/java/io/bitsquare/gui/main/settings/network/NetworkSettingsView.fxml index 44006d185e4..e39a9bddc87 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/settings/network/NetworkSettingsView.fxml +++ b/gui/src/main/java/io/bitsquare/gui/main/settings/network/NetworkSettingsView.fxml @@ -34,14 +34,14 @@ - -