BitcoindRpcClient Improvements (#3600)

* Changes in bitcoind-rpc module

* Get things compiling

* Implement BitcoindInstanceRemote & BitcoindInstanceLocal

* Fix doc compile

* bitcoind remote instance test

* rename BitcoindInstanceRemote

* Scalafmt

* Applied requested changes(except one)

* implemented fromConfigFile and fromDataDir for BitcoindInstanceRemote

* implemented fromNetworkVersion

* BitcoindInstanceRemote changes

* Get things compiling

* Add implict type parameter to InstanceFactory

* Fix recursive reference of version <-> getNetworkInfo

* bug fix

* bug fix 2

Co-authored-by: Chris Stewart <stewart.chris1234@gmail.com>
This commit is contained in:
Shruthii RG 2021-09-07 19:19:01 +05:30 committed by GitHub
parent 2c8a2b0e32
commit d53f164478
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
50 changed files with 794 additions and 348 deletions

View file

@ -10,8 +10,6 @@ import scalafx.scene.control._
import scalafx.scene.layout._
import scalafx.scene.text.{Font, TextAlignment}
import scala.util.Try
class BitcoindConfigPane(
appConfig: BitcoinSAppConfig,
model: LandingPaneModel) {
@ -39,12 +37,12 @@ class BitcoindConfigPane(
GUIUtil.setNumericInput(portTF)
private val rpcUserTF: TextField = new TextField {
text = Try(appConfig.rpcUser).getOrElse("")
text = appConfig.rpcUser.getOrElse("")
minWidth = 300
}
private val rpcPasswordTF: PasswordField = new PasswordField {
text = Try(appConfig.rpcPassword).getOrElse("")
text = appConfig.rpcPassword.getOrElse("")
minWidth = 300
}

View file

@ -6,11 +6,13 @@ import org.bitcoins.rpc.client.common.BitcoindRpcClient
import org.bitcoins.rpc.config.{
BitcoindAuthCredentials,
BitcoindConfig,
BitcoindInstance
BitcoindInstanceLocal,
BitcoindInstanceRemote
}
import org.bitcoins.rpc.util.RpcUtil
import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil
import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil.newestBitcoindBinary
import org.bitcoins.testkit.util.{BitcoindRpcTest, FileUtil}
import org.scalatest.compatible.Assertion
@ -66,7 +68,7 @@ class BitcoindInstanceTest extends BitcoindRpcTest {
""".stripMargin
val conf = BitcoindConfig(confStr, FileUtil.tmpDir())
val instance = BitcoindInstance.fromConfig(conf, newestBitcoindBinary)
val instance = BitcoindInstanceLocal.fromConfig(conf, newestBitcoindBinary)
assert(
instance.authCredentials
.isInstanceOf[BitcoindAuthCredentials.CookieBased])
@ -86,7 +88,7 @@ class BitcoindInstanceTest extends BitcoindRpcTest {
""".stripMargin
val conf = BitcoindConfig(confStr, FileUtil.tmpDir())
val instance = BitcoindInstance.fromConfig(conf, newestBitcoindBinary)
val instance = BitcoindInstanceLocal.fromConfig(conf, newestBitcoindBinary)
assert(
instance.authCredentials
.isInstanceOf[BitcoindAuthCredentials.PasswordBased])
@ -94,7 +96,7 @@ class BitcoindInstanceTest extends BitcoindRpcTest {
}
// the values in this conf was generated by executing
// rpcauth.py from Bicoin Core like this:
// rpcauth.py from Bitcoin Core like this:
//
// ./rpcauth.py bitcoin-s strong_password
// String to be appended to bitcoin.conf:
@ -117,7 +119,7 @@ class BitcoindInstanceTest extends BitcoindRpcTest {
BitcoindAuthCredentials.PasswordBased(username = "bitcoin-s",
password = "strong_password")
val instance =
BitcoindInstance(
BitcoindInstanceLocal(
network = RegTest,
uri = new URI(s"http://localhost:$port"),
rpcUri = new URI(s"http://localhost:$rpcPort"),
@ -131,7 +133,7 @@ class BitcoindInstanceTest extends BitcoindRpcTest {
it should "parse a bitcoin.conf file, start bitcoind, mine some blocks and quit" in {
val instance =
BitcoindInstance.fromDatadir(datadir.toFile, newestBitcoindBinary)
BitcoindInstanceLocal.fromDatadir(datadir.toFile, newestBitcoindBinary)
val client = BitcoindRpcClient.withActorSystem(instance)
for {
@ -152,4 +154,53 @@ class BitcoindInstanceTest extends BitcoindRpcTest {
}
it should "connect us to a remote bitcoind instance and ping it successfully" in {
val port = RpcUtil.randomPort
val rpcPort = RpcUtil.randomPort
val confStr = s"""
|daemon=1
|rpcauth=bitcoin-s:6d7580be1deb4ae52bc4249871845b09$$82b282e7c6493f6982a5a7af9fbb1b671bab702e2f31bbb1c016bb0ea1cc27ca
|regtest=1
|port=${RpcUtil.randomPort}
|rpcport=${RpcUtil.randomPort}
""".stripMargin
val conf = BitcoindConfig(confStr, FileUtil.tmpDir())
val authCredentials =
BitcoindAuthCredentials.PasswordBased(username = "bitcoin-s",
password = "strong_password")
val instance =
BitcoindInstanceLocal(
network = RegTest,
uri = new URI(s"http://localhost:$port"),
rpcUri = new URI(s"http://localhost:$rpcPort"),
authCredentials = authCredentials,
datadir = conf.datadir,
binary = newestBitcoindBinary
)
val client = BitcoindRpcClient.withActorSystem(instance)
for {
_ <- startClient(client)
remoteInstance = BitcoindInstanceRemote(
network = instance.network,
uri = instance.uri,
rpcUri = instance.rpcUri,
authCredentials = instance.authCredentials,
zmqConfig = instance.zmqConfig,
proxyParams = None
)
remoteClient = BitcoindRpcClient.withActorSystem(remoteInstance)
_ <- remoteClient.isStartedF.map {
case false =>
client.stop()
fail("Couldn't ping remote instance")
case true =>
}
_ <- remoteClient.stop()
} yield succeed
}
}

View file

@ -0,0 +1,23 @@
package org.bitcoins.rpc.common
import org.bitcoins.rpc.client.common.BitcoindVersion
import org.bitcoins.rpc.client.common.BitcoindVersion.{V19, V20, V21}
import org.bitcoins.testkit.util.BitcoindRpcTest
class BitcoindVersionTest extends BitcoindRpcTest {
behavior of "BitcoindVersion"
it should "return version 21" in {
val version = BitcoindVersion.fromNetworkVersion(210100)
assert(version.equals(V21))
}
it should "return version 20" in {
val version = BitcoindVersion.fromNetworkVersion(200309)
assert(version.equals(V20))
}
it should "return version 19" in {
val version = BitcoindVersion.fromNetworkVersion(190100)
assert(version.equals(V19))
}
}

View file

@ -9,6 +9,7 @@ import org.bitcoins.core.protocol.transaction.{
}
import org.bitcoins.crypto.DoubleSha256Digest
import org.bitcoins.rpc.BitcoindException
import org.bitcoins.rpc.config.{BitcoindInstanceLocal, BitcoindInstanceRemote}
import org.bitcoins.testkit.rpc.{
BitcoindFixturesCachedPairV21,
BitcoindRpcTestUtil
@ -180,8 +181,13 @@ class MempoolRpcTest extends BitcoindFixturesCachedPairV21 {
it should "be able to save the mem pool to disk" in {
nodePair: FixtureParam =>
val client = nodePair.node1
val localInstance = client.getDaemon match {
case _: BitcoindInstanceRemote =>
sys.error(s"Cannot have remote bitcoind instance in tests")
case local: BitcoindInstanceLocal => local
}
val regTest =
new File(client.getDaemon.datadir.getAbsolutePath + "/regtest")
new File(localInstance.datadir.getAbsolutePath + "/regtest")
assert(regTest.isDirectory)
assert(!regTest.list().contains("mempool.dat"))
for {

View file

@ -12,6 +12,7 @@ import org.bitcoins.crypto.{ECPrivateKey, ECPublicKey}
import org.bitcoins.rpc._
import org.bitcoins.rpc.client.common._
import org.bitcoins.rpc.client.v19.BitcoindV19RpcClient
import org.bitcoins.rpc.config.{BitcoindInstanceLocal, BitcoindInstanceRemote}
import org.bitcoins.rpc.util.{NodePair, RpcUtil}
import org.bitcoins.testkit.rpc.{
BitcoindFixturesCachedPairV19,
@ -105,7 +106,13 @@ class MultiWalletRpcTest extends BitcoindFixturesCachedPairV19 {
it should "be able to backup the wallet" in { nodePair =>
val walletClient = nodePair.node2
val datadir = walletClient.getDaemon.datadir.getAbsolutePath
val localInstance = walletClient.getDaemon match {
case _: BitcoindInstanceRemote =>
sys.error(s"Cannot use remote bitcoind instance in test cases")
case local: BitcoindInstanceLocal =>
local
}
val datadir = localInstance.datadir.getAbsolutePath
for {
_ <- walletClient.backupWallet(datadir + "/backup.dat", Some(walletName))
} yield {
@ -286,6 +293,12 @@ class MultiWalletRpcTest extends BitcoindFixturesCachedPairV19 {
val publicKey = ecPrivateKey.publicKey
val address = P2PKHAddress(publicKey, networkParam)
val localInstance = client.getDaemon match {
case _: BitcoindInstanceRemote =>
sys.error(s"Cannot use remote bitcoind instance in test cases")
case local: BitcoindInstanceLocal =>
local
}
for {
_ <- client.importPrivKey(ecPrivateKey.toPrivateKeyBytes(),
rescan = false,
@ -294,7 +307,7 @@ class MultiWalletRpcTest extends BitcoindFixturesCachedPairV19 {
result <-
client
.dumpWallet(
client.getDaemon.datadir.getAbsolutePath + "/wallet_dump.dat",
localInstance.datadir.getAbsolutePath + "/wallet_dump.dat",
Some(walletName))
} yield {
assert(key.toPrivateKey == ecPrivateKey)
@ -359,10 +372,16 @@ class MultiWalletRpcTest extends BitcoindFixturesCachedPairV19 {
it should "be able to import a wallet" in { nodePair =>
val client = nodePair.node2
val walletClient = client
val localInstance = client.getDaemon match {
case _: BitcoindInstanceRemote =>
sys.error(s"Cannot use remote bitcoind instance in test cases")
case local: BitcoindInstanceLocal =>
local
}
for {
address <- client.getNewAddress(Some(walletName))
walletFile =
client.getDaemon.datadir.getAbsolutePath + "/client_wallet.dat"
localInstance.datadir.getAbsolutePath + "/client_wallet.dat"
fileResult <-
client.dumpWallet(walletFile, walletNameOpt = Some(walletName))

View file

@ -20,6 +20,7 @@ import org.bitcoins.core.wallet.signer.BitcoinSigner
import org.bitcoins.core.wallet.utxo.{ECSignatureParams, P2WPKHV0InputInfo}
import org.bitcoins.crypto.{DoubleSha256DigestBE, ECPrivateKey, ECPublicKey}
import org.bitcoins.rpc.client.common.{BitcoindRpcClient, BitcoindVersion}
import org.bitcoins.rpc.config.{BitcoindInstanceLocal, BitcoindInstanceRemote}
import org.bitcoins.rpc.util.RpcUtil
import org.bitcoins.testkit.rpc.{
BitcoindFixturesCachedPairV21,
@ -71,9 +72,15 @@ class WalletRpcTest extends BitcoindFixturesCachedPairV21 {
it should "be able to dump the wallet" in { nodePair: FixtureParam =>
val client = nodePair.node1
val localInstance = client.getDaemon match {
case _: BitcoindInstanceRemote =>
sys.error(s"Cannot use remote bitcoind instance in test cases")
case local: BitcoindInstanceLocal =>
local
}
for {
result <- {
val datadir = client.getDaemon.datadir.getAbsolutePath
val datadir = localInstance.datadir.getAbsolutePath
client.dumpWallet(datadir + "/test.dat")
}
} yield {
@ -84,12 +91,18 @@ class WalletRpcTest extends BitcoindFixturesCachedPairV21 {
it should "be able to list wallets" in { nodePair: FixtureParam =>
val client = nodePair.node1
val localInstance = client.getDaemon match {
case _: BitcoindInstanceRemote =>
sys.error(s"Cannot use remote bitcoind instance in test cases")
case local: BitcoindInstanceLocal =>
local
}
for {
wallets <- client.listWallets
} yield {
val expectedFileName =
if (client.instance.getVersion == BitcoindVersion.V16) "wallet.dat"
if (localInstance.getVersion == BitcoindVersion.V16) "wallet.dat"
else ""
assert(wallets == Vector(expectedFileName))
@ -98,13 +111,19 @@ class WalletRpcTest extends BitcoindFixturesCachedPairV21 {
it should "be able to backup the wallet" in { nodePair: FixtureParam =>
val client = nodePair.node1
val localInstance = client.getDaemon match {
case _: BitcoindInstanceRemote =>
sys.error(s"Cannot use remote bitcoind instance in test cases")
case local: BitcoindInstanceLocal =>
local
}
for {
_ <- {
val datadir = client.getDaemon.datadir.getAbsolutePath
val datadir = localInstance.datadir.getAbsolutePath
client.backupWallet(datadir + "/backup.dat")
}
} yield {
val datadir = client.getDaemon.datadir.getAbsolutePath
val datadir = localInstance.datadir.getAbsolutePath
val file = new File(datadir + "/backup.dat")
assert(file.exists)
assert(file.isFile)
@ -397,7 +416,12 @@ class WalletRpcTest extends BitcoindFixturesCachedPairV21 {
val ecPrivateKey = ECPrivateKey.freshPrivateKey
val publicKey = ecPrivateKey.publicKey
val address = P2PKHAddress(publicKey, networkParam)
val localInstance = client.getDaemon match {
case _: BitcoindInstanceRemote =>
sys.error(s"Cannot use remote bitcoind instance in test cases")
case local: BitcoindInstanceLocal =>
local
}
for {
_ <- client.importPrivKey(ecPrivateKey.toPrivateKeyBytes(),
rescan = false)
@ -405,7 +429,7 @@ class WalletRpcTest extends BitcoindFixturesCachedPairV21 {
result <-
client
.dumpWallet(
client.getDaemon.datadir.getAbsolutePath + "/wallet_dump.dat")
localInstance.datadir.getAbsolutePath + "/wallet_dump.dat")
} yield {
assert(key.toPrivateKey == ecPrivateKey)
val reader = new Scanner(result.filename)
@ -465,11 +489,17 @@ class WalletRpcTest extends BitcoindFixturesCachedPairV21 {
it should "be able to import a wallet" in { nodePair: FixtureParam =>
val client = nodePair.node1
val localInstance = client.getDaemon match {
case _: BitcoindInstanceRemote =>
sys.error(s"Cannot use remote bitcoind instance in test cases")
case local: BitcoindInstanceLocal =>
local
}
for {
walletClient <- walletClientF
address <- client.getNewAddress
walletFile =
client.getDaemon.datadir.getAbsolutePath + "/client_wallet.dat"
localInstance.datadir.getAbsolutePath + "/client_wallet.dat"
fileResult <- client.dumpWallet(walletFile)
_ <- walletClient.walletPassphrase(password, 1000)
@ -482,11 +512,16 @@ class WalletRpcTest extends BitcoindFixturesCachedPairV21 {
it should "be able to load a wallet" in { nodePair: FixtureParam =>
val client = nodePair.node1
val name = "tmp_wallet"
val localInstance = client.getDaemon match {
case _: BitcoindInstanceRemote =>
sys.error(s"Cannot use remote bitcoind instance in test cases")
case local: BitcoindInstanceLocal =>
local
}
for {
walletClient <- walletClientF
walletFile =
client.getDaemon.datadir.getAbsolutePath + s"/regtest/wallets/$name"
localInstance.datadir.getAbsolutePath + s"/regtest/wallets/$name"
_ <- client.createWallet(walletFile)
_ <- client.unloadWallet(walletFile)

View file

@ -41,8 +41,13 @@ class BitcoindV16RpcClientTest extends BitcoindFixturesCachedPairV16 {
it should "be able to start a V16 bitcoind" in { nodePair: FixtureParam =>
val client = nodePair.node1
val otherClient = nodePair.node2
assert(client.version == BitcoindVersion.V16)
assert(otherClient.version == BitcoindVersion.V16)
for {
v <- client.version
v1 <- otherClient.version
} yield {
assert(v == BitcoindVersion.V16)
assert(v1 == BitcoindVersion.V16)
}
}
it should "be able to sign a raw transaction" in { nodePair: FixtureParam =>

View file

@ -30,7 +30,9 @@ class BitcoindV18RpcClientTest extends BitcoindFixturesFundedCachedV18 {
}
it should "be able to start a V18 bitcoind instance" in { client =>
assert(client.version == BitcoindVersion.V18)
for {
v <- client.version
} yield assert(v == BitcoindVersion.V18)
}
it should "return active rpc commands" in { client =>

View file

@ -20,7 +20,11 @@ class BitcoindV19RpcClientTest extends BitcoindFixturesFundedCachedV19 {
it should "be able to start a V19 bitcoind instance" in {
client: BitcoindV19RpcClient =>
assert(client.version == BitcoindVersion.V19)
for {
v <- client.version
} yield {
assert(v == BitcoindVersion.V19)
}
}
it should "get a block filter given a block hash" in {

View file

@ -20,7 +20,9 @@ class BitcoindV20RpcClientTest extends BitcoindFixturesFundedCachedV20 {
it should "be able to start a V20 bitcoind instance" in {
client: BitcoindV20RpcClient =>
assert(client.version == BitcoindVersion.V20)
for {
v <- client.version
} yield assert(v == BitcoindVersion.V20)
}
it should "get a block filter given a block hash" in {

View file

@ -21,7 +21,9 @@ class BitcoindV21RpcClientTest extends BitcoindFixturesFundedCachedV21 {
it should "be able to start a V21 bitcoind instance" in {
client: BitcoindV21RpcClient =>
assert(client.version == BitcoindVersion.V21)
for {
v <- client.version
} yield assert(v == BitcoindVersion.V21)
}
it should "be able to get network info" in {

View file

@ -27,7 +27,12 @@ import org.bitcoins.rpc.client.v18.BitcoindV18RpcClient
import org.bitcoins.rpc.client.v19.BitcoindV19RpcClient
import org.bitcoins.rpc.client.v20.BitcoindV20RpcClient
import org.bitcoins.rpc.client.v21.BitcoindV21RpcClient
import org.bitcoins.rpc.config.{BitcoindConfig, BitcoindInstance}
import org.bitcoins.rpc.config.{
BitcoindConfig,
BitcoindInstance,
BitcoindInstanceLocal,
BitcoindInstanceRemote
}
import java.io.File
import scala.concurrent.Future
@ -45,7 +50,7 @@ import scala.concurrent.Future
* This is a sealed abstract class, so you can pattern match easily
* on the errors, and handle them as you see fit.
*/
class BitcoindRpcClient(val instance: BitcoindInstance)(implicit
class BitcoindRpcClient(override val instance: BitcoindInstance)(implicit
override val system: ActorSystem)
extends Client
with FeeRateApi
@ -65,12 +70,15 @@ class BitcoindRpcClient(val instance: BitcoindInstance)(implicit
with PsbtRpc
with UtilRpc {
override def version: BitcoindVersion = instance.getVersion
require(
instance.isRemote ||
version == BitcoindVersion.Unknown || version == instance.getVersion,
s"bitcoind version must be $version, got ${instance.getVersion}")
override lazy val version: Future[BitcoindVersion] = {
instance match {
case _: BitcoindInstanceRemote =>
getNetworkInfo.map(info =>
BitcoindVersion.fromNetworkVersion(info.version))
case local: BitcoindInstanceLocal =>
Future.successful(local.getVersion)
}
}
// Fee Rate Provider
@ -209,14 +217,17 @@ class BitcoindRpcClient(val instance: BitcoindInstance)(implicit
height: Int): Future[Vector[CompactFilterDb]] = filterHeadersUnsupported
protected def filtersUnsupported: Future[Nothing] = {
Future.failed(
new UnsupportedOperationException(
s"bitcoind ${instance.getVersion} does not support block filters"))
version.map { v =>
throw new UnsupportedOperationException(
s"bitcoind $v does not support block filters")
}
}
protected def filterHeadersUnsupported: Future[Nothing] = {
Future.failed(new UnsupportedOperationException(
s"bitcoind ${instance.getVersion} does not support block filters headers through the rpc"))
version.map { v =>
throw new UnsupportedOperationException(
s"bitcoind $v does not support block filters headers through the rpc")
}
}
}
@ -249,12 +260,14 @@ object BitcoindRpcClient {
new BitcoindRpcClient(instance)
/** Constructs a RPC client from the given datadir, or
* the default datadir if no directory is provided
* the default datadir if no directory is provided.
* This is always a [[BitcoindInstanceLocal]] since a binary
* is passed into this method
*/
def fromDatadir(
datadir: File = BitcoindConfig.DEFAULT_DATADIR,
binary: File): BitcoindRpcClient = {
val instance = BitcoindInstance.fromDatadir(datadir, binary)
val instance = BitcoindInstanceLocal.fromDatadir(datadir, binary)
val cli = BitcoindRpcClient(instance)
cli
}
@ -336,4 +349,24 @@ object BitcoindVersion extends StringFactory[BitcoindVersion] {
override def fromString(string: String): BitcoindVersion = {
fromStringOpt(string).get
}
/** Gets the bitcoind version from the 'getnetworkresult' bitcoind rpc
* An example for 210100 for the 21.1.0 release of bitcoin core
*/
def fromNetworkVersion(int: Int): BitcoindVersion = {
//need to translate the int 210100 (as an example) to a BitcoindVersion
int.toString.substring(0, 2) match {
case "16" => V16
case "17" => V17
case "18" =>
int.toString.substring(2, 4) match {
case "99" => Experimental
case _ => V18
}
case "19" => V19
case "20" => V20
case "21" => V21
case _ => Unknown
}
}
}

View file

@ -28,7 +28,7 @@ trait BlockchainRpc { self: Client =>
}
def getBlockChainInfo: Future[GetBlockChainInfoResult] = {
self.version match {
self.version.flatMap {
case V16 | V17 | V18 =>
bitcoindCall[GetBlockChainInfoResultPreV19]("getblockchaininfo")
case V21 | V20 | V19 | Experimental | Unknown =>

View file

@ -22,7 +22,12 @@ import org.bitcoins.rpc.config.BitcoindAuthCredentials.{
CookieBased,
PasswordBased
}
import org.bitcoins.rpc.config.{BitcoindAuthCredentials, BitcoindInstance}
import org.bitcoins.rpc.config.{
BitcoindAuthCredentials,
BitcoindInstance,
BitcoindInstanceLocal,
BitcoindInstanceRemote
}
import org.bitcoins.tor.Socks5ClientTransport
import play.api.libs.json._
@ -45,28 +50,46 @@ trait Client
extends Logging
with StartStopAsync[BitcoindRpcClient]
with NativeProcessFactory {
def version: BitcoindVersion
def version: Future[BitcoindVersion]
protected val instance: BitcoindInstance
protected def walletExtension(walletName: String): String =
s"/wallet/$walletName"
/** The log file of the Bitcoin Core daemon
/** The log file of the Bitcoin Core daemon.
* This returns the log file if the underlying instance is
* [[org.bitcoins.rpc.config.BitcoindInstanceLocal]], and
* None if the underlying instance is [[BitcoindInstanceRemote]]
*/
lazy val logFile: Path = {
val prefix = instance.network match {
case MainNet => ""
case TestNet3 => "testnet"
case RegTest => "regtest"
case SigNet => "signet"
lazy val logFileOpt: Option[Path] = {
instance match {
case _: BitcoindInstanceRemote => None
case local: BitcoindInstanceLocal =>
val prefix = instance.network match {
case MainNet => ""
case TestNet3 => "testnet"
case RegTest => "regtest"
case SigNet => "signet"
}
val path = local.datadir.toPath.resolve(prefix).resolve("debug.log")
Some(path)
}
instance.datadir.toPath.resolve(prefix).resolve("debug.log")
}
/** The configuration file of the Bitcoin Core daemon */
lazy val confFile: Path =
instance.datadir.toPath.resolve("bitcoin.conf")
/** The configuration file of the Bitcoin Core daemon
* This returns the conf file is the underlying instance is
* [[BitcoindInstanceLocal]] and None if the underlying
* instance is [[BitcoindInstanceRemote]]
*/
lazy val confFileOpt: Option[Path] = {
instance match {
case _: BitcoindInstanceRemote =>
None
case local: BitcoindInstanceLocal =>
val path = local.datadir.toPath.resolve("bitcoin.conf")
Some(path)
}
}
implicit protected val system: ActorSystem
@ -108,15 +131,23 @@ trait Client
def getDaemon: BitcoindInstance = instance
override lazy val cmd: String = {
val binaryPath = instance.binary.getAbsolutePath
val cmd = List(binaryPath,
"-datadir=" + instance.datadir,
"-rpcport=" + instance.rpcUri.getPort,
"-port=" + instance.uri.getPort)
logger.debug(
s"starting bitcoind with datadir ${instance.datadir} and binary path $binaryPath")
instance match {
case _: BitcoindInstanceRemote =>
logger.warn(
s"Cannot start remote instance with local binary command. You've likely misconfigured something")
""
case local: BitcoindInstanceLocal =>
val binaryPath = local.binary.getAbsolutePath
val cmd = List(binaryPath,
"-datadir=" + local.datadir,
"-rpcport=" + instance.rpcUri.getPort,
"-port=" + instance.uri.getPort)
logger.debug(
s"starting bitcoind with datadir ${local.datadir} and binary path $binaryPath")
cmd.mkString(" ")
cmd.mkString(" ")
}
}
/** Starts bitcoind on the local system.
@ -125,15 +156,6 @@ trait Client
* cannot be started
*/
override def start(): Future[BitcoindRpcClient] = {
if (version != BitcoindVersion.Unknown) {
val foundVersion = instance.getVersion
if (foundVersion != version) {
throw new RuntimeException(
s"Wrong version for bitcoind RPC client! Expected $version, got $foundVersion")
}
}
val startedF = startBinary()
// if we're doing cookie based authentication, we might attempt
// to read the cookie file before it's written. this ensures
@ -151,16 +173,41 @@ trait Client
case _: PasswordBased => Future.successful(())
}
val isAlreadyStarted: Future[Boolean] = isStartedF
val started: Future[BitcoindRpcClient] = {
for {
_ <- startedF
_ <- awaitCookie(instance.authCredentials)
_ = isStartedFlag.set(true)
_ <- AsyncUtil.retryUntilSatisfiedF(() => isStartedF,
interval = 1.seconds,
maxTries = 120)
} yield this.asInstanceOf[BitcoindRpcClient]
val started: Future[Future[BitcoindRpcClient]] = isAlreadyStarted.map {
case false =>
instance match {
case _: BitcoindInstanceRemote =>
sys.error(
s"Cannot start a remote instance, it needs to be started on the remote host machine")
case local: BitcoindInstanceLocal =>
val versionCheckF = version.map { v =>
if (v != BitcoindVersion.Unknown) {
val foundVersion = local.getVersion
if (foundVersion != v) {
throw new RuntimeException(
s"Wrong version for bitcoind RPC client! Expected $version, got $foundVersion")
}
}
}
val startedF = versionCheckF.flatMap(_ => startBinary())
for {
_ <- startedF
_ <- awaitCookie(instance.authCredentials)
_ = isStartedFlag.set(true)
_ <- AsyncUtil.retryUntilSatisfiedF(() => isStartedF,
interval = 1.seconds,
maxTries = 120)
} yield this.asInstanceOf[BitcoindRpcClient]
}
case true =>
for {
_ <- awaitCookie(instance.authCredentials)
_ = isStartedFlag.set(true)
} yield this.asInstanceOf[BitcoindRpcClient]
}
started.onComplete {
@ -176,19 +223,27 @@ trait Client
// of our instances. We don't want to do this on mainnet,
// as both the logs and conf file most likely contain sensitive
// information
if (network != MainNet) {
val tempfile = Files.createTempFile("bitcoind-log-", ".dump")
val logfile = Files.readAllBytes(logFile)
Files.write(tempfile, logfile)
logger.info(s"Dumped debug.log to $tempfile")
val otherTempfile = Files.createTempFile("bitcoin-conf-", ".dump")
val conffile = Files.readAllBytes(confFile)
Files.write(otherTempfile, conffile)
logger.info(s"Dumped bitcoin.conf to $otherTempfile")
instance match {
case _: BitcoindInstanceRemote =>
()
case _: BitcoindInstanceLocal =>
if (network != MainNet) {
val tempfile = Files.createTempFile("bitcoind-log-", ".dump")
val logfile = Files.readAllBytes(logFileOpt.get)
Files.write(tempfile, logfile)
logger.info(s"Dumped debug.log to $tempfile")
val otherTempfile = Files.createTempFile("bitcoin-conf-", ".dump")
val conffile = Files.readAllBytes(confFileOpt.get)
Files.write(otherTempfile, conffile)
logger.info(s"Dumped bitcoin.conf to $otherTempfile")
}
}
}
started
started.flatten
}
private def tryPing(): Future[Boolean] = {
@ -227,11 +282,7 @@ trait Client
// if the cookie file doesn't exist we're not started
Future.successful(false)
case (CookieBased(_, _) | PasswordBased(_, _)) =>
if (isStartedFlag.get) {
tryPing()
} else {
Future.successful(false)
}
tryPing()
}
}
@ -327,9 +378,16 @@ trait Client
/** Cached http client to send requests to bitcoind with */
private lazy val httpClient: HttpExt = Http(system)
private lazy val httpConnectionPoolSettings: ConnectionPoolSettings =
Socks5ClientTransport.createConnectionPoolSettings(instance.rpcUri,
instance.proxyParams)
private lazy val httpConnectionPoolSettings: ConnectionPoolSettings = {
instance match {
case remote: BitcoindInstanceRemote =>
Socks5ClientTransport.createConnectionPoolSettings(instance.rpcUri,
remote.proxyParams)
case _: BitcoindInstanceLocal =>
Socks5ClientTransport.createConnectionPoolSettings(instance.rpcUri,
None)
}
}
protected def sendRequest(req: HttpRequest): Future[HttpResponse] = {
httpClient.singleRequest(req, settings = httpConnectionPoolSettings)

View file

@ -31,7 +31,7 @@ trait MempoolRpc { self: Client =>
def getMemPoolAncestorsVerbose(txid: DoubleSha256DigestBE): Future[
Map[DoubleSha256DigestBE, GetMemPoolResult]] = {
self.version match {
self.version.flatMap {
case V21 | V20 | V19 | Experimental | Unknown =>
bitcoindCall[Map[DoubleSha256DigestBE, GetMemPoolResultPostV19]](
"getmempoolancestors",
@ -62,7 +62,7 @@ trait MempoolRpc { self: Client =>
def getMemPoolDescendantsVerbose(txid: DoubleSha256DigestBE): Future[
Map[DoubleSha256DigestBE, GetMemPoolResult]] = {
self.version match {
self.version.flatMap {
case V21 | V20 | V19 | Experimental | Unknown =>
bitcoindCall[Map[DoubleSha256DigestBE, GetMemPoolResultPostV19]](
"getmempooldescendants",
@ -82,7 +82,7 @@ trait MempoolRpc { self: Client =>
def getMemPoolEntry(
txid: DoubleSha256DigestBE): Future[GetMemPoolEntryResult] = {
self.version match {
self.version.flatMap {
case V21 | V20 | V19 | Experimental | Unknown =>
bitcoindCall[GetMemPoolEntryResultPostV19]("getmempoolentry",
List(JsString(txid.hex)))
@ -124,7 +124,7 @@ trait MempoolRpc { self: Client =>
def getRawMemPoolWithTransactions: Future[
Map[DoubleSha256DigestBE, GetMemPoolResult]] = {
self.version match {
self.version.flatMap {
case V21 | V20 | V19 | Experimental | Unknown =>
bitcoindCall[Map[DoubleSha256DigestBE, GetMemPoolResultPostV19]](
"getrawmempool",

View file

@ -40,7 +40,7 @@ trait MultisigRpc { self: Client =>
JsArray(keys.map(keyToString)),
JsString(account)) ++ addressType.map(Json.toJson(_)).toList
self.version match {
self.version.flatMap {
case V21 | V20 | Unknown =>
bitcoindCall[MultiSigResultPostV20](
"addmultisigaddress",
@ -82,7 +82,7 @@ trait MultisigRpc { self: Client =>
minSignatures: Int,
keys: Vector[ECPublicKey],
walletNameOpt: Option[String] = None): Future[MultiSigResult] = {
self.version match {
self.version.flatMap {
case V21 | V20 | Unknown =>
bitcoindCall[MultiSigResultPostV20](
"createmultisig",

View file

@ -56,16 +56,14 @@ trait P2PRpc { self: Client =>
}
def getNetworkInfo: Future[GetNetworkInfoResult] = {
version match {
case V21 | Unknown =>
bitcoindCall[GetNetworkInfoResultPostV21]("getnetworkinfo")
case V16 | V17 | V18 | V19 | V20 | Experimental =>
bitcoindCall[GetNetworkInfoResultPostV21]("getnetworkinfo")
.recoverWith { case _ =>
bitcoindCall[GetNetworkInfoResultPreV21]("getnetworkinfo")
}
}
}
def getPeerInfo: Future[Vector[Peer]] = {
self.version match {
self.version.flatMap {
case V21 | Unknown =>
bitcoindCall[Vector[PeerPostV21]]("getpeerinfo")
case V20 =>
@ -76,7 +74,7 @@ trait P2PRpc { self: Client =>
}
def listBanned: Future[Vector[NodeBan]] = {
self.version match {
self.version.flatMap {
case V21 | V20 | Unknown =>
bitcoindCall[Vector[NodeBanPostV20]]("listbanned")
case V16 | V17 | V18 | V19 | Experimental =>

View file

@ -110,16 +110,18 @@ trait RawTransactionRpc { self: Client =>
transaction: Transaction,
maxfeerate: Double = 0.10): Future[DoubleSha256DigestBE] = {
val feeParameter = self.version match {
val feeParameterF = self.version.map {
case V21 | V20 | V19 | Experimental | Unknown =>
JsNumber(maxfeerate)
case V16 | V17 | V18 =>
JsBoolean(maxfeerate == 0)
}
bitcoindCall[DoubleSha256DigestBE](
"sendrawtransaction",
List(JsString(transaction.hex), feeParameter))
feeParameterF.flatMap { feeParameter =>
bitcoindCall[DoubleSha256DigestBE](
"sendrawtransaction",
List(JsString(transaction.hex), feeParameter))
}
}
}

View file

@ -25,7 +25,7 @@ trait UtilRpc { self: Client =>
}
def getIndexInfo: Future[Map[String, IndexInfoResult]] = {
version match {
version.flatMap {
case V21 | Unknown =>
bitcoindCall[Map[String, IndexInfoResult]]("getindexinfo")
case V16 | V17 | V18 | V19 | V20 | Experimental =>
@ -36,7 +36,7 @@ trait UtilRpc { self: Client =>
}
def getIndexInfo(indexName: String): Future[IndexInfoResult] = {
version match {
version.flatMap {
case V21 | Unknown =>
bitcoindCall[Map[String, IndexInfoResult]](
"getindexinfo",

View file

@ -306,7 +306,7 @@ trait WalletRpc { self: Client =>
walletNameOpt: Option[String] = None
): Future[SetWalletFlagResult] = {
self.version match {
self.version.flatMap {
case V21 | V20 | V19 | Experimental | Unknown =>
bitcoindCall[SetWalletFlagResult](
"setwalletflag",
@ -320,7 +320,7 @@ trait WalletRpc { self: Client =>
}
def getBalances: Future[GetBalancesResult] = {
self.version match {
self.version.flatMap {
case V21 | V20 | V19 | Experimental | Unknown =>
bitcoindCall[GetBalancesResult]("getbalances")
case V16 | V17 | V18 =>
@ -331,7 +331,7 @@ trait WalletRpc { self: Client =>
}
def getBalances(walletName: String): Future[GetBalancesResult] = {
self.version match {
self.version.flatMap {
case V21 | V20 | V19 | Experimental | Unknown =>
bitcoindCall[GetBalancesResult]("getbalances",
uriExtensionOpt =
@ -404,7 +404,7 @@ trait WalletRpc { self: Client =>
disablePrivateKeys: Boolean = false,
blank: Boolean = false,
passphrase: String = ""): Future[CreateWalletResult] =
self.version match {
self.version.flatMap {
case V21 | V20 | V19 | Experimental | Unknown =>
bitcoindCall[CreateWalletResult]("createwallet",
List(JsString(walletName),
@ -422,7 +422,7 @@ trait WalletRpc { self: Client =>
def getAddressInfo(
address: BitcoinAddress,
walletNameOpt: Option[String] = None): Future[AddressInfoResult] = {
self.version match {
self.version.flatMap {
case V16 | V17 =>
bitcoindCall[AddressInfoResultPreV18](
"getaddressinfo",

View file

@ -29,7 +29,8 @@ class BitcoindV16RpcClient(override val instance: BitcoindInstance)(implicit
with V16AccountRpc
with V16SendRpc {
override def version: BitcoindVersion = BitcoindVersion.V16
override lazy val version: Future[BitcoindVersion] =
Future.successful(BitcoindVersion.V16)
override def getFilterCount(): Future[Int] = filtersUnsupported

View file

@ -35,7 +35,8 @@ class BitcoindV17RpcClient(override val instance: BitcoindInstance)(implicit
extends BitcoindRpcClient(instance)
with V17LabelRpc {
override def version: BitcoindVersion = BitcoindVersion.V17
override lazy val version: Future[BitcoindVersion.V17.type] =
Future.successful(BitcoindVersion.V17)
override def getFilterCount(): Future[Int] = filtersUnsupported

View file

@ -35,7 +35,8 @@ class BitcoindV18RpcClient(override val instance: BitcoindInstance)(implicit
with PsbtRpc
with V18AssortedRpc {
override lazy val version: BitcoindVersion = BitcoindVersion.V18
override lazy val version: Future[BitcoindVersion.V18.type] =
Future.successful(BitcoindVersion.V18)
override def getFilterCount(): Future[Int] = filtersUnsupported

View file

@ -78,7 +78,8 @@ class BitcoindV19RpcClient(override val instance: BitcoindInstance)(implicit
} yield Vector(filter.filterDb(height))
}
override lazy val version: BitcoindVersion = BitcoindVersion.V19
override lazy val version: Future[BitcoindVersion.V19.type] =
Future.successful(BitcoindVersion.V19)
/** $signRawTx
*

View file

@ -78,7 +78,8 @@ class BitcoindV20RpcClient(override val instance: BitcoindInstance)(implicit
} yield Vector(filter.filterDb(height))
}
override lazy val version: BitcoindVersion = BitcoindVersion.V20
override lazy val version: Future[BitcoindVersion.V20.type] =
Future.successful(BitcoindVersion.V20)
/** $signRawTx
*

View file

@ -36,7 +36,7 @@ trait V20MultisigRpc extends MultisigRpc { self: Client =>
JsArray(keys.map(keyToString)),
JsString(account)) ++ addressType.map(Json.toJson(_)).toList
self.version match {
self.version.flatMap {
case V20 | V21 | Unknown =>
bitcoindCall[MultiSigResultPostV20]("addmultisigaddress", params)
case version @ (V16 | V17 | V18 | V19 | Experimental) =>
@ -74,7 +74,7 @@ trait V20MultisigRpc extends MultisigRpc { self: Client =>
minSignatures: Int,
keys: Vector[ECPublicKey],
walletNameOpt: Option[String] = None): Future[MultiSigResultPostV20] = {
self.version match {
self.version.flatMap {
case V20 | V21 | Unknown =>
bitcoindCall[MultiSigResultPostV20](
"createmultisig",

View file

@ -79,7 +79,8 @@ class BitcoindV21RpcClient(override val instance: BitcoindInstance)(implicit
} yield Vector(filter.filterDb(height))
}
override lazy val version: BitcoindVersion = BitcoindVersion.V21
override lazy val version: Future[BitcoindVersion.V21.type] =
Future.successful(BitcoindVersion.V21)
/** $signRawTx
*

View file

@ -1,13 +1,15 @@
package org.bitcoins.rpc.config
import akka.actor.ActorSystem
import com.typesafe.config.ConfigFactory
import grizzled.slf4j.Logging
import org.bitcoins.commons.util.NativeProcessFactory
import org.bitcoins.core.api.commons.InstanceFactory
import org.bitcoins.core.api.commons.{InstanceFactory, InstanceFactoryLocal}
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.io.File
import java.net.URI
import java.nio.file.{Files, Path, Paths}
import scala.sys.process._
@ -17,25 +19,31 @@ import scala.util.Properties
*/
sealed trait BitcoindInstance extends Logging {
require(binary.exists || isRemote,
s"bitcoind binary path (${binary.getAbsolutePath}) does not exist!")
// would like to check .canExecute as well, but we've run into issues on some machines
require(binary.isFile || isRemote,
s"bitcoind binary path (${binary.getAbsolutePath}) must be a file")
/** The binary file that should get executed to start Bitcoin Core */
def binary: File
def datadir: File
def network: NetworkParameters
def uri: URI
def rpcUri: URI
def authCredentials: BitcoindAuthCredentials
def zmqConfig: ZmqConfig
def isRemote: Boolean
def p2pPort: Int = uri.getPort
implicit def system: ActorSystem
}
/** Represents a bitcoind instance that is running locally on the same host */
sealed trait BitcoindInstanceLocal extends BitcoindInstance {
/** The binary file that should get executed to start Bitcoin Core */
def binary: File
def datadir: File
// would like to check .canExecute as well, but we've run into issues on some machines
require(binary.isFile,
s"bitcoind binary path (${binary.getAbsolutePath}) must be a file")
require(binary.exists,
s"bitcoind binary path (${binary.getAbsolutePath}) does not exist!")
def getVersion: BitcoindVersion = {
@ -65,25 +73,26 @@ sealed trait BitcoindInstance extends Logging {
case _: String => BitcoindVersion.Unknown
}
}
}
def p2pPort: Int = uri.getPort
/** Refers to a bitcoind instance that is running remotely on another machine */
sealed trait BitcoindInstanceRemote extends BitcoindInstance {
def proxyParams: Option[Socks5ProxyParams]
}
object BitcoindInstance extends InstanceFactory[BitcoindInstance] {
object BitcoindInstanceLocal
extends InstanceFactoryLocal[BitcoindInstanceLocal, ActorSystem] {
private case class BitcoindInstanceImpl(
private case class BitcoindInstanceLocalImpl(
network: NetworkParameters,
uri: URI,
rpcUri: URI,
authCredentials: BitcoindAuthCredentials,
zmqConfig: ZmqConfig,
binary: File,
datadir: File,
isRemote: Boolean,
proxyParams: Option[Socks5ProxyParams]
) extends BitcoindInstance
datadir: File
)(implicit override val system: ActorSystem)
extends BitcoindInstanceLocal
def apply(
network: NetworkParameters,
@ -91,23 +100,22 @@ object BitcoindInstance extends InstanceFactory[BitcoindInstance] {
rpcUri: URI,
authCredentials: BitcoindAuthCredentials,
zmqConfig: ZmqConfig = ZmqConfig(),
binary: File = DEFAULT_BITCOIND_LOCATION,
datadir: File = BitcoindConfig.DEFAULT_DATADIR,
isRemote: Boolean = false,
proxyParams: Option[Socks5ProxyParams] = None
): BitcoindInstance = {
BitcoindInstanceImpl(network,
uri,
rpcUri,
authCredentials,
zmqConfig = zmqConfig,
binary = binary,
datadir = datadir,
isRemote = isRemote,
proxyParams = proxyParams)
binary: File = DEFAULT_BITCOIND_LOCATION match {
case Some(file) => file
case None => bitcoindLocationFromConfigFile
},
datadir: File = BitcoindConfig.DEFAULT_DATADIR
)(implicit system: ActorSystem): BitcoindInstanceLocal = {
BitcoindInstanceLocalImpl(network,
uri,
rpcUri,
authCredentials,
zmqConfig = zmqConfig,
binary = binary,
datadir = datadir)
}
lazy val DEFAULT_BITCOIND_LOCATION: File = {
lazy val DEFAULT_BITCOIND_LOCATION: Option[File] = {
val cmd =
if (Properties.isWin) {
NativeProcessFactory.findExecutableOnPath("bitcoind.exe")
@ -115,14 +123,21 @@ object BitcoindInstance extends InstanceFactory[BitcoindInstance] {
NativeProcessFactory.findExecutableOnPath("bitcoind")
}
cmd.getOrElse(
throw new FileNotFoundException("Cannot find a path to bitcoind"))
cmd
}
lazy val remoteFilePath: File = {
Files.createTempFile("dummy", "").toFile
}
def bitcoindLocationFromConfigFile: File = {
val homeVar = sys.env("HOME");
val config = ConfigFactory
.parseFile(new File(homeVar + "/.bitcoin-s/bitcoin-s.conf"))
.resolve()
new File(config.getString("bitcoin-s.bitcoind-rpc.binary"))
}
/** Constructs a `bitcoind` instance from the given datadir, using the
* `bitcoin.conf` found within (if any)
*
@ -130,8 +145,11 @@ object BitcoindInstance extends InstanceFactory[BitcoindInstance] {
*/
def fromDatadir(
datadir: File = BitcoindConfig.DEFAULT_DATADIR,
binary: File = DEFAULT_BITCOIND_LOCATION
): BitcoindInstance = {
binary: File = DEFAULT_BITCOIND_LOCATION match {
case Some(file) => file
case None => bitcoindLocationFromConfigFile
}
)(implicit system: ActorSystem): BitcoindInstanceLocal = {
require(datadir.exists, s"${datadir.getPath} does not exist!")
require(datadir.isDirectory, s"${datadir.getPath} is not a directory!")
@ -145,8 +163,15 @@ object BitcoindInstance extends InstanceFactory[BitcoindInstance] {
}
}
override def fromDataDir(dir: File): BitcoindInstance = {
fromDatadir(dir, DEFAULT_BITCOIND_LOCATION)
def fromDataDir(dir: File)(implicit
system: ActorSystem): BitcoindInstanceLocal = {
fromDatadir(
dir,
DEFAULT_BITCOIND_LOCATION match {
case Some(file) => file
case None => bitcoindLocationFromConfigFile
}
)
}
/** Construct a `bitcoind` from the given config file. If no `datadir` setting
@ -156,8 +181,11 @@ object BitcoindInstance extends InstanceFactory[BitcoindInstance] {
*/
def fromConfFile(
file: File = BitcoindConfig.DEFAULT_CONF_FILE,
binary: File = DEFAULT_BITCOIND_LOCATION
): BitcoindInstance = {
binary: File = DEFAULT_BITCOIND_LOCATION match {
case Some(file) => file
case None => bitcoindLocationFromConfigFile
}
)(implicit system: ActorSystem): BitcoindInstanceLocal = {
require(file.exists, s"${file.getPath} does not exist!")
require(file.isFile, s"${file.getPath} is not a file!")
@ -166,27 +194,104 @@ object BitcoindInstance extends InstanceFactory[BitcoindInstance] {
fromConfig(conf, binary)
}
override def fromConfigFile(file: File): BitcoindInstance = {
fromConfFile(file, DEFAULT_BITCOIND_LOCATION)
def fromConfigFile(file: File)(implicit
system: ActorSystem): BitcoindInstanceLocal = {
fromConfFile(
file,
DEFAULT_BITCOIND_LOCATION match {
case Some(file) => file
case None => bitcoindLocationFromConfigFile
}
)
}
/** Constructs a `bitcoind` instance from the given config */
def fromConfig(
config: BitcoindConfig,
binary: File = DEFAULT_BITCOIND_LOCATION
): BitcoindInstance = {
binary: File = DEFAULT_BITCOIND_LOCATION match {
case Some(file) => file
case None => bitcoindLocationFromConfigFile
}
)(implicit system: ActorSystem): BitcoindInstanceLocal = {
val authCredentials = BitcoindAuthCredentials.fromConfig(config)
BitcoindInstance(config.network,
config.uri,
config.rpcUri,
authCredentials,
zmqConfig = ZmqConfig.fromConfig(config),
binary = binary,
datadir = config.datadir)
BitcoindInstanceLocalImpl(config.network,
config.uri,
config.rpcUri,
authCredentials,
zmqConfig = ZmqConfig.fromConfig(config),
binary = binary,
datadir = config.datadir)
}
override val DEFAULT_DATADIR: Path = BitcoindConfig.DEFAULT_DATADIR.toPath
override val DEFAULT_CONF_FILE: Path = BitcoindConfig.DEFAULT_CONF_FILE.toPath
}
object BitcoindInstanceRemote
extends InstanceFactory[BitcoindInstanceRemote, ActorSystem] {
private case class BitcoindInstanceRemoteImpl(
network: NetworkParameters,
uri: URI,
rpcUri: URI,
authCredentials: BitcoindAuthCredentials,
zmqConfig: ZmqConfig,
proxyParams: Option[Socks5ProxyParams]
)(implicit override val system: ActorSystem)
extends BitcoindInstanceRemote
def apply(
network: NetworkParameters,
uri: URI,
rpcUri: URI,
authCredentials: BitcoindAuthCredentials,
zmqConfig: ZmqConfig = ZmqConfig(),
proxyParams: Option[Socks5ProxyParams] = None
)(implicit system: ActorSystem): BitcoindInstanceRemote = {
BitcoindInstanceRemoteImpl(network,
uri,
rpcUri,
authCredentials,
zmqConfig = zmqConfig,
proxyParams = proxyParams)
}
def fromConfFile(
file: File
)(implicit system: ActorSystem): BitcoindInstanceRemote = {
require(file.exists, s"${file.getPath} does not exist!")
require(file.isFile, s"${file.getPath} is not a file!")
val conf = BitcoindRpcAppConfig(file.toPath)
fromConfig(conf)
}
override def fromConfigFile(file: File)(implicit
system: ActorSystem): BitcoindInstanceRemote = {
fromConfFile(
file
)
}
def fromConfig(
config: BitcoindRpcAppConfig
)(implicit system: ActorSystem): BitcoindInstanceRemote = {
BitcoindInstanceRemoteImpl(config.network,
config.uri,
config.rpcUri,
config.authCredentials,
zmqConfig = config.zmqConfig,
proxyParams = None)
}
override def fromDataDir(dir: File)(implicit
system: ActorSystem): BitcoindInstanceRemote = {
require(dir.exists, s"${dir.getPath} does not exist!")
require(dir.isDirectory, s"${dir.getPath} is not a directory!")
val conf = BitcoindRpcAppConfig(dir.toPath)
fromConfig(conf)
}
}

View file

@ -21,11 +21,14 @@ case class BitcoindRpcAppConfig(
private val directory: Path,
private val confs: Config*)(implicit val system: ActorSystem)
extends AppConfig {
import system.dispatcher
override protected[bitcoins] def configOverrides: List[Config] = confs.toList
override protected[bitcoins] def moduleName: String =
BitcoindRpcAppConfig.moduleName
override protected[bitcoins] type ConfigType = BitcoindRpcAppConfig
override protected[bitcoins] def newConfigOfType(
@ -38,8 +41,8 @@ case class BitcoindRpcAppConfig(
override def stop(): Future[Unit] = Future.unit
lazy val DEFAULT_BINARY_PATH: File =
BitcoindInstance.DEFAULT_BITCOIND_LOCATION
lazy val DEFAULT_BINARY_PATH: Option[File] =
BitcoindInstanceLocal.DEFAULT_BITCOIND_LOCATION
lazy val binaryOpt: Option[File] =
config.getStringOrNone("bitcoin-s.bitcoind-rpc.binary").map(new File(_))
@ -72,10 +75,11 @@ case class BitcoindRpcAppConfig(
lazy val rpcUri: URI = new URI(s"$rpcBind:$rpcPort")
lazy val rpcUser: String = config.getString("bitcoin-s.bitcoind-rpc.rpcuser")
lazy val rpcUser: Option[String] =
config.getStringOrNone("bitcoin-s.bitcoind-rpc.rpcuser")
lazy val rpcPassword: String =
config.getString("bitcoin-s.bitcoind-rpc.rpcpassword")
lazy val rpcPassword: Option[String] =
config.getStringOrNone("bitcoin-s.bitcoind-rpc.rpcpassword")
lazy val torConf: TorAppConfig =
TorAppConfig(directory, confs: _*)
@ -91,8 +95,17 @@ case class BitcoindRpcAppConfig(
lazy val isRemote: Boolean =
config.getBooleanOrElse("bitcoin-s.bitcoind-rpc.isRemote", default = false)
lazy val authCredentials: BitcoindAuthCredentials =
BitcoindAuthCredentials.PasswordBased(rpcUser, rpcPassword)
lazy val authCredentials: BitcoindAuthCredentials = rpcUser match {
case Some(rpcUser) => {
rpcPassword match {
case Some(rpcPassword) =>
BitcoindAuthCredentials.PasswordBased(rpcUser, rpcPassword)
case None =>
BitcoindAuthCredentials.CookieBased(network)
}
}
case None => BitcoindAuthCredentials.CookieBased(network)
}
lazy val zmqRawBlock: Option[InetSocketAddress] =
config.getStringOrNone("bitcoin-s.bitcoind-rpc.zmqpubrawblock").map { str =>
@ -121,40 +134,49 @@ case class BitcoindRpcAppConfig(
lazy val zmqConfig: ZmqConfig =
ZmqConfig(zmqHashBlock, zmqRawBlock, zmqHashTx, zmqRawTx)
lazy val bitcoindInstance: BitcoindInstance = {
val fallbackBinary =
if (isRemote) BitcoindInstance.remoteFilePath else DEFAULT_BINARY_PATH
lazy val bitcoindInstance = binaryOpt match {
case Some(file) =>
BitcoindInstanceLocal(
network = network,
uri = uri,
rpcUri = rpcUri,
authCredentials = authCredentials,
zmqConfig = zmqConfig,
binary = file,
datadir = bitcoindDataDir
)
BitcoindInstance(
network = network,
uri = uri,
rpcUri = rpcUri,
authCredentials = authCredentials,
zmqConfig = zmqConfig,
binary = binaryOpt.getOrElse(fallbackBinary),
datadir = bitcoindDataDir,
isRemote = isRemote,
proxyParams = socks5ProxyParams
)
case None =>
BitcoindInstanceRemote(network = network,
uri = uri,
rpcUri = rpcUri,
authCredentials = authCredentials,
zmqConfig = zmqConfig,
proxyParams = socks5ProxyParams)
}
lazy val client: BitcoindRpcClient = {
val version = versionOpt.getOrElse(bitcoindInstance.getVersion)
implicit val system: ActorSystem =
ActorSystem.create("bitcoind-rpc-client-created-by-bitcoin-s", config)
BitcoindRpcClient.fromVersion(version, bitcoindInstance)
bitcoindInstance match {
case local: BitcoindInstanceLocal =>
val version = versionOpt.getOrElse(local.getVersion)
BitcoindRpcClient.fromVersion(version, bitcoindInstance)
case _: BitcoindInstanceRemote =>
new BitcoindRpcClient(bitcoindInstance)
}
}
}
object BitcoindRpcAppConfig
extends AppConfigFactoryActorSystem[BitcoindRpcAppConfig] {
override val moduleName: String = "bitcoind"
/** Constructs a node configuration from the default Bitcoin-S
* data directory and given list of configuration overrides.
*/
override def fromDatadir(datadir: Path, confs: Vector[Config])(implicit
system: ActorSystem): BitcoindRpcAppConfig =
BitcoindRpcAppConfig(datadir, confs: _*)
}

View file

@ -3,11 +3,17 @@ package org.bitcoins.core.api.commons
import java.io.File
import java.nio.file.Path
trait InstanceFactory[+T] {
/** A factory to create things like bitcoind instances or eclair instances
* @tparam the type of the instance (i.e. BitcoindInstance)
* @tparam the type of hte implicit parameter, this can be an execution context or ActorSystem
*/
trait InstanceFactory[+T, I] {
def fromConfigFile(file: File)(implicit i: I): T
def fromDataDir(dir: File)(implicit i: I): T
}
trait InstanceFactoryLocal[+T, I] extends InstanceFactory[T, I] {
def DEFAULT_DATADIR: Path
def DEFAULT_CONF_FILE: Path
def fromConfigFile(file: File = DEFAULT_CONF_FILE.toFile): T
def fromDataDir(dir: File = DEFAULT_DATADIR.toFile): T
}

View file

@ -17,7 +17,7 @@ import org.bitcoins.feeprovider._
import org.bitcoins.keymanager.bip39.BIP39KeyManager
import org.bitcoins.node._
import org.bitcoins.rpc.client.v19.BitcoindV19RpcClient
import org.bitcoins.rpc.config.BitcoindInstance
import org.bitcoins.rpc.config._
import org.bitcoins.testkit.BitcoinSTestAppConfig
import org.bitcoins.testkit.wallet.BitcoinSWalletTest
import org.bitcoins.wallet.Wallet
@ -77,7 +77,8 @@ implicit val walletConf: WalletAppConfig =
// let's use a helper method to get a v19 bitcoind
// and a ChainApi
val bitcoind = BitcoindV19RpcClient(BitcoindInstance.fromConfigFile())
val instance = BitcoindInstanceLocal.fromConfigFile(BitcoindConfig.DEFAULT_CONF_FILE)
val bitcoind = BitcoindV19RpcClient(instance)
val nodeApi = BitcoinSWalletTest.MockNodeApi
// Create our key manager

View file

@ -21,10 +21,10 @@ import org.bitcoins.chain.blockchain._
import org.bitcoins.chain.blockchain.sync._
import org.bitcoins.chain.models._
import org.bitcoins.chain.config.ChainAppConfig
import org.bitcoins.rpc.config.BitcoindInstance
import org.bitcoins.rpc.config.BitcoindInstanceLocal
import org.bitcoins.rpc.client.common.BitcoindRpcClient
import org.bitcoins.testkit.chain._
import akka.actor.ActorSystem
import scala.concurrent._
import java.nio.file.Files
```
@ -32,12 +32,12 @@ import java.nio.file.Files
```scala mdoc:compile-only
implicit val ec = ExecutionContext.global
implicit val system = ActorSystem("System")
// We are assuming that a `bitcoind` regtest node is running the background.
// You can see our `bitcoind` guides to see how to connect
// to a local or remote `bitcoind` node.
val bitcoindInstance = BitcoindInstance.fromDatadir()
val bitcoindInstance = BitcoindInstanceLocal.fromDatadir()
val rpcCli = BitcoindRpcClient(bitcoindInstance)
// Next, we need to create a way to monitor the chain:

View file

@ -14,7 +14,7 @@ import org.bitcoins.feeprovider._
import org.bitcoins.keymanager.bip39.BIP39KeyManager
import org.bitcoins.node._
import org.bitcoins.rpc.client.v19.BitcoindV19RpcClient
import org.bitcoins.rpc.config.BitcoindInstance
import org.bitcoins.rpc.config._
import org.bitcoins.testkit.BitcoinSTestAppConfig
import org.bitcoins.testkit.wallet.BitcoinSWalletTest
import org.bitcoins.wallet.Wallet
@ -55,7 +55,8 @@ implicit val walletConf: WalletAppConfig =
// let's use a helper method to get a v19 bitcoind
// and a ChainApi
val bitcoind = BitcoindV19RpcClient(BitcoindInstance.fromConfigFile())
val instance = BitcoindInstanceLocal.fromConfigFile(BitcoindConfig.DEFAULT_CONF_FILE)
val bitcoind = BitcoindV19RpcClient(instance)
val chainApi = BitcoinSWalletTest.MockChainQueryApi
val aesPasswordOpt = Some(AesPassword.fromString("password"))

View file

@ -67,7 +67,7 @@ implicit val ec = system.dispatcher
//so let's start one (make sure you ran 'sbt downloadBitcoind')
val instance = BitcoindRpcTestUtil.instance(versionOpt = Some(BitcoindVersion.Experimental))
val p2pPort = instance.p2pPort
val bitcoindF = BitcoindRpcTestUtil.startedBitcoindRpcClient(instance, Vector.newBuilder)
val bitcoindF = BitcoindRpcTestUtil.startedBitcoindRpcClient(Some(instance), Vector.newBuilder)
//contains information on how to connect to bitcoin's p2p info
val peerF = bitcoindF.flatMap(b => NodeUnitTest.createPeer(b))

View file

@ -42,13 +42,13 @@ import org.bitcoins.rpc.BitcoindWalletException
import org.bitcoins.crypto._
import org.bitcoins.core.protocol._
import org.bitcoins.core.currency._
import akka.actor.ActorSystem
```
```scala mdoc:compile-only
implicit val ec: ExecutionContext = ExecutionContext.global
implicit val system = ActorSystem("System")
// this reads authentication credentials and
// connection details from the default data
// directory on your platform
@ -68,7 +68,7 @@ To do so the wallet rpc functions have an optional `walletName` parameter.
```scala mdoc:compile-only
implicit val ec: ExecutionContext = ExecutionContext.global
implicit val system = ActorSystem("System")
val client = BitcoindRpcClient.fromDatadir(binary=new File("/path/to/bitcoind"), datadir=new File("/path/to/bitcoind-datadir"))
for {
@ -96,7 +96,7 @@ ready to create the connection with our RPC client
```scala mdoc:compile-only
implicit val ec: ExecutionContext = ExecutionContext.global
implicit val system = ActorSystem("System")
val username = "FILL_ME_IN" //this username comes from 'rpcuser' in your bitcoin.conf file
val password = "FILL_ME_IN" //this password comes from your 'rpcpassword' in your bitcoin.conf file
@ -106,7 +106,7 @@ val authCredentials = BitcoindAuthCredentials.PasswordBased(
)
val bitcoindInstance = {
BitcoindInstance (
BitcoindInstanceLocal(
network = MainNet,
uri = new URI(s"http://localhost:${MainNet.port}"),
rpcUri = new URI(s"http://localhost:${MainNet.rpcPort}"),

View file

@ -36,7 +36,7 @@ Here is an example of how to start eclair:
```scala mdoc:invisible
import akka.actor.ActorSystem
import org.bitcoins.eclair.rpc.client.EclairRpcClient
import org.bitcoins.eclair.rpc.config.EclairInstance
import org.bitcoins.eclair.rpc.config.EclairInstanceLocal
import java.nio.file.Paths
```
@ -47,7 +47,7 @@ implicit val ec = system.dispatcher
val datadirPath = Paths.get("path", "to", "datadir")
val binaryPath = Paths.get("path", "to", "eclair-node-0.5.0-ac08560", "bin", "eclair-node.sh")
val instance = EclairInstance.fromDatadir(datadirPath.toFile, logbackXml = None, proxyParams = None)
val instance = EclairInstanceLocal.fromDatadir(datadirPath.toFile, logbackXml = None, proxyParams = None)
val client = new EclairRpcClient(instance, Some(binaryPath.toFile))
val startedF = client.start()

View file

@ -43,7 +43,7 @@ implicit val ec = system.dispatcher
val datadirPath = Paths.get("path", "to", "datadir")
val binaryPath = Paths.get("path", "to", "lnd-linux-amd64-v0.13.1-beta", "lnd")
val instance = LndInstance.fromDataDir(datadirPath.toFile)
val instance = LndInstanceLocal.fromDataDir(datadirPath.toFile)
val client = new LndRpcClient(instance, Some(binaryPath.toFile))
val startedF = client.start()

View file

@ -48,7 +48,7 @@ val bitcoindV = BitcoindVersion.V19
val instance = BitcoindRpcTestUtil.instance(versionOpt = Some(bitcoindV))
//now let's create an rpc client off of that instance
val bitcoindRpcClientF = BitcoindRpcTestUtil.startedBitcoindRpcClient(instance, Vector.newBuilder)
val bitcoindRpcClientF = BitcoindRpcTestUtil.startedBitcoindRpcClient(Some(instance), Vector.newBuilder)
//yay! it's started. Now you can run tests against this.
//let's just grab the block count for an example

View file

@ -29,7 +29,7 @@ import org.bitcoins.core.wallet.fee._
import org.bitcoins.feeprovider._
import org.bitcoins.keymanager.bip39.BIP39KeyManager
import org.bitcoins.rpc.client.v19.BitcoindV19RpcClient
import org.bitcoins.rpc.config.BitcoindInstance
import org.bitcoins.rpc.config.BitcoindInstanceLocal
import org.bitcoins.testkit.BitcoinSTestAppConfig
import org.bitcoins.wallet._
import org.bitcoins.wallet.config.WalletAppConfig
@ -46,7 +46,8 @@ implicit val walletConf: WalletAppConfig =
// let's use a helper method to get a v19 bitcoind
// and a ChainApi
val bitcoind = BitcoindV19RpcClient(BitcoindInstance.fromConfigFile())
val bitcoind = BitcoindV19RpcClient(BitcoindInstanceLocal.fromConfFile())
val aesPasswordOpt = Some(AesPassword.fromString("password"))
// Create our key manager

View file

@ -62,7 +62,7 @@ import org.bitcoins.core.wallet.fee._
import org.bitcoins.feeprovider._
import org.bitcoins.keymanager.bip39._
import org.bitcoins.rpc.client.common.BitcoindRpcClient
import org.bitcoins.rpc.config.BitcoindInstance
import org.bitcoins.rpc.config._
import org.bitcoins.wallet.config.WalletAppConfig
import org.bitcoins.core.api.wallet.WalletApi
import org.bitcoins.wallet.Wallet
@ -71,6 +71,7 @@ import com.typesafe.config.ConfigFactory
import java.nio.file.Files
import java.time.Instant
import scala.concurrent._
import akka.actor.ActorSystem
val chainApi = new ChainQueryApi {
override def epochSecondToBlockHeight(time: Long): Future[Int] = Future.successful(0)
@ -85,7 +86,7 @@ val chainApi = new ChainQueryApi {
```scala mdoc:compile-only
implicit val ec = scala.concurrent.ExecutionContext.global
implicit val system = ActorSystem("System")
val config = ConfigFactory.parseString {
"""
@ -113,7 +114,7 @@ val configF: Future[Unit] = for {
_ <- chainConfig.start()
} yield ()
val bitcoindInstance = BitcoindInstance.fromDatadir()
val bitcoindInstance = BitcoindInstanceLocal.fromDatadir()
val bitcoind = BitcoindRpcClient(bitcoindInstance)

View file

@ -21,7 +21,10 @@ import org.bitcoins.core.protocol.ln.{
}
import org.bitcoins.eclair.rpc.api._
import org.bitcoins.eclair.rpc.client.EclairRpcClient
import org.bitcoins.eclair.rpc.config.{EclairAuthCredentials, EclairInstance}
import org.bitcoins.eclair.rpc.config.{
EclairAuthCredentials,
EclairInstanceLocal
}
import org.bitcoins.rpc.client.common.BitcoindRpcClient
import org.bitcoins.testkit.async.TestAsyncUtil
import org.bitcoins.testkit.eclair.rpc.{EclairNodes4, EclairRpcTestUtil}
@ -512,7 +515,7 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
val badInstanceF = badCredentialsF.flatMap { badCredentials =>
val getBadInstance = (client: EclairRpcClient, _: EclairRpcClient) => {
val instance = EclairInstance(
val instance = EclairInstanceLocal(
network = client.instance.network,
uri = client.instance.uri,
rpcUri = client.instance.rpcUri,

View file

@ -1,7 +1,8 @@
package org.bitcoins.eclair.rpc.config
import akka.actor.ActorSystem
import com.typesafe.config.{Config, ConfigFactory}
import org.bitcoins.core.api.commons.InstanceFactory
import org.bitcoins.core.api.commons.InstanceFactoryLocal
import org.bitcoins.core.config.{MainNet, NetworkParameters, RegTest, TestNet3}
import org.bitcoins.core.protocol.ln.LnPolicy
import org.bitcoins.core.util.NetworkUtil
@ -25,15 +26,20 @@ sealed trait EclairInstance {
def proxyParams: Option[Socks5ProxyParams]
}
sealed trait EclairInstanceLocal extends EclairInstance
sealed trait EclairInstanceRemote extends EclairInstance
/** @define fromConfigDoc
* Parses a [[com.typesafe.config.Config Config]] in the format of this
* [[https://github.com/ACINQ/eclair/blob/master/eclair-core/src/main/resources/reference.conf sample reference.conf]]
* file to a
* [[org.bitcoins.eclair.rpc.config.EclairInstance EclairInstance]]
*/
object EclairInstance extends InstanceFactory[EclairInstance] {
object EclairInstanceLocal
extends InstanceFactoryLocal[EclairInstanceLocal, ActorSystem] {
private case class EclairInstanceImpl(
private case class EclairInstanceLocalImpl(
network: NetworkParameters,
uri: URI,
rpcUri: URI,
@ -43,7 +49,7 @@ object EclairInstance extends InstanceFactory[EclairInstance] {
bitcoindAuthCredentials: Option[BitcoindAuthCredentials],
zmqConfig: Option[ZmqConfig],
proxyParams: Option[Socks5ProxyParams])
extends EclairInstance
extends EclairInstanceLocal
def apply(
network: NetworkParameters,
@ -56,15 +62,15 @@ object EclairInstance extends InstanceFactory[EclairInstance] {
bitcoindAuthCredentials: Option[BitcoindAuthCredentials] = None,
zmqConfig: Option[ZmqConfig] = None
): EclairInstance = {
EclairInstanceImpl(network,
uri,
rpcUri,
authCredentials,
logbackXmlPath,
bitcoindRpcUri,
bitcoindAuthCredentials,
zmqConfig,
proxyParams)
EclairInstanceLocalImpl(network,
uri,
rpcUri,
authCredentials,
logbackXmlPath,
bitcoindRpcUri,
bitcoindAuthCredentials,
zmqConfig,
proxyParams)
}
override val DEFAULT_DATADIR: Path = Paths.get(Properties.userHome, ".eclair")
@ -80,7 +86,7 @@ object EclairInstance extends InstanceFactory[EclairInstance] {
def fromDatadir(
datadir: File = DEFAULT_DATADIR.toFile,
logbackXml: Option[String],
proxyParams: Option[Socks5ProxyParams]): EclairInstance = {
proxyParams: Option[Socks5ProxyParams]): EclairInstanceLocal = {
require(datadir.exists, s"${datadir.getPath} does not exist!")
require(datadir.isDirectory, s"${datadir.getPath} is not a directory!")
@ -90,14 +96,14 @@ object EclairInstance extends InstanceFactory[EclairInstance] {
}
override def fromConfigFile(
file: File = DEFAULT_CONF_FILE.toFile): EclairInstance =
override def fromConfigFile(file: File = DEFAULT_CONF_FILE.toFile)(implicit
system: ActorSystem): EclairInstanceLocal =
fromConfFile(file, None, None)
def fromConfFile(
file: File = DEFAULT_CONF_FILE.toFile,
logbackXml: Option[String],
proxyParams: Option[Socks5ProxyParams]): EclairInstance = {
proxyParams: Option[Socks5ProxyParams]): EclairInstanceLocal = {
require(file.exists, s"${file.getPath} does not exist!")
require(file.isFile, s"${file.getPath} is not a file!")
@ -106,8 +112,8 @@ object EclairInstance extends InstanceFactory[EclairInstance] {
fromConfig(config, file.getParentFile, logbackXml, proxyParams)
}
override def fromDataDir(
dir: File = DEFAULT_DATADIR.toFile): EclairInstance = {
override def fromDataDir(dir: File = DEFAULT_DATADIR.toFile)(implicit
system: ActorSystem): EclairInstanceLocal = {
require(dir.exists, s"${dir.getPath} does not exist!")
require(dir.isDirectory, s"${dir.getPath} is not a directory!")
@ -121,7 +127,7 @@ object EclairInstance extends InstanceFactory[EclairInstance] {
config: Config,
datadir: File,
logbackXml: Option[String],
proxyParams: Option[Socks5ProxyParams]): EclairInstance = {
proxyParams: Option[Socks5ProxyParams]): EclairInstanceLocal = {
fromConfig(config, Some(datadir), logbackXml, proxyParams)
}
@ -135,7 +141,7 @@ object EclairInstance extends InstanceFactory[EclairInstance] {
config: Config,
datadir: Option[File],
logbackXml: Option[String],
proxyParams: Option[Socks5ProxyParams]): EclairInstance = {
proxyParams: Option[Socks5ProxyParams]): EclairInstanceLocal = {
val chain = ConfigUtil.getStringOrElse(config, "eclair.chain", "testnet")
// default conf: https://github.com/ACINQ/eclair/blob/master/eclair-core/src/main/resources/reference.conf
@ -193,7 +199,7 @@ object EclairInstance extends InstanceFactory[EclairInstance] {
val zmqConfig = ZmqConfig(rawBlock = rawBlock, rawTx = rawTx)
EclairInstance(
EclairInstanceLocalImpl(
network = np,
uri = uri,
rpcUri = rpcUri,

View file

@ -25,7 +25,7 @@ import org.bitcoins.core.util.StartStopAsync
import org.bitcoins.core.wallet.fee.{SatoshisPerKW, SatoshisPerVirtualByte}
import org.bitcoins.crypto._
import org.bitcoins.lnd.rpc.LndRpcClient._
import org.bitcoins.lnd.rpc.config.LndInstance
import org.bitcoins.lnd.rpc.config.{LndInstance, LndInstanceLocal}
import scodec.bits.ByteVector
import signrpc.TxOut
import walletrpc.{SendOutputsRequest, WalletKitClient}
@ -40,17 +40,21 @@ import scala.util.{Failure, Success, Try}
/** @param binary Path to lnd executable
*/
class LndRpcClient(val instance: LndInstance, binary: Option[File] = None)(
class LndRpcClient(val instance: LndInstance, binaryOpt: Option[File] = None)(
implicit system: ActorSystem)
extends NativeProcessFactory
with StartStopAsync[LndRpcClient]
with Logging {
instance match {
case _: LndInstanceLocal =>
require(binaryOpt.isDefined,
s"Binary must be defined with a local instance of lnd")
}
/** The command to start the daemon on the underlying OS */
override def cmd: String = binary match {
case Some(file) =>
s"$file --lnddir=${instance.datadir.toAbsolutePath}"
case None => ""
override def cmd: String = instance match {
case local: LndInstanceLocal =>
s"${binaryOpt.get} --lnddir=${local.datadir.toAbsolutePath}"
}
implicit val executionContext: ExecutionContext = system.dispatcher
@ -513,9 +517,10 @@ class LndRpcClient(val instance: LndInstance, binary: Option[File] = None)(
// too many tries to get info about a payment
// either Lnd is down or the payment is still in PENDING state for some reason
// complete the promise with an exception so the runnable will be canceled
p.failure(new RuntimeException(
s"LndApi.monitorInvoice() [${instance.datadir}] too many attempts: ${attempts
.get()} for invoice=${rHash.hash.hex}"))
p.failure(
new RuntimeException(
s"LndApi.monitorInvoice() [${instance}] too many attempts: ${attempts
.get()} for invoice=${rHash.hash.hex}"))
}
}
}

View file

@ -153,7 +153,7 @@ case class LndConfig(private[bitcoins] val lines: Seq[String], datadir: File)
LndConfig(lines, newDatadir)
}
lazy val lndInstance: LndInstance = LndInstance(
lazy val lndInstance: LndInstanceLocal = LndInstanceLocal(
datadir.toPath,
network,
listenBinding,

View file

@ -1,6 +1,7 @@
package org.bitcoins.lnd.rpc.config
import org.bitcoins.core.api.commons.InstanceFactory
import akka.actor.ActorSystem
import org.bitcoins.core.api.commons.InstanceFactoryLocal
import org.bitcoins.core.config._
import org.bitcoins.rpc.config.BitcoindAuthCredentials._
import org.bitcoins.rpc.config._
@ -11,7 +12,23 @@ import java.net._
import java.nio.file._
import scala.util.Properties
case class LndInstance(
sealed trait LndInstance {
def network: BitcoinNetwork
def listenBinding: URI
def restUri: URI
def rpcUri: URI
def bitcoindAuthCredentials: PasswordBased
def bitcoindRpcUri: URI
def zmqConfig: ZmqConfig
def debugLevel: LogLevel
def macaroon: String
def datadir: Path
def certFile: File
}
case class LndInstanceLocal(
datadir: Path,
network: BitcoinNetwork,
listenBinding: URI,
@ -20,14 +37,12 @@ case class LndInstance(
bitcoindAuthCredentials: PasswordBased,
bitcoindRpcUri: URI,
zmqConfig: ZmqConfig,
debugLevel: LogLevel) {
lazy val certFile: File =
datadir.resolve("tls.cert").toFile
debugLevel: LogLevel)
extends LndInstance {
private var macaroonOpt: Option[String] = None
def macaroon: String = {
override def macaroon: String = {
macaroonOpt match {
case Some(value) => value
case None =>
@ -36,7 +51,7 @@ case class LndInstance(
.resolve("data")
.resolve("chain")
.resolve("bitcoin")
.resolve(LndInstance.getNetworkDirName(network))
.resolve(LndInstanceLocal.getNetworkDirName(network))
.resolve("admin.macaroon")
val bytes = Files.readAllBytes(path)
@ -46,9 +61,13 @@ case class LndInstance(
hex
}
}
lazy val certFile: File =
datadir.resolve("tls.cert").toFile
}
object LndInstance extends InstanceFactory[LndInstance] {
object LndInstanceLocal
extends InstanceFactoryLocal[LndInstanceLocal, ActorSystem] {
override val DEFAULT_DATADIR: Path = Paths.get(Properties.userHome, ".lnd")
@ -63,8 +82,8 @@ object LndInstance extends InstanceFactory[LndInstance] {
}
}
override def fromConfigFile(
file: File = DEFAULT_CONF_FILE.toFile): LndInstance = {
override def fromConfigFile(file: File = DEFAULT_CONF_FILE.toFile)(implicit
system: ActorSystem): LndInstanceLocal = {
require(file.exists, s"${file.getPath} does not exist!")
require(file.isFile, s"${file.getPath} is not a file!")
@ -73,7 +92,8 @@ object LndInstance extends InstanceFactory[LndInstance] {
fromConfig(config)
}
override def fromDataDir(dir: File = DEFAULT_DATADIR.toFile): LndInstance = {
override def fromDataDir(dir: File = DEFAULT_DATADIR.toFile)(implicit
system: ActorSystem): LndInstanceLocal = {
require(dir.exists, s"${dir.getPath} does not exist!")
require(dir.isDirectory, s"${dir.getPath} is not a directory!")
@ -83,7 +103,7 @@ object LndInstance extends InstanceFactory[LndInstance] {
fromConfig(config)
}
def fromConfig(config: LndConfig): LndInstance = {
def fromConfig(config: LndConfig): LndInstanceLocal = {
config.lndInstance
}
}

View file

@ -21,11 +21,12 @@ import org.bitcoins.core.protocol.ln.node.NodeId
import org.bitcoins.crypto.Sha256Digest
import org.bitcoins.eclair.rpc.api._
import org.bitcoins.eclair.rpc.client.EclairRpcClient
import org.bitcoins.eclair.rpc.config.EclairInstance
import org.bitcoins.eclair.rpc.config.EclairInstanceLocal
import org.bitcoins.rpc.client.common.{BitcoindRpcClient, BitcoindVersion}
import org.bitcoins.rpc.config.{
BitcoindAuthCredentials,
BitcoindInstance,
BitcoindInstanceLocal,
ZmqConfig
}
import org.bitcoins.rpc.util.RpcUtil
@ -59,10 +60,13 @@ trait EclairRpcTestUtil extends Logging {
/** Makes a best effort to get a 0.16 bitcoind instance
*/
def startedBitcoindRpcClient(instance: BitcoindInstance = bitcoindInstance())(
implicit actorSystem: ActorSystem): Future[BitcoindRpcClient] = {
def startedBitcoindRpcClient(
instanceOpt: Option[BitcoindInstanceLocal] = None)(implicit
actorSystem: ActorSystem): Future[BitcoindRpcClient] = {
//need to do something with the Vector.newBuilder presumably?
BitcoindRpcTestUtil.startedBitcoindRpcClient(instance, Vector.newBuilder)
val instance = instanceOpt.getOrElse(bitcoindInstance())
BitcoindRpcTestUtil.startedBitcoindRpcClient(Some(instance),
Vector.newBuilder)
}
/** Creates a bitcoind instance with the given parameters */
@ -70,8 +74,8 @@ trait EclairRpcTestUtil extends Logging {
port: Int = RpcUtil.randomPort,
rpcPort: Int = RpcUtil.randomPort,
zmqConfig: ZmqConfig = RpcUtil.zmqConfig,
bitcoindV: BitcoindVersion =
EclairRpcClient.bitcoindV): BitcoindInstance = {
bitcoindV: BitcoindVersion = EclairRpcClient.bitcoindV)(implicit
system: ActorSystem): BitcoindInstanceLocal = {
BitcoindRpcTestUtil.getInstance(bitcoindVersion = bitcoindV,
port = port,
rpcPort = rpcPort,
@ -147,29 +151,29 @@ trait EclairRpcTestUtil extends Logging {
/** Assumes bitcoind is running already and you have specified correct bindings in eclair.conf */
def cannonicalEclairInstance(
logbackXml: Option[String] = None): EclairInstance = {
logbackXml: Option[String] = None): EclairInstanceLocal = {
val datadir = cannonicalDatadir
eclairInstance(datadir, logbackXml)
}
def eclairInstance(
datadir: File,
logbackXml: Option[String]): EclairInstance = {
val instance = EclairInstance.fromDatadir(datadir, logbackXml, None)
logbackXml: Option[String]): EclairInstanceLocal = {
val instance = EclairInstanceLocal.fromDatadir(datadir, logbackXml, None)
instance
}
/** Starts the given bitcoind instance and then starts the eclair instance */
def eclairInstance(
bitcoindRpc: BitcoindRpcClient,
logbackXml: Option[String] = None): EclairInstance = {
logbackXml: Option[String] = None): EclairInstanceLocal = {
val datadir = eclairDataDir(bitcoindRpc, false)
eclairInstance(datadir, logbackXml)
}
def randomEclairInstance(
bitcoindRpc: BitcoindRpcClient,
logbackXml: Option[String] = None): EclairInstance = {
logbackXml: Option[String] = None): EclairInstanceLocal = {
val datadir = eclairDataDir(bitcoindRpc, false)
eclairInstance(datadir, logbackXml)
}
@ -682,7 +686,7 @@ trait EclairRpcTestUtil extends Logging {
val bitcoindRpc = {
val instance = eclairRpcClient.instance
val auth = instance.authCredentials
val bitcoindInstance = BitcoindInstance(
val bitcoindInstance = BitcoindInstanceLocal(
network = instance.network,
uri = new URI("http://localhost:18333"),
rpcUri = auth.bitcoindRpcUri,

View file

@ -1,7 +1,11 @@
package org.bitcoins.testkit.fixtures
import com.typesafe.config.{Config, ConfigFactory}
import org.bitcoins.rpc.config.BitcoindInstance
import org.bitcoins.rpc.config.{
BitcoindInstance,
BitcoindInstanceLocal,
BitcoindInstanceRemote
}
import org.bitcoins.rpc.util.RpcUtil
import org.bitcoins.server.BitcoinSAppConfig
import org.bitcoins.testkit.rpc.CachedBitcoindNewest
@ -35,7 +39,11 @@ trait BitcoinSAppConfigBitcoinFixtureNotStarted
val builder: () => Future[BitcoinSAppConfig] = () => {
for {
bitcoind <- cachedBitcoindWithFundsF
datadir = bitcoind.instance.datadir
datadir = bitcoind.instance match {
case local: BitcoindInstanceLocal => local.datadir
case _: BitcoindInstanceRemote =>
sys.error("Remote instance should not be used in tests")
}
conf = buildConfig(bitcoind.instance)
bitcoinSAppConfig = BitcoinSAppConfig(datadir.toPath, conf)
} yield bitcoinSAppConfig
@ -56,6 +64,11 @@ trait BitcoinSAppConfigBitcoinFixtureNotStarted
* and sets tor config
*/
private def buildConfig(instance: BitcoindInstance): Config = {
val version = instance match {
case local: BitcoindInstanceLocal => local.getVersion
case _: BitcoindInstanceRemote =>
sys.error("Remote instance should not be used in tests")
}
val configStr =
s"""
|bitcoin-s.bitcoind-rpc.rpcuser="${instance.authCredentials.username}"
@ -63,7 +76,7 @@ trait BitcoinSAppConfigBitcoinFixtureNotStarted
|bitcoin-s.bitcoind-rpc.rpcbind="${instance.rpcUri.getHost}"
|bitcoin-s.bitcoind-rpc.rpcport="${instance.rpcUri.getPort}"
|bitcoin-s.bitcoind-rpc.isRemote=true
|bitcoin-s.bitcoind-rpc.version="${instance.getVersion}"
|bitcoin-s.bitcoind-rpc.version="${version}"
|bitcoin-s.node.mode=bitcoind
|bitcoin-s.tor.enabled=${TorUtil.torEnabled}
|bitcoin-s.proxy.enabled=${TorUtil.torEnabled}

View file

@ -8,11 +8,12 @@ import org.bitcoins.core.protocol.ln.node.NodeId
import org.bitcoins.core.protocol.transaction.TransactionOutPoint
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
import org.bitcoins.lnd.rpc.LndRpcClient
import org.bitcoins.lnd.rpc.config.LndInstance
import org.bitcoins.lnd.rpc.config.LndInstanceLocal
import org.bitcoins.rpc.client.common.{BitcoindRpcClient, BitcoindVersion}
import org.bitcoins.rpc.config.{
BitcoindAuthCredentials,
BitcoindInstance,
BitcoindInstanceLocal,
ZmqConfig
}
import org.bitcoins.rpc.util.RpcUtil
@ -39,10 +40,11 @@ trait LndRpcTestUtil extends Logging {
/** Makes a best effort to get a 0.21 bitcoind instance
*/
def startedBitcoindRpcClient(instance: BitcoindInstance = bitcoindInstance())(
implicit actorSystem: ActorSystem): Future[BitcoindRpcClient] = {
def startedBitcoindRpcClient(
instanceOpt: Option[BitcoindInstanceLocal] = None)(implicit
actorSystem: ActorSystem): Future[BitcoindRpcClient] = {
//need to do something with the Vector.newBuilder presumably?
BitcoindRpcTestUtil.startedBitcoindRpcClient(instance, Vector.newBuilder)
BitcoindRpcTestUtil.startedBitcoindRpcClient(instanceOpt, Vector.newBuilder)
}
/** Creates a bitcoind instance with the given parameters */
@ -50,7 +52,8 @@ trait LndRpcTestUtil extends Logging {
port: Int = RpcUtil.randomPort,
rpcPort: Int = RpcUtil.randomPort,
zmqConfig: ZmqConfig = RpcUtil.zmqConfig,
bitcoindV: BitcoindVersion = BitcoindVersion.V21): BitcoindInstance = {
bitcoindV: BitcoindVersion = BitcoindVersion.V21)(implicit
system: ActorSystem): BitcoindInstanceLocal = {
BitcoindRpcTestUtil.getInstance(bitcoindVersion = bitcoindV,
port = port,
rpcPort = rpcPort,
@ -107,13 +110,15 @@ trait LndRpcTestUtil extends Logging {
}
}
def lndInstance(bitcoindRpc: BitcoindRpcClient): LndInstance = {
def lndInstance(bitcoindRpc: BitcoindRpcClient)(implicit
system: ActorSystem): LndInstanceLocal = {
val datadir = lndDataDir(bitcoindRpc, isCannonical = false)
lndInstance(datadir)
}
def lndInstance(datadir: File): LndInstance = {
LndInstance.fromDataDir(datadir)
def lndInstance(datadir: File)(implicit
system: ActorSystem): LndInstanceLocal = {
LndInstanceLocal.fromDataDir(datadir)
}
/** Returns a `Future` that is completed when both lnd and bitcoind have the same block height

View file

@ -210,8 +210,8 @@ trait BitcoindRpcTestUtil extends Logging {
zmqConfig: ZmqConfig = RpcUtil.zmqConfig,
pruneMode: Boolean = false,
versionOpt: Option[BitcoindVersion] = None,
binaryDirectory: Path =
BitcoindRpcTestClient.sbtBinaryDirectory): BitcoindInstance = {
binaryDirectory: Path = BitcoindRpcTestClient.sbtBinaryDirectory)(implicit
system: ActorSystem): BitcoindInstanceLocal = {
val uri = new URI("http://localhost:" + port)
val rpcUri = new URI("http://localhost:" + rpcPort)
val hasNeutrinoSupport = versionOpt match {
@ -241,7 +241,7 @@ trait BitcoindRpcTestUtil extends Logging {
}
BitcoindInstance.fromConfig(conf, binary)
BitcoindInstanceLocal.fromConfig(conf, binary)
}
def v16Instance(
@ -250,7 +250,7 @@ trait BitcoindRpcTestUtil extends Logging {
zmqConfig: ZmqConfig = RpcUtil.zmqConfig,
pruneMode: Boolean = false,
binaryDirectory: Path = BitcoindRpcTestClient.sbtBinaryDirectory
): BitcoindInstance =
)(implicit system: ActorSystem): BitcoindInstanceLocal =
instance(port = port,
rpcPort = rpcPort,
zmqConfig = zmqConfig,
@ -264,7 +264,7 @@ trait BitcoindRpcTestUtil extends Logging {
zmqConfig: ZmqConfig = RpcUtil.zmqConfig,
pruneMode: Boolean = false,
binaryDirectory: Path = BitcoindRpcTestClient.sbtBinaryDirectory
): BitcoindInstance =
)(implicit system: ActorSystem): BitcoindInstanceLocal =
instance(port = port,
rpcPort = rpcPort,
zmqConfig = zmqConfig,
@ -278,7 +278,7 @@ trait BitcoindRpcTestUtil extends Logging {
zmqConfig: ZmqConfig = RpcUtil.zmqConfig,
pruneMode: Boolean = false,
binaryDirectory: Path = BitcoindRpcTestClient.sbtBinaryDirectory
): BitcoindInstance =
)(implicit system: ActorSystem): BitcoindInstanceLocal =
instance(port = port,
rpcPort = rpcPort,
zmqConfig = zmqConfig,
@ -292,7 +292,7 @@ trait BitcoindRpcTestUtil extends Logging {
zmqConfig: ZmqConfig = RpcUtil.zmqConfig,
pruneMode: Boolean = false,
binaryDirectory: Path = BitcoindRpcTestClient.sbtBinaryDirectory
): BitcoindInstance =
)(implicit system: ActorSystem): BitcoindInstanceLocal =
instance(port = port,
rpcPort = rpcPort,
zmqConfig = zmqConfig,
@ -306,7 +306,7 @@ trait BitcoindRpcTestUtil extends Logging {
zmqConfig: ZmqConfig = RpcUtil.zmqConfig,
pruneMode: Boolean = false,
binaryDirectory: Path = BitcoindRpcTestClient.sbtBinaryDirectory
): BitcoindInstance =
)(implicit system: ActorSystem): BitcoindInstanceLocal =
instance(port = port,
rpcPort = rpcPort,
zmqConfig = zmqConfig,
@ -320,7 +320,7 @@ trait BitcoindRpcTestUtil extends Logging {
zmqConfig: ZmqConfig = RpcUtil.zmqConfig,
pruneMode: Boolean = false,
binaryDirectory: Path = BitcoindRpcTestClient.sbtBinaryDirectory
): BitcoindInstance =
)(implicit system: ActorSystem): BitcoindInstanceLocal =
instance(port = port,
rpcPort = rpcPort,
zmqConfig = zmqConfig,
@ -334,7 +334,7 @@ trait BitcoindRpcTestUtil extends Logging {
zmqConfig: ZmqConfig = RpcUtil.zmqConfig,
pruneMode: Boolean = false,
binaryDirectory: Path = BitcoindRpcTestClient.sbtBinaryDirectory
): BitcoindInstance =
)(implicit system: ActorSystem): BitcoindInstanceLocal =
instance(port = port,
rpcPort = rpcPort,
zmqConfig = zmqConfig,
@ -349,8 +349,8 @@ trait BitcoindRpcTestUtil extends Logging {
rpcPort: Int = RpcUtil.randomPort,
zmqConfig: ZmqConfig = RpcUtil.zmqConfig,
pruneMode: Boolean = false,
binaryDirectory: Path =
BitcoindRpcTestClient.sbtBinaryDirectory): BitcoindInstance = {
binaryDirectory: Path = BitcoindRpcTestClient.sbtBinaryDirectory)(implicit
system: ActorSystem): BitcoindInstanceLocal = {
bitcoindVersion match {
case BitcoindVersion.V16 =>
BitcoindRpcTestUtil.v16Instance(port,
@ -624,15 +624,21 @@ trait BitcoindRpcTestUtil extends Logging {
maxTries: Int = 50)(implicit system: ActorSystem): Future[Unit] = {
implicit val ec = system.dispatcher
AsyncUtil
.retryUntilSatisfiedF(conditionF = { () =>
Future {
val dir = client.getDaemon.datadir
FileUtil.deleteTmpDir(dir)
!dir.exists()
}
},
interval = interval,
maxTries = maxTries)
.retryUntilSatisfiedF(
conditionF = { () =>
Future {
val dir = client.getDaemon match {
case _: BitcoindInstanceRemote =>
sys.error(s"Cannot have remote bitcoind instance in testkit")
case local: BitcoindInstanceLocal => local.datadir
}
FileUtil.deleteTmpDir(dir)
!dir.exists()
}
},
interval = interval,
maxTries = maxTries
)
}
/** Returns a pair of unconnected
@ -982,13 +988,13 @@ trait BitcoindRpcTestUtil extends Logging {
case v16: BitcoindV16RpcClient =>
v16.getAddressInfo(address).map(_.pubkey)
case other: BitcoindRpcClient =>
if (
other.instance.getVersion.toString >= BitcoindVersion.V17.toString
) {
val v17 = new BitcoindV17RpcClient(other.instance)
v17.getAddressInfo(address).map(_.pubkey)
} else {
other.getAddressInfo(address).map(_.pubkey)
other.version.flatMap { v =>
if (v.toString >= BitcoindVersion.V17.toString) {
val v17 = new BitcoindV17RpcClient(other.instance)
v17.getAddressInfo(address).map(_.pubkey)
} else {
other.getAddressInfo(address).map(_.pubkey)
}
}
}
}
@ -1121,10 +1127,13 @@ trait BitcoindRpcTestUtil extends Logging {
* this vectorbuilder.
*/
def startedBitcoindRpcClient(
instance: BitcoindInstance = BitcoindRpcTestUtil.instance(),
instanceOpt: Option[BitcoindInstanceLocal] = None,
clientAccum: RpcClientAccum)(implicit
system: ActorSystem): Future[BitcoindRpcClient] = {
implicit val ec: ExecutionContextExecutor = system.dispatcher
val instance = instanceOpt.getOrElse(BitcoindRpcTestUtil.instance())
require(
instance.datadir.getPath.startsWith(Properties.tmpDir),
s"${instance.datadir} is not in user temp dir! This could lead to bad things happening.")

View file

@ -2,7 +2,7 @@ package org.bitcoins.testkit.util
import akka.actor.ActorSystem
import org.bitcoins.rpc.client.common.{BitcoindRpcClient, BitcoindVersion}
import org.bitcoins.rpc.config.BitcoindInstance
import org.bitcoins.rpc.config.BitcoindInstanceLocal
import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil
import java.nio.file.{Files, Path}
@ -17,7 +17,7 @@ case class BitcoindRpcTestClient(
s"Path did not exist! got=${binary.toAbsolutePath.toString}")
import system.dispatcher
private lazy val bitcoindInstance: BitcoindInstance = {
private lazy val bitcoindInstance: BitcoindInstanceLocal = {
BitcoindRpcTestUtil.getInstance(bitcoindVersion = version,
binaryDirectory = binaryDirectory)
}
@ -32,7 +32,7 @@ case class BitcoindRpcTestClient(
case Some(client) => Future.successful(client)
case None =>
val clientF =
BitcoindRpcTestUtil.startedBitcoindRpcClient(bitcoindInstance,
BitcoindRpcTestUtil.startedBitcoindRpcClient(Some(bitcoindInstance),
clientAccum =
Vector.newBuilder)
clientF.map { c =>