From a6898defe23ca34eeb5777bc189fd9e916316777 Mon Sep 17 00:00:00 2001 From: rorp Date: Fri, 19 Nov 2021 11:58:27 -0800 Subject: [PATCH] Support for Bitcoin Core v22 (#3834) * Support for Bitcoin Core v22 * respond to the PR comments --- .../org/bitcoins/rpc/common/UtilRpcTest.scala | 2 +- .../rpc/v22/BitcoindV22RpcClientTest.scala | 52 +++++++++++++++++++ bitcoind-rpc/bitcoind-rpc.sbt | 3 +- .../rpc/client/common/BitcoindRpcClient.scala | 18 +++++-- .../rpc/client/common/BlockchainRpc.scala | 2 +- .../rpc/client/common/MempoolRpc.scala | 8 +-- .../rpc/client/common/MultisigRpc.scala | 4 +- .../bitcoins/rpc/client/common/P2PRpc.scala | 4 +- .../rpc/client/common/RawTransactionRpc.scala | 2 +- .../bitcoins/rpc/client/common/UtilRpc.scala | 4 +- .../rpc/client/common/WalletRpc.scala | 10 ++-- .../rpc/client/v20/V20MultisigRpc.scala | 4 +- .../rpc/client/v21/BitcoindV21RpcClient.scala | 2 +- .../rpc/client/v22/BitcoindV22RpcClient.scala | 50 ++++++++++++++++++ .../rpc/config/BitcoindInstance.scala | 2 + docs/rpc/bitcoind.md | 1 + .../peer/DataMessageHandlerTest.scala | 18 +++---- .../node/NodeTestWithCachedBitcoind.scala | 32 ++++++------ .../bitcoins/testkit/node/NodeUnitTest.scala | 37 ++++++++++--- .../fixture/NodeConnectedWithBitcoind.scala | 12 +++++ .../testkit/rpc/BitcoindFixtures.scala | 35 +++++++++++++ .../testkit/rpc/BitcoindRpcTestUtil.scala | 34 ++++++++++-- .../bitcoins/testkit/rpc/CachedBitcoind.scala | 32 ++++++++++++ 23 files changed, 306 insertions(+), 62 deletions(-) create mode 100644 bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/v22/BitcoindV22RpcClientTest.scala create mode 100644 bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/v22/BitcoindV22RpcClient.scala diff --git a/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/common/UtilRpcTest.scala b/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/common/UtilRpcTest.scala index 17ebafbde5..e55f9dab8b 100644 --- a/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/common/UtilRpcTest.scala +++ b/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/common/UtilRpcTest.scala @@ -13,7 +13,7 @@ import scala.concurrent.Future class UtilRpcTest extends BitcoindRpcTest { lazy val clientsF: Future[(BitcoindRpcClient, BitcoindRpcClient)] = - BitcoindRpcTestUtil.createNodePair(clientAccum = clientAccum) + BitcoindRpcTestUtil.createNodePairV21(clientAccum = clientAccum) behavior of "RpcUtilTest" diff --git a/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/v22/BitcoindV22RpcClientTest.scala b/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/v22/BitcoindV22RpcClientTest.scala new file mode 100644 index 0000000000..0fca643056 --- /dev/null +++ b/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/v22/BitcoindV22RpcClientTest.scala @@ -0,0 +1,52 @@ +package org.bitcoins.rpc.v22 + +import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts.AddressType +import org.bitcoins.core.protocol.P2PKHAddress +import org.bitcoins.core.script.ScriptType +import org.bitcoins.crypto.ECPrivateKey +import org.bitcoins.rpc.client.common.BitcoindVersion +import org.bitcoins.rpc.client.v22.BitcoindV22RpcClient +import org.bitcoins.testkit.rpc.BitcoindFixturesFundedCachedV22 + +class BitcoindV22RpcClientTest extends BitcoindFixturesFundedCachedV22 { + + behavior of "BitcoindV22RpcClient" + + it should "be able to start a V22 bitcoind instance" in { + client: BitcoindV22RpcClient => + for { + v <- client.version + } yield assert(v == BitcoindVersion.V22) + } + + it should "be able to get network info" in { + freshClient: BitcoindV22RpcClient => + for { + info <- freshClient.getNetworkInfo + } yield { + assert(info.networkactive) + assert(info.localrelay) + } + } + + it should "be able to decode a reedem script" in { + client: BitcoindV22RpcClient => + val ecPrivKey1 = ECPrivateKey.freshPrivateKey + val pubKey1 = ecPrivKey1.publicKey + for { + address <- client.getNewAddress(addressType = AddressType.Legacy) + multisig <- + client + .addMultiSigAddress( + 2, + Vector(Left(pubKey1), Right(address.asInstanceOf[P2PKHAddress]))) + decoded <- client.decodeScript(multisig.redeemScript) + } yield { + assert(decoded.typeOfScript.contains(ScriptType.MULTISIG)) + // these fields are deprecated since v22 + assert(decoded.reqSigs.isEmpty) + assert(decoded.addresses.isEmpty) + } + } + +} diff --git a/bitcoind-rpc/bitcoind-rpc.sbt b/bitcoind-rpc/bitcoind-rpc.sbt index 4ea1a138ee..0f27933f2e 100644 --- a/bitcoind-rpc/bitcoind-rpc.sbt +++ b/bitcoind-rpc/bitcoind-rpc.sbt @@ -25,7 +25,8 @@ TaskKeys.downloadBitcoind := { "0.18.99" // TODO: change this when new version compiled on suredbits server val versions = - List("0.21.1", + List("22.0", + "0.21.1", "0.20.1", "0.19.0.1", "0.18.1", diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/BitcoindRpcClient.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/BitcoindRpcClient.scala index 2b942e6c35..b6ec6131bb 100644 --- a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/BitcoindRpcClient.scala +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/BitcoindRpcClient.scala @@ -27,6 +27,7 @@ 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.client.v22.BitcoindV22RpcClient import org.bitcoins.rpc.config.{ BitcoindConfig, BitcoindInstance, @@ -219,14 +220,14 @@ class BitcoindRpcClient(override val instance: BitcoindInstance)(implicit protected def filtersUnsupported: Future[Nothing] = { version.map { v => throw new UnsupportedOperationException( - s"bitcoind $v does not support block filters") + s"Bitcoin Core $v does not support block filters") } } protected def filterHeadersUnsupported: Future[Nothing] = { version.map { v => throw new UnsupportedOperationException( - s"bitcoind $v does not support block filters headers through the rpc") + s"Bitcoin Core $v does not support block filters headers through the rpc") } } } @@ -282,11 +283,12 @@ object BitcoindRpcClient { case BitcoindVersion.V19 => BitcoindV19RpcClient.withActorSystem(instance) case BitcoindVersion.V20 => BitcoindV20RpcClient.withActorSystem(instance) case BitcoindVersion.V21 => BitcoindV21RpcClient.withActorSystem(instance) + case BitcoindVersion.V22 => BitcoindV22RpcClient.withActorSystem(instance) case BitcoindVersion.Experimental => BitcoindV18RpcClient.withActorSystem(instance) case BitcoindVersion.Unknown => sys.error( - s"Cannot create a bitcoind from a unknown or experimental version") + s"Cannot create a Bitcoin Core RPC client: unsupported version") } bitcoind @@ -304,9 +306,10 @@ sealed trait BitcoindVersion object BitcoindVersion extends StringFactory[BitcoindVersion] { /** The newest version of `bitcoind` we support */ - val newest: BitcoindVersion = V21 + val newest: BitcoindVersion = V22 - val standard: Vector[BitcoindVersion] = Vector(V16, V17, V18, V19, V20, V21) + val standard: Vector[BitcoindVersion] = + Vector(V16, V17, V18, V19, V20, V21, V22) val known: Vector[BitcoindVersion] = standard :+ Experimental @@ -334,6 +337,10 @@ object BitcoindVersion extends StringFactory[BitcoindVersion] { override def toString: String = "v0.21" } + case object V22 extends BitcoindVersion { + override def toString: String = "v22" + } + case object Experimental extends BitcoindVersion { override def toString: String = "v0.18.99" } @@ -366,6 +373,7 @@ object BitcoindVersion extends StringFactory[BitcoindVersion] { case "19" => V19 case "20" => V20 case "21" => V21 + case "22" => V22 case _ => Unknown } } diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/BlockchainRpc.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/BlockchainRpc.scala index 9b70cdfdd2..ee5be61e1a 100644 --- a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/BlockchainRpc.scala +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/BlockchainRpc.scala @@ -31,7 +31,7 @@ trait BlockchainRpc { self: Client => self.version.flatMap { case V16 | V17 | V18 => bitcoindCall[GetBlockChainInfoResultPreV19]("getblockchaininfo") - case V21 | V20 | V19 | Experimental | Unknown => + case V22 | V21 | V20 | V19 | Experimental | Unknown => bitcoindCall[GetBlockChainInfoResultPostV19]("getblockchaininfo") } } diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/MempoolRpc.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/MempoolRpc.scala index 14f0983ca6..81e9f4b6b4 100644 --- a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/MempoolRpc.scala +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/MempoolRpc.scala @@ -32,7 +32,7 @@ trait MempoolRpc { self: Client => Map[DoubleSha256DigestBE, GetMemPoolResult]] = { self.version.flatMap { - case V21 | V20 | V19 | Experimental | Unknown => + case V22 | V21 | V20 | V19 | Experimental | Unknown => bitcoindCall[Map[DoubleSha256DigestBE, GetMemPoolResultPostV19]]( "getmempoolancestors", List(JsString(txid.hex), JsBoolean(true))) @@ -63,7 +63,7 @@ trait MempoolRpc { self: Client => def getMemPoolDescendantsVerbose(txid: DoubleSha256DigestBE): Future[ Map[DoubleSha256DigestBE, GetMemPoolResult]] = { self.version.flatMap { - case V21 | V20 | V19 | Experimental | Unknown => + case V22 | V21 | V20 | V19 | Experimental | Unknown => bitcoindCall[Map[DoubleSha256DigestBE, GetMemPoolResultPostV19]]( "getmempooldescendants", List(JsString(txid.hex), JsBoolean(true))) @@ -83,7 +83,7 @@ trait MempoolRpc { self: Client => txid: DoubleSha256DigestBE): Future[GetMemPoolEntryResult] = { self.version.flatMap { - case V21 | V20 | V19 | Experimental | Unknown => + case V22 | V21 | V20 | V19 | Experimental | Unknown => bitcoindCall[GetMemPoolEntryResultPostV19]("getmempoolentry", List(JsString(txid.hex))) case V16 | V17 | V18 => @@ -125,7 +125,7 @@ trait MempoolRpc { self: Client => Map[DoubleSha256DigestBE, GetMemPoolResult]] = { self.version.flatMap { - case V21 | V20 | V19 | Experimental | Unknown => + case V22 | V21 | V20 | V19 | Experimental | Unknown => bitcoindCall[Map[DoubleSha256DigestBE, GetMemPoolResultPostV19]]( "getrawmempool", List(JsBoolean(true))) diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/MultisigRpc.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/MultisigRpc.scala index 93f2196bd7..161808d352 100644 --- a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/MultisigRpc.scala +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/MultisigRpc.scala @@ -41,7 +41,7 @@ trait MultisigRpc { self: Client => JsString(account)) ++ addressType.map(Json.toJson(_)).toList self.version.flatMap { - case V21 | V20 | Unknown => + case V22 | V21 | V20 | Unknown => bitcoindCall[MultiSigResultPostV20]( "addmultisigaddress", params, @@ -83,7 +83,7 @@ trait MultisigRpc { self: Client => keys: Vector[ECPublicKey], walletNameOpt: Option[String] = None): Future[MultiSigResult] = { self.version.flatMap { - case V21 | V20 | Unknown => + case V22 | V21 | V20 | Unknown => bitcoindCall[MultiSigResultPostV20]( "createmultisig", List(JsNumber(minSignatures), Json.toJson(keys.map(_.hex))), diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/P2PRpc.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/P2PRpc.scala index 4a7fe63d5a..d613afc7fd 100644 --- a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/P2PRpc.scala +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/P2PRpc.scala @@ -64,7 +64,7 @@ trait P2PRpc { self: Client => def getPeerInfo: Future[Vector[Peer]] = { self.version.flatMap { - case V21 | Unknown => + case V22 | V21 | Unknown => bitcoindCall[Vector[PeerPostV21]]("getpeerinfo") case V20 => bitcoindCall[Vector[PeerV20]]("getpeerinfo") @@ -75,7 +75,7 @@ trait P2PRpc { self: Client => def listBanned: Future[Vector[NodeBan]] = { self.version.flatMap { - case V21 | V20 | Unknown => + case V22 | V21 | V20 | Unknown => bitcoindCall[Vector[NodeBanPostV20]]("listbanned") case V16 | V17 | V18 | V19 | Experimental => bitcoindCall[Vector[NodeBanPreV20]]("listbanned") diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/RawTransactionRpc.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/RawTransactionRpc.scala index da3b9368dd..45dc8fe186 100644 --- a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/RawTransactionRpc.scala +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/RawTransactionRpc.scala @@ -111,7 +111,7 @@ trait RawTransactionRpc { self: Client => maxfeerate: Double = 0.10): Future[DoubleSha256DigestBE] = { val feeParameterF = self.version.map { - case V21 | V20 | V19 | Experimental | Unknown => + case V22 | V21 | V20 | V19 | Experimental | Unknown => JsNumber(maxfeerate) case V16 | V17 | V18 => JsBoolean(maxfeerate == 0) diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/UtilRpc.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/UtilRpc.scala index 9e859bec2b..923259ba75 100644 --- a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/UtilRpc.scala +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/UtilRpc.scala @@ -26,7 +26,7 @@ trait UtilRpc { self: Client => def getIndexInfo: Future[Map[String, IndexInfoResult]] = { version.flatMap { - case V21 | Unknown => + case V22 | V21 | Unknown => bitcoindCall[Map[String, IndexInfoResult]]("getindexinfo") case V16 | V17 | V18 | V19 | V20 | Experimental => Future.failed( @@ -37,7 +37,7 @@ trait UtilRpc { self: Client => def getIndexInfo(indexName: String): Future[IndexInfoResult] = { version.flatMap { - case V21 | Unknown => + case V22 | V21 | Unknown => bitcoindCall[Map[String, IndexInfoResult]]( "getindexinfo", List(JsString(indexName))).map(_.head._2) diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/WalletRpc.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/WalletRpc.scala index bc64d53a84..9e0f398635 100644 --- a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/WalletRpc.scala +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/WalletRpc.scala @@ -307,7 +307,7 @@ trait WalletRpc { self: Client => ): Future[SetWalletFlagResult] = { self.version.flatMap { - case V21 | V20 | V19 | Experimental | Unknown => + case V22 | V21 | V20 | V19 | Experimental | Unknown => bitcoindCall[SetWalletFlagResult]( "setwalletflag", List(JsString(flag.toString), Json.toJson(value)), @@ -321,7 +321,7 @@ trait WalletRpc { self: Client => def getBalances: Future[GetBalancesResult] = { self.version.flatMap { - case V21 | V20 | V19 | Experimental | Unknown => + case V22 | V21 | V20 | V19 | Experimental | Unknown => bitcoindCall[GetBalancesResult]("getbalances") case V16 | V17 | V18 => Future.failed( @@ -332,7 +332,7 @@ trait WalletRpc { self: Client => def getBalances(walletName: String): Future[GetBalancesResult] = { self.version.flatMap { - case V21 | V20 | V19 | Experimental | Unknown => + case V22 | V21 | V20 | V19 | Experimental | Unknown => bitcoindCall[GetBalancesResult]("getbalances", uriExtensionOpt = Some(walletExtension(walletName))) @@ -405,7 +405,7 @@ trait WalletRpc { self: Client => blank: Boolean = false, passphrase: String = ""): Future[CreateWalletResult] = self.version.flatMap { - case V21 | V20 | V19 | Experimental | Unknown => + case V22 | V21 | V20 | V19 | Experimental | Unknown => bitcoindCall[CreateWalletResult]("createwallet", List(JsString(walletName), JsBoolean(disablePrivateKeys), @@ -433,7 +433,7 @@ trait WalletRpc { self: Client => "getaddressinfo", List(JsString(address.value)), uriExtensionOpt = walletNameOpt.map(walletExtension)) - case V21 | Unknown => + case V22 | V21 | Unknown => bitcoindCall[AddressInfoResultPostV21]( "getaddressinfo", List(JsString(address.value)), diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/v20/V20MultisigRpc.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/v20/V20MultisigRpc.scala index 709e07c09c..fd471383ee 100644 --- a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/v20/V20MultisigRpc.scala +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/v20/V20MultisigRpc.scala @@ -37,7 +37,7 @@ trait V20MultisigRpc extends MultisigRpc { self: Client => JsString(account)) ++ addressType.map(Json.toJson(_)).toList self.version.flatMap { - case V20 | V21 | Unknown => + case V20 | V21 | V22 | Unknown => bitcoindCall[MultiSigResultPostV20]("addmultisigaddress", params) case version @ (V16 | V17 | V18 | V19 | Experimental) => throw new RuntimeException( @@ -75,7 +75,7 @@ trait V20MultisigRpc extends MultisigRpc { self: Client => keys: Vector[ECPublicKey], walletNameOpt: Option[String] = None): Future[MultiSigResultPostV20] = { self.version.flatMap { - case V20 | V21 | Unknown => + case V20 | V21 | V22 | Unknown => bitcoindCall[MultiSigResultPostV20]( "createmultisig", List(JsNumber(minSignatures), Json.toJson(keys.map(_.hex))), diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/v21/BitcoindV21RpcClient.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/v21/BitcoindV21RpcClient.scala index 9fc0e45399..81eec3d3af 100644 --- a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/v21/BitcoindV21RpcClient.scala +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/v21/BitcoindV21RpcClient.scala @@ -79,7 +79,7 @@ class BitcoindV21RpcClient(override val instance: BitcoindInstance)(implicit } yield Vector(filter.filterDb(height)) } - override lazy val version: Future[BitcoindVersion.V21.type] = + override lazy val version: Future[BitcoindVersion] = Future.successful(BitcoindVersion.V21) /** $signRawTx diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/v22/BitcoindV22RpcClient.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/v22/BitcoindV22RpcClient.scala new file mode 100644 index 0000000000..aafb490e3c --- /dev/null +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/v22/BitcoindV22RpcClient.scala @@ -0,0 +1,50 @@ +package org.bitcoins.rpc.client.v22 + +import akka.actor.ActorSystem +import org.bitcoins.rpc.client.common.{BitcoindRpcClient, BitcoindVersion} +import org.bitcoins.rpc.client.v21.BitcoindV21RpcClient +import org.bitcoins.rpc.config.BitcoindInstance + +import scala.concurrent.Future +import scala.util.Try + +/** Class for creating a BitcoindV22 instance that can access RPCs + */ +class BitcoindV22RpcClient(override val instance: BitcoindInstance)(implicit + actorSystem: ActorSystem) + extends BitcoindV21RpcClient(instance) { + + override lazy val version: Future[BitcoindVersion] = + Future.successful(BitcoindVersion.V22) +} + +object BitcoindV22RpcClient { + + /** Creates an RPC client from the given instance. + * + * Behind the scenes, we create an actor system for + * you. You can use `withActorSystem` if you want to + * manually specify an actor system for the RPC client. + */ + def apply(instance: BitcoindInstance): BitcoindV22RpcClient = { + implicit val system: ActorSystem = + ActorSystem.create(BitcoindRpcClient.ActorSystemName) + withActorSystem(instance) + } + + /** Creates an RPC client from the given instance, + * together with the given actor system. This is for + * advanced users, where you need fine grained control + * over the RPC client. + */ + def withActorSystem(instance: BitcoindInstance)(implicit + system: ActorSystem): BitcoindV22RpcClient = + new BitcoindV22RpcClient(instance)(system) + + def fromUnknownVersion( + rpcClient: BitcoindRpcClient): Try[BitcoindV22RpcClient] = + Try { + new BitcoindV22RpcClient(rpcClient.instance)(rpcClient.system) + } + +} 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 3745bcf1a3..dabe87eaa8 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 @@ -70,6 +70,8 @@ sealed trait BitcoindInstanceLocal extends BitcoindInstance { BitcoindVersion.V20 case _: String if foundVersion.startsWith(BitcoindVersion.V21.toString) => BitcoindVersion.V21 + case _: String if foundVersion.startsWith(BitcoindVersion.V22.toString) => + BitcoindVersion.V22 case _: String => BitcoindVersion.Unknown } } diff --git a/docs/rpc/bitcoind.md b/docs/rpc/bitcoind.md index 1f62a79e5c..0a7adc1765 100644 --- a/docs/rpc/bitcoind.md +++ b/docs/rpc/bitcoind.md @@ -12,6 +12,7 @@ The Bitcoin Core RPC client in Bitcoin-S currently supports the Bitcoin Core - 0.19 - 0.20 - 0.21 +- 22 version lines. It can be set up to work with both local and remote Bitcoin Core servers. diff --git a/node-test/src/test/scala/org/bitcoins/node/networking/peer/DataMessageHandlerTest.scala b/node-test/src/test/scala/org/bitcoins/node/networking/peer/DataMessageHandlerTest.scala index db5a029aed..2ed001d484 100644 --- a/node-test/src/test/scala/org/bitcoins/node/networking/peer/DataMessageHandlerTest.scala +++ b/node-test/src/test/scala/org/bitcoins/node/networking/peer/DataMessageHandlerTest.scala @@ -12,7 +12,7 @@ import org.bitcoins.node._ import org.bitcoins.server.BitcoinSAppConfig import org.bitcoins.testkit.BitcoinSTestAppConfig import org.bitcoins.testkit.node.NodeUnitTest -import org.bitcoins.testkit.node.fixture.SpvNodeConnectedWithBitcoindV21 +import org.bitcoins.testkit.node.fixture.SpvNodeConnectedWithBitcoindV22 import org.bitcoins.testkit.tor.CachedTor import org.scalatest.FutureOutcome @@ -24,14 +24,14 @@ class DataMessageHandlerTest extends NodeUnitTest with CachedTor { override protected def getFreshConfig: BitcoinSAppConfig = BitcoinSTestAppConfig.getSpvWithEmbeddedDbTestConfig(pgUrl, Vector.empty) - override type FixtureParam = SpvNodeConnectedWithBitcoindV21 + override type FixtureParam = SpvNodeConnectedWithBitcoindV22 override def withFixture(test: OneArgAsyncTest): FutureOutcome = - withSpvNodeConnectedToBitcoindV21(test)(system, getFreshConfig) + withSpvNodeConnectedToBitcoindV22(test)(system, getFreshConfig) it must "catch errors and not fail when processing an invalid payload" in { - param: SpvNodeConnectedWithBitcoindV21 => - val SpvNodeConnectedWithBitcoindV21(spv, _) = param + param: SpvNodeConnectedWithBitcoindV22 => + val SpvNodeConnectedWithBitcoindV22(spv, _) = param val sender = spv.peerMsgSenders(0) for { @@ -55,7 +55,7 @@ class DataMessageHandlerTest extends NodeUnitTest with CachedTor { it must "verify OnMerkleBlock callbacks are executed" in { param: FixtureParam => - val SpvNodeConnectedWithBitcoindV21(spv, bitcoind) = param + val SpvNodeConnectedWithBitcoindV22(spv, bitcoind) = param val resultP: Promise[(MerkleBlock, Vector[Transaction])] = Promise() @@ -92,7 +92,7 @@ class DataMessageHandlerTest extends NodeUnitTest with CachedTor { it must "verify OnBlockReceived callbacks are executed" in { param: FixtureParam => - val SpvNodeConnectedWithBitcoindV21(spv, bitcoind) = param + val SpvNodeConnectedWithBitcoindV22(spv, bitcoind) = param val resultP: Promise[Block] = Promise() @@ -124,7 +124,7 @@ class DataMessageHandlerTest extends NodeUnitTest with CachedTor { it must "verify OnBlockHeadersReceived callbacks are executed" in { param: FixtureParam => - val SpvNodeConnectedWithBitcoindV21(spv, bitcoind) = param + val SpvNodeConnectedWithBitcoindV22(spv, bitcoind) = param val resultP: Promise[Vector[BlockHeader]] = Promise() @@ -159,7 +159,7 @@ class DataMessageHandlerTest extends NodeUnitTest with CachedTor { it must "verify OnCompactFilterReceived callbacks are executed" in { param: FixtureParam => - val SpvNodeConnectedWithBitcoindV21(spv, bitcoind) = param + val SpvNodeConnectedWithBitcoindV22(spv, bitcoind) = param val resultP: Promise[Vector[(DoubleSha256Digest, GolombFilter)]] = Promise() diff --git a/testkit/src/main/scala/org/bitcoins/testkit/node/NodeTestWithCachedBitcoind.scala b/testkit/src/main/scala/org/bitcoins/testkit/node/NodeTestWithCachedBitcoind.scala index 7406124728..4def301bf9 100644 --- a/testkit/src/main/scala/org/bitcoins/testkit/node/NodeTestWithCachedBitcoind.scala +++ b/testkit/src/main/scala/org/bitcoins/testkit/node/NodeTestWithCachedBitcoind.scala @@ -2,22 +2,22 @@ package org.bitcoins.testkit.node import akka.actor.ActorSystem import org.bitcoins.core.api.node.NodeType -import org.bitcoins.node.models.Peer import org.bitcoins.node.Node +import org.bitcoins.node.models.Peer import org.bitcoins.rpc.client.common.BitcoindRpcClient -import org.bitcoins.rpc.client.v21.BitcoindV21RpcClient +import org.bitcoins.rpc.client.v19.BitcoindV19RpcClient import org.bitcoins.server.BitcoinSAppConfig import org.bitcoins.testkit.node.NodeUnitTest.{createPeer, syncNeutrinoNode} import org.bitcoins.testkit.node.fixture.{ NeutrinoNodeConnectedWithBitcoind, NeutrinoNodeConnectedWithBitcoinds, SpvNodeConnectedWithBitcoind, - SpvNodeConnectedWithBitcoindV21 + SpvNodeConnectedWithBitcoindV19 } import org.bitcoins.testkit.rpc.{ CachedBitcoind, CachedBitcoindNewest, - CachedBitcoindPairV21, + CachedBitcoindPairV22, CachedBitcoindV19 } import org.bitcoins.testkit.tor.CachedTor @@ -49,7 +49,7 @@ trait NodeTestWithCachedBitcoind extends BaseNodeTest with CachedTor { bitcoind = bitcoind)( system, // Force V18 because Spv is disabled on versions after appConfig), - { case x: SpvNodeFundedWalletBitcoind => + { x: SpvNodeFundedWalletBitcoind => tearDownNodeWithBitcoind(x) } )(test) @@ -75,7 +75,7 @@ trait NodeTestWithCachedBitcoind extends BaseNodeTest with CachedTor { makeDependentFixture[SpvNodeConnectedWithBitcoind]( build = nodeWithBitcoindBuilder, - { case x: SpvNodeConnectedWithBitcoind => + { x: SpvNodeConnectedWithBitcoind => NodeUnitTest.destroyNode(x.node) })(test) } @@ -96,7 +96,7 @@ trait NodeTestWithCachedBitcoind extends BaseNodeTest with CachedTor { } makeDependentFixture[NeutrinoNodeConnectedWithBitcoind]( build = nodeWithBitcoindBuilder, - { case x: NeutrinoNodeConnectedWithBitcoind => + { x: NeutrinoNodeConnectedWithBitcoind => tearDownNode(x.node) })(test) } @@ -120,7 +120,7 @@ trait NodeTestWithCachedBitcoind extends BaseNodeTest with CachedTor { } makeDependentFixture[NeutrinoNodeConnectedWithBitcoinds]( build = nodeWithBitcoindBuilder, - { case x: NeutrinoNodeConnectedWithBitcoinds => + { x: NeutrinoNodeConnectedWithBitcoinds => tearDownNode(x.node) })(test) } @@ -139,7 +139,7 @@ trait NodeTestWithCachedBitcoind extends BaseNodeTest with CachedTor { bip39PasswordOpt = bip39PasswordOpt, bitcoind, walletCallbacks = walletCallbacks)(system, appConfig), - { case x: NeutrinoNodeFundedWalletBitcoind => + { x: NeutrinoNodeFundedWalletBitcoind => tearDownNodeWithBitcoind(x) } )(test) @@ -188,10 +188,10 @@ trait NodeTestWithCachedBitcoindNewest trait NodeTestWithCachedBitcoindPair extends NodeTestWithCachedBitcoind - with CachedBitcoindPairV21 { + with CachedBitcoindPairV22 { override def afterAll(): Unit = { - super[CachedBitcoindPairV21].afterAll() + super[CachedBitcoindPairV22].afterAll() super[NodeTestWithCachedBitcoind].afterAll() } } @@ -202,11 +202,11 @@ trait NodeTestWithCachedBitcoindV19 def withSpvNodeConnectedToBitcoindV19Cached( test: OneArgAsyncTest, - bitcoind: BitcoindV21RpcClient)(implicit + bitcoind: BitcoindV19RpcClient)(implicit system: ActorSystem, appConfig: BitcoinSAppConfig): FutureOutcome = { val nodeWithBitcoindBuilder: () => Future[ - SpvNodeConnectedWithBitcoindV21] = { () => + SpvNodeConnectedWithBitcoindV19] = { () => require(appConfig.nodeConf.nodeType == NodeType.SpvNode) for { peer <- createPeer(bitcoind) @@ -215,12 +215,12 @@ trait NodeTestWithCachedBitcoindV19 appConfig.nodeConf) started <- node.start() _ <- NodeUnitTest.syncSpvNode(started, bitcoind) - } yield SpvNodeConnectedWithBitcoindV21(node, bitcoind) + } yield SpvNodeConnectedWithBitcoindV19(node, bitcoind) } - makeDependentFixture[SpvNodeConnectedWithBitcoindV21]( + makeDependentFixture[SpvNodeConnectedWithBitcoindV19]( build = nodeWithBitcoindBuilder, - { case x: SpvNodeConnectedWithBitcoindV21 => + { x: SpvNodeConnectedWithBitcoindV19 => NodeUnitTest.destroyNode(x.node) } )(test) diff --git a/testkit/src/main/scala/org/bitcoins/testkit/node/NodeUnitTest.scala b/testkit/src/main/scala/org/bitcoins/testkit/node/NodeUnitTest.scala index 4adbeadc98..57f70f91ce 100644 --- a/testkit/src/main/scala/org/bitcoins/testkit/node/NodeUnitTest.scala +++ b/testkit/src/main/scala/org/bitcoins/testkit/node/NodeUnitTest.scala @@ -10,9 +10,10 @@ import org.bitcoins.node._ import org.bitcoins.node.config.NodeAppConfig import org.bitcoins.node.models.Peer import org.bitcoins.node.networking.peer._ -import org.bitcoins.rpc.client.common.BitcoindVersion.{V18, V21} +import org.bitcoins.rpc.client.common.BitcoindVersion.{V18, V21, V22} import org.bitcoins.rpc.client.common.{BitcoindRpcClient, BitcoindVersion} import org.bitcoins.rpc.client.v21.BitcoindV21RpcClient +import org.bitcoins.rpc.client.v22.BitcoindV22RpcClient import org.bitcoins.rpc.util.RpcUtil import org.bitcoins.server.BitcoinSAppConfig import org.bitcoins.testkit.chain.ChainUnitTest @@ -22,12 +23,7 @@ import org.bitcoins.testkit.node.NodeUnitTest.{ emptyPeer, syncNeutrinoNode } -import org.bitcoins.testkit.node.fixture.{ - NeutrinoNodeConnectedWithBitcoind, - NodeConnectedWithBitcoind, - SpvNodeConnectedWithBitcoind, - SpvNodeConnectedWithBitcoindV21 -} +import org.bitcoins.testkit.node.fixture._ import org.bitcoins.testkit.wallet.{BitcoinSWalletTest, WalletWithBitcoindRpc} import org.bitcoins.testkitcore.node.P2PMessageTestUtil import org.bitcoins.wallet.WalletCallbacks @@ -120,6 +116,33 @@ trait NodeUnitTest extends BaseNodeTest { )(test) } + def withSpvNodeConnectedToBitcoindV22(test: OneArgAsyncTest)(implicit + system: ActorSystem, + appConfig: BitcoinSAppConfig): FutureOutcome = { + val nodeWithBitcoindBuilder: () => Future[ + SpvNodeConnectedWithBitcoindV22] = { () => + require(appConfig.nodeConf.nodeType == NodeType.SpvNode) + for { + bitcoind <- + BitcoinSFixture + .createBitcoindWithFunds(Some(V22)) + .map(_.asInstanceOf[BitcoindV22RpcClient]) + peer <- createPeer(bitcoind) + node <- NodeUnitTest.createSpvNode(peer)(system, + appConfig.chainConf, + appConfig.nodeConf) + started <- node.start() + _ <- NodeUnitTest.syncSpvNode(started, bitcoind) + } yield SpvNodeConnectedWithBitcoindV22(node, bitcoind) + } + + makeDependentFixture( + build = nodeWithBitcoindBuilder, + destroy = NodeUnitTest.destroyNodeConnectedWithBitcoind( + _: NodeConnectedWithBitcoind)(system, appConfig) + )(test) + } + def withNeutrinoNodeConnectedToBitcoind( test: OneArgAsyncTest, versionOpt: Option[BitcoindVersion] = None)(implicit diff --git a/testkit/src/main/scala/org/bitcoins/testkit/node/fixture/NodeConnectedWithBitcoind.scala b/testkit/src/main/scala/org/bitcoins/testkit/node/fixture/NodeConnectedWithBitcoind.scala index 0bd2ee11d7..aaf0e0a7a9 100644 --- a/testkit/src/main/scala/org/bitcoins/testkit/node/fixture/NodeConnectedWithBitcoind.scala +++ b/testkit/src/main/scala/org/bitcoins/testkit/node/fixture/NodeConnectedWithBitcoind.scala @@ -2,7 +2,9 @@ package org.bitcoins.testkit.node.fixture import org.bitcoins.node.{NeutrinoNode, Node, SpvNode} import org.bitcoins.rpc.client.common.BitcoindRpcClient +import org.bitcoins.rpc.client.v19.BitcoindV19RpcClient import org.bitcoins.rpc.client.v21.BitcoindV21RpcClient +import org.bitcoins.rpc.client.v22.BitcoindV22RpcClient /** Gives us a fixture that has a SPV node connected with the bitcoind instance */ trait NodeConnectedWithBitcoind { @@ -15,11 +17,21 @@ case class SpvNodeConnectedWithBitcoind( bitcoind: BitcoindRpcClient) extends NodeConnectedWithBitcoind +case class SpvNodeConnectedWithBitcoindV22( + node: SpvNode, + bitcoind: BitcoindV22RpcClient) + extends NodeConnectedWithBitcoind + case class SpvNodeConnectedWithBitcoindV21( node: SpvNode, bitcoind: BitcoindV21RpcClient) extends NodeConnectedWithBitcoind +case class SpvNodeConnectedWithBitcoindV19( + node: SpvNode, + bitcoind: BitcoindV19RpcClient) + extends NodeConnectedWithBitcoind + case class NeutrinoNodeConnectedWithBitcoind( node: NeutrinoNode, bitcoind: BitcoindRpcClient) diff --git a/testkit/src/main/scala/org/bitcoins/testkit/rpc/BitcoindFixtures.scala b/testkit/src/main/scala/org/bitcoins/testkit/rpc/BitcoindFixtures.scala index 7e8b965bdc..12a94911b5 100644 --- a/testkit/src/main/scala/org/bitcoins/testkit/rpc/BitcoindFixtures.scala +++ b/testkit/src/main/scala/org/bitcoins/testkit/rpc/BitcoindFixtures.scala @@ -7,6 +7,7 @@ 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.client.v22.BitcoindV22RpcClient import org.bitcoins.rpc.util.{NodePair, NodeTriple} import org.bitcoins.testkit.EmbeddedPg import org.bitcoins.testkit.fixtures.BitcoinSFixture @@ -189,6 +190,40 @@ trait BitcoindFixturesFundedCachedV21 } } +/** Test trait that caches a [[BitcoindV22RpcClient]] that is funded + * and available to use with fixtures + */ +trait BitcoindFixturesFundedCachedV22 + extends BitcoinSAsyncFixtureTest + with BitcoindFixturesFundedCached + with CachedBitcoindV22 { + override type FixtureParam = BitcoindV22RpcClient + + override def withFixture(test: OneArgAsyncTest): FutureOutcome = { + val f: Future[Outcome] = for { + bitcoind <- cachedBitcoindWithFundsF + futOutcome = withV22FundedBitcoindCached(test, bitcoind) + fut <- futOutcome.toFuture + } yield fut + new FutureOutcome(f) + } + + def withV22FundedBitcoindCached( + test: OneArgAsyncTest, + bitcoind: BitcoindV22RpcClient): FutureOutcome = { + makeDependentFixture[BitcoindV22RpcClient]( + () => Future.successful(bitcoind), + { case _ => + Future.unit // don't want to destroy anything since it is cached + })(test) + } + + override def afterAll(): Unit = { + super[CachedBitcoindV22].afterAll() + super[BitcoinSAsyncFixtureTest].afterAll() + } +} + trait BitcoindFixturesFundedCachedNewest extends BitcoinSAsyncFixtureTest with BitcoindFixturesFundedCached diff --git a/testkit/src/main/scala/org/bitcoins/testkit/rpc/BitcoindRpcTestUtil.scala b/testkit/src/main/scala/org/bitcoins/testkit/rpc/BitcoindRpcTestUtil.scala index 84cfb2410a..bd157cb8a5 100644 --- a/testkit/src/main/scala/org/bitcoins/testkit/rpc/BitcoindRpcTestUtil.scala +++ b/testkit/src/main/scala/org/bitcoins/testkit/rpc/BitcoindRpcTestUtil.scala @@ -36,6 +36,7 @@ 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.client.v22.BitcoindV22RpcClient import org.bitcoins.rpc.config._ import org.bitcoins.rpc.util.RpcUtil import org.bitcoins.testkit.util.{BitcoindRpcTestClient, FileUtil, TorUtil} @@ -174,7 +175,7 @@ trait BitcoindRpcTestUtil extends Logging { version match { // default to newest version case Unknown => getBinary(BitcoindVersion.newest, binaryDirectory) - case known @ (Experimental | V16 | V17 | V18 | V19 | V20 | V21) => + case known @ (Experimental | V16 | V17 | V18 | V19 | V20 | V21 | V22) => val fileList = Files .list(binaryDirectory) .iterator() @@ -223,8 +224,8 @@ trait BitcoindRpcTestUtil extends Logging { val hasNeutrinoSupport = versionOpt match { case Some(V16) | Some(V17) | Some(V18) => false - case Some(V19) | Some(V20) | Some(V21) | Some(Experimental) | Some( - Unknown) | None => + case Some(V19) | Some(V20) | Some(V21) | Some(V22) | Some(Experimental) | + Some(Unknown) | None => true } val configFile = @@ -334,6 +335,20 @@ trait BitcoindRpcTestUtil extends Logging { versionOpt = Some(BitcoindVersion.V21), binaryDirectory = binaryDirectory) + def v22Instance( + port: Int = RpcUtil.randomPort, + rpcPort: Int = RpcUtil.randomPort, + zmqConfig: ZmqConfig = RpcUtil.zmqConfig, + pruneMode: Boolean = false, + binaryDirectory: Path = BitcoindRpcTestClient.sbtBinaryDirectory + )(implicit system: ActorSystem): BitcoindInstanceLocal = + instance(port = port, + rpcPort = rpcPort, + zmqConfig = zmqConfig, + pruneMode = pruneMode, + versionOpt = Some(BitcoindVersion.V22), + binaryDirectory = binaryDirectory) + def vExperimentalInstance( port: Int = RpcUtil.randomPort, rpcPort: Int = RpcUtil.randomPort, @@ -394,6 +409,12 @@ trait BitcoindRpcTestUtil extends Logging { zmqConfig, pruneMode, binaryDirectory = binaryDirectory) + case BitcoindVersion.V22 => + BitcoindRpcTestUtil.v22Instance(port, + rpcPort, + zmqConfig, + pruneMode, + binaryDirectory = binaryDirectory) case BitcoindVersion.Experimental => BitcoindRpcTestUtil.vExperimentalInstance(port, rpcPort, @@ -728,6 +749,9 @@ trait BitcoindRpcTestUtil extends Logging { case BitcoindVersion.V21 => BitcoindV21RpcClient.withActorSystem( BitcoindRpcTestUtil.v21Instance()) + case BitcoindVersion.V22 => + BitcoindV22RpcClient.withActorSystem( + BitcoindRpcTestUtil.v22Instance()) case BitcoindVersion.Experimental => BitcoindV19RpcClient.withActorSystem( BitcoindRpcTestUtil.vExperimentalInstance()) @@ -836,6 +860,10 @@ trait BitcoindRpcTestUtil extends Logging { system: ActorSystem): Future[(BitcoindV21RpcClient, BitcoindV21RpcClient)] = createNodePairInternal(BitcoindVersion.V21, clientAccum) + def createNodePairV22(clientAccum: RpcClientAccum)(implicit + system: ActorSystem): Future[(BitcoindV21RpcClient, BitcoindV21RpcClient)] = + createNodePairInternal(BitcoindVersion.V22, clientAccum) + /** Returns a triple of [[org.bitcoins.rpc.client.common.BitcoindRpcClient BitcoindRpcClient]] * that are connected with some blocks in the chain */ diff --git a/testkit/src/main/scala/org/bitcoins/testkit/rpc/CachedBitcoind.scala b/testkit/src/main/scala/org/bitcoins/testkit/rpc/CachedBitcoind.scala index b944f8aa0a..60bd47b440 100644 --- a/testkit/src/main/scala/org/bitcoins/testkit/rpc/CachedBitcoind.scala +++ b/testkit/src/main/scala/org/bitcoins/testkit/rpc/CachedBitcoind.scala @@ -6,6 +6,7 @@ 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.client.v22.BitcoindV22RpcClient import org.bitcoins.rpc.util.{NodePair, NodeTriple} import org.bitcoins.testkit.fixtures.BitcoinSFixture import org.bitcoins.testkit.util.BitcoinSAkkaAsyncTest @@ -131,6 +132,18 @@ trait CachedBitcoindV21 extends CachedBitcoindFunded[BitcoindV21RpcClient] { } } +trait CachedBitcoindV22 extends CachedBitcoindFunded[BitcoindV22RpcClient] { + _: BitcoinSAkkaAsyncTest => + + override protected lazy val cachedBitcoindWithFundsF: Future[ + BitcoindV22RpcClient] = { + val _ = isBitcoindUsed.set(true) + BitcoinSFixture + .createBitcoindWithFunds(Some(BitcoindVersion.V22)) + .map(_.asInstanceOf[BitcoindV22RpcClient]) + } +} + trait CachedBitcoindCollection[T <: BitcoindRpcClient] extends CachedBitcoind[T] { _: BitcoinSAkkaAsyncTest => @@ -208,6 +221,25 @@ trait CachedBitcoindPairV21 } } +trait CachedBitcoindPairV22 + extends CachedBitcoindCollection[BitcoindV21RpcClient] { + _: BitcoinSAkkaAsyncTest => + + override val version: BitcoindVersion = BitcoindVersion.V22 + + lazy val clientsF: Future[NodePair[BitcoindV22RpcClient]] = { + BitcoindRpcTestUtil + .createNodePair[BitcoindV22RpcClient](version) + .map(NodePair.fromTuple) + .map { tuple => + isClientsUsed.set(true) + val clients = cachedClients.get() + cachedClients.set(clients ++ tuple.toVector) + tuple + } + } +} + trait CachedBitcoindTriple[T <: BitcoindRpcClient] extends CachedBitcoindCollection[T] { _: BitcoinSAkkaAsyncTest =>