diff --git a/app/bundle/src/main/scala/org/bitcoins/bundle/gui/BitcoindConfigPane.scala b/app/bundle/src/main/scala/org/bitcoins/bundle/gui/BitcoindConfigPane.scala index 40a9e8f8dc..18b1265301 100644 --- a/app/bundle/src/main/scala/org/bitcoins/bundle/gui/BitcoindConfigPane.scala +++ b/app/bundle/src/main/scala/org/bitcoins/bundle/gui/BitcoindConfigPane.scala @@ -53,6 +53,8 @@ class BitcoindConfigPane( minWidth = 300 } + private val torCheckBox: CheckBox = new CheckBox() + private var nextRow: Int = 0 val gridPane: GridPane = new GridPane() { @@ -80,6 +82,10 @@ class BitcoindConfigPane( add(new Label("Bitcoin Core Version"), 0, nextRow) add(versionComboBox, 1, nextRow) nextRow += 1 + + add(new Label("Use Tor"), 0, nextRow) + add(torCheckBox, 1, nextRow) + nextRow += 1 } val launchButton: Button = new Button("Launch Wallet") { @@ -94,7 +100,14 @@ class BitcoindConfigPane( } def getConfig: Config = { - val configStr = + val proxyConfStr = + if (hostTF.text.value.contains(".onion") || torCheckBox.selected.value) { + s""" + |bitcoin-s.proxy.enabled = true + |""".stripMargin + } else "" + + val configStr = proxyConfStr + s""" |bitcoin-s.node.mode = bitcoind |bitcoin-s.bitcoind-rpc.isRemote = true diff --git a/app/bundle/src/main/scala/org/bitcoins/bundle/gui/NeutrinoConfigPane.scala b/app/bundle/src/main/scala/org/bitcoins/bundle/gui/NeutrinoConfigPane.scala index f292fa9f5a..f2b4d059c5 100644 --- a/app/bundle/src/main/scala/org/bitcoins/bundle/gui/NeutrinoConfigPane.scala +++ b/app/bundle/src/main/scala/org/bitcoins/bundle/gui/NeutrinoConfigPane.scala @@ -70,6 +70,8 @@ class NeutrinoConfigPane( minWidth = 300 } + private val torCheckBox: CheckBox = new CheckBox() + private var nextRow: Int = 0 val gridPane: GridPane = new GridPane() { @@ -84,6 +86,10 @@ class NeutrinoConfigPane( add(new Label("Peer Address"), 0, nextRow) add(peerAddressTF, 1, nextRow) nextRow += 1 + + add(new Label("Use Tor"), 0, nextRow) + add(torCheckBox, 1, nextRow) + nextRow += 1 } val launchButton: Button = new Button("Launch Wallet") { @@ -99,11 +105,15 @@ class NeutrinoConfigPane( def getConfig: Config = { // Auto-enable proxy for .onion peers - val proxyConfStr = if (peerAddressTF.text.value.contains(".onion")) { - s""" - |bitcoin-s.proxy.enabled = true - |""".stripMargin - } else "" + val proxyConfStr = + if ( + peerAddressTF.text.value.contains( + ".onion") || torCheckBox.selected.value + ) { + s""" + |bitcoin-s.proxy.enabled = true + |""".stripMargin + } else "" val configStr = proxyConfStr + s""" |bitcoin-s.network = ${DatadirUtil.networkStrToDirName( diff --git a/app/server/src/main/scala/org/bitcoins/server/BitcoindRpcAppConfig.scala b/app/server/src/main/scala/org/bitcoins/server/BitcoindRpcAppConfig.scala index 0e1dd718ca..4148353982 100644 --- a/app/server/src/main/scala/org/bitcoins/server/BitcoindRpcAppConfig.scala +++ b/app/server/src/main/scala/org/bitcoins/server/BitcoindRpcAppConfig.scala @@ -2,11 +2,13 @@ package org.bitcoins.server import akka.actor.ActorSystem import com.typesafe.config.Config +import org.bitcoins.core.util.NetworkUtil import org.bitcoins.db._ import org.bitcoins.node.NodeType import org.bitcoins.node.config.NodeAppConfig import org.bitcoins.rpc.client.common.{BitcoindRpcClient, BitcoindVersion} import org.bitcoins.rpc.config._ +import org.bitcoins.tor.Socks5ProxyParams import java.io.File import java.net.{InetSocketAddress, URI} @@ -90,6 +92,22 @@ case class BitcoindRpcAppConfig( lazy val rpcPassword: String = config.getString("bitcoin-s.bitcoind-rpc.rpcpassword") + lazy val socks5ProxyParams: Option[Socks5ProxyParams] = { + if (config.getBoolean("bitcoin-s.proxy.enabled")) { + Some( + Socks5ProxyParams( + address = NetworkUtil.parseInetSocketAddress( + config.getString("bitcoin-s.proxy.socks5"), + Socks5ProxyParams.DefaultPort), + credentialsOpt = None, + randomizeCredentials = true + ) + ) + } else { + None + } + } + lazy val versionOpt: Option[BitcoindVersion] = config .getStringOrNone("bitcoin-s.bitcoind-rpc.version") @@ -140,7 +158,8 @@ case class BitcoindRpcAppConfig( zmqConfig = zmqConfig, binary = binaryOpt.getOrElse(fallbackBinary), datadir = bitcoindDataDir, - isRemote = isRemote + isRemote = isRemote, + proxyParams = socks5ProxyParams ) } diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/Client.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/Client.scala index 6ee423086f..aecf0cf3a1 100644 --- a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/Client.scala +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/Client.scala @@ -3,6 +3,10 @@ package org.bitcoins.rpc.client.common import akka.actor.ActorSystem import akka.http.javadsl.model.headers.HttpCredentials import akka.http.scaladsl.model._ +import akka.http.scaladsl.settings.{ + ClientConnectionSettings, + ConnectionPoolSettings +} import akka.http.scaladsl.unmarshalling.Unmarshal import akka.http.scaladsl.{Http, HttpExt} import akka.stream.StreamTcpException @@ -22,6 +26,7 @@ import org.bitcoins.rpc.config.BitcoindAuthCredentials.{ } import org.bitcoins.rpc.config.{BitcoindAuthCredentials, BitcoindInstance} import org.bitcoins.rpc.util.NativeProcessFactory +import org.bitcoins.tor.Socks5ClientTransport import play.api.libs.json._ import java.nio.file.{Files, Path} @@ -325,8 +330,21 @@ trait Client /** Cached http client to send requests to bitcoind with */ private lazy val httpClient: HttpExt = Http(system) + private lazy val httpConnectionPoolSettings: ConnectionPoolSettings = + instance.proxyParams match { + case Some(proxyParams) => + val socks5ClientTransport = new Socks5ClientTransport(proxyParams) + + val clientConnectionSettings = + ClientConnectionSettings(system).withTransport(socks5ClientTransport) + + ConnectionPoolSettings(system).withConnectionSettings( + clientConnectionSettings) + case None => ConnectionPoolSettings(system) + } + protected def sendRequest(req: HttpRequest): Future[HttpResponse] = { - httpClient.singleRequest(req) + httpClient.singleRequest(req, settings = httpConnectionPoolSettings) } /** Parses the payload of the given response into JSON. diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/config/BitcoindInstance.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/config/BitcoindInstance.scala index 35c7f02d2b..15b230842f 100644 --- a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/config/BitcoindInstance.scala +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/config/BitcoindInstance.scala @@ -4,6 +4,7 @@ import grizzled.slf4j.Logging import org.bitcoins.core.api.commons.InstanceFactory import org.bitcoins.core.config.NetworkParameters import org.bitcoins.rpc.client.common.BitcoindVersion +import org.bitcoins.tor.Socks5ProxyParams import java.io.{File, FileNotFoundException} import java.net.URI @@ -65,6 +66,8 @@ sealed trait BitcoindInstance extends Logging { } def p2pPort: Int = uri.getPort + + def proxyParams: Option[Socks5ProxyParams] } object BitcoindInstance extends InstanceFactory[BitcoindInstance] { @@ -77,7 +80,8 @@ object BitcoindInstance extends InstanceFactory[BitcoindInstance] { zmqConfig: ZmqConfig, binary: File, datadir: File, - isRemote: Boolean + isRemote: Boolean, + proxyParams: Option[Socks5ProxyParams] ) extends BitcoindInstance def apply( @@ -88,7 +92,8 @@ object BitcoindInstance extends InstanceFactory[BitcoindInstance] { zmqConfig: ZmqConfig = ZmqConfig(), binary: File = DEFAULT_BITCOIND_LOCATION, datadir: File = BitcoindConfig.DEFAULT_DATADIR, - isRemote: Boolean = false + isRemote: Boolean = false, + proxyParams: Option[Socks5ProxyParams] = None ): BitcoindInstance = { BitcoindInstanceImpl(network, uri, @@ -97,7 +102,8 @@ object BitcoindInstance extends InstanceFactory[BitcoindInstance] { zmqConfig = zmqConfig, binary = binary, datadir = datadir, - isRemote = isRemote) + isRemote = isRemote, + proxyParams = proxyParams) } lazy val DEFAULT_BITCOIND_LOCATION: File = { diff --git a/build.sbt b/build.sbt index 6f18cf468f..8a841f9f6a 100644 --- a/build.sbt +++ b/build.sbt @@ -122,7 +122,8 @@ lazy val bitcoindRpc = project .settings(CommonSettings.prodSettings: _*) .dependsOn( asyncUtilsJVM, - appCommons + appCommons, + tor ) lazy val eclairRpc = project diff --git a/core/src/main/scala/org/bitcoins/core/util/NetworkUtil.scala b/core/src/main/scala/org/bitcoins/core/util/NetworkUtil.scala index 7fcd82b946..45c7680179 100644 --- a/core/src/main/scala/org/bitcoins/core/util/NetworkUtil.scala +++ b/core/src/main/scala/org/bitcoins/core/util/NetworkUtil.scala @@ -1,34 +1,18 @@ package org.bitcoins.core.util -import java.net.InetSocketAddress +import java.net.{InetSocketAddress, URI} abstract class NetworkUtil { - private def parsePort(port: String): Int = { - lazy val errorMsg = s"Invalid peer port: $port" - try { - val res = port.toInt - if (res < 0 || res > 0xffff) { - throw new RuntimeException(errorMsg) - } - res - } catch { - case _: NumberFormatException => - throw new RuntimeException(errorMsg) - } - } - /** Parses a string that looks like this to [[java.net.InetSocketAddress]] * "neutrino.testnet3.suredbits.com:18333" */ def parseInetSocketAddress( address: String, defaultPort: Int): InetSocketAddress = { - address.split(":") match { - case Array(host) => new InetSocketAddress(host, defaultPort) - case Array(host, port) => new InetSocketAddress(host, parsePort(port)) - case _ => throw new RuntimeException(s"Invalid peer address: $address") - } + val uri = new URI("tcp://" + address) + val port = if (uri.getPort < 0) defaultPort else uri.getPort + InetSocketAddress.createUnresolved(uri.getHost, port) } } diff --git a/node/src/main/scala/org/bitcoins/node/config/NodeAppConfig.scala b/node/src/main/scala/org/bitcoins/node/config/NodeAppConfig.scala index ae683f163b..78ee8e88fd 100644 --- a/node/src/main/scala/org/bitcoins/node/config/NodeAppConfig.scala +++ b/node/src/main/scala/org/bitcoins/node/config/NodeAppConfig.scala @@ -9,7 +9,7 @@ import org.bitcoins.chain.models.{ CompactFilterDAO, CompactFilterHeaderDAO } -import org.bitcoins.core.util.Mutable +import org.bitcoins.core.util.{Mutable, NetworkUtil} import org.bitcoins.db.{AppConfigFactory, DbAppConfig, JdbcProfileComponent} import org.bitcoins.node._ import org.bitcoins.node.db.NodeDbManagement @@ -17,7 +17,6 @@ import org.bitcoins.node.models.Peer import org.bitcoins.node.networking.peer.DataMessageHandler import org.bitcoins.tor.Socks5ProxyParams -import java.net.{InetSocketAddress, URI} import java.nio.file.Path import scala.concurrent.{ExecutionContext, Future} @@ -91,11 +90,11 @@ case class NodeAppConfig( lazy val socks5ProxyParams: Option[Socks5ProxyParams] = { if (config.getBoolean("bitcoin-s.proxy.enabled")) { - val uri = new URI("tcp://" + config.getString("bitcoin-s.proxy.socks5")) - val sock5 = InetSocketAddress.createUnresolved(uri.getHost, uri.getPort) Some( Socks5ProxyParams( - address = sock5, + address = NetworkUtil.parseInetSocketAddress( + config.getString("bitcoin-s.proxy.socks5"), + Socks5ProxyParams.DefaultPort), credentialsOpt = None, randomizeCredentials = true ) diff --git a/tor/src/main/scala/org/bitcoins/tor/Socks5Connection.scala b/tor/src/main/scala/org/bitcoins/tor/Socks5Connection.scala index 8476628cca..cce906ce25 100644 --- a/tor/src/main/scala/org/bitcoins/tor/Socks5Connection.scala +++ b/tor/src/main/scala/org/bitcoins/tor/Socks5Connection.scala @@ -251,6 +251,8 @@ case class Socks5ProxyParams( object Socks5ProxyParams { + val DefaultPort = 9050 + def proxyCredentials( proxyParams: Socks5ProxyParams): Option[Socks5Connection.Credentials] = if (proxyParams.randomizeCredentials) {