Implement bitcoind RPC 27.0 (#5552)

* Add bitcoind 27.0

* Fix linux, windows hashes

* Add BitcoindV27RpcClient

* Switch BitcoindVersion.newest = V27

* Try downgrading lndRpcTest to use V26 in tests

* Make pattern matching against 'bitcoind --version' more resilient for new versions of bitcoind

* Add awaitDisconnect() to BitcoindRpcTestUtil.disconnectNodes(), fix flaky v2 connection in unit test
This commit is contained in:
Chris Stewart 2024-04-28 13:16:39 -05:00 committed by GitHub
parent 7ef6086673
commit 6543b261c4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 164 additions and 74 deletions

View file

@ -148,7 +148,11 @@ class DisconnectedPeersRpcTest
_ <- BitcoindRpcTestUtil.awaitConnection(from = freshClient, _ <- BitcoindRpcTestUtil.awaitConnection(from = freshClient,
to = otherFreshClient, to = otherFreshClient,
interval = 1.second) interval = 1.second)
// takes awhile to figure out what the transport type is...
// wait until we are no longer 'detecting' the transport_protocol_type
_ <- AsyncUtil.retryUntilSatisfiedF(() =>
freshClient.getPeerInfo.map(
_.exists(_.transport_protocol_type != "detecting")))
info <- freshClient.getPeerInfo info <- freshClient.getPeerInfo
} yield { } yield {
assert(info.exists(_.transport_protocol_type == "v2")) assert(info.exists(_.transport_protocol_type == "v2"))

View file

@ -23,7 +23,7 @@ TaskKeys.downloadBitcoind := {
} }
val versions = val versions =
List("26.1", "25.2") List("27.0", "26.1", "25.2")
logger.debug( logger.debug(
s"(Maybe) downloading Bitcoin Core binaries for versions: ${versions.mkString(",")}") s"(Maybe) downloading Bitcoin Core binaries for versions: ${versions.mkString(",")}")
@ -95,7 +95,8 @@ TaskKeys.downloadBitcoind := {
if (Properties.isLinux) if (Properties.isLinux)
Map( Map(
"25.2" -> "8d8c387e597e0edfc256f0bbace1dac3ad1ebf4a3c06da3e2975fda333817dea", "25.2" -> "8d8c387e597e0edfc256f0bbace1dac3ad1ebf4a3c06da3e2975fda333817dea",
"26.1" -> "a5b7d206384a8100058d3f2e2f02123a8e49e83f523499e70e86e121a4897d5b" "26.1" -> "a5b7d206384a8100058d3f2e2f02123a8e49e83f523499e70e86e121a4897d5b",
"27.0" -> "2a6974c5486f528793c79d42694b5987401e4a43c97f62b1383abf35bcee44a8"
) )
else if (Properties.isMac) else if (Properties.isMac)
Map( Map(
@ -105,13 +106,18 @@ TaskKeys.downloadBitcoind := {
"e06ba379f6039ca99bc32d3e7974d420a31363498936f88aac7bab6f239de0f5"), "e06ba379f6039ca99bc32d3e7974d420a31363498936f88aac7bab6f239de0f5"),
"26.1" -> (if (System.getProperty("os.arch") == "aarch64") "26.1" -> (if (System.getProperty("os.arch") == "aarch64")
"8a8e415763b7ffd5988153cf03967d812eca629016dd3b0ddf6da3ab6f4a3621" "8a8e415763b7ffd5988153cf03967d812eca629016dd3b0ddf6da3ab6f4a3621"
else
""),
"27.0" -> (if (System.getProperty("os.arch") == "aarch64")
"1d9d9b837297a73fc7a3b1cfed376644e3fa25c4e1672fbc143d5946cb52431d"
else else
"") "")
) )
else if (Properties.isWin) else if (Properties.isWin)
Map( Map(
"25.2" -> "c2ac84f55ee879caefd4414868d318a741c52a7286da190bf7233d86a2ffca69", "25.2" -> "c2ac84f55ee879caefd4414868d318a741c52a7286da190bf7233d86a2ffca69",
"26.1" -> "7bd0849e47472aeff99a0ea2c0cefd98f5be829e5a2d3b0168b5a54456cc638a" "26.1" -> "7bd0849e47472aeff99a0ea2c0cefd98f5be829e5a2d3b0168b5a54456cc638a",
"27.0" -> "ca75babeaa3fb75f5a166f544adaa93fd7c1f06cf20d4e2c8c2a8b010f4c7603"
) )
else sys.error(s"Unsupported OS: ${Properties.osName}") else sys.error(s"Unsupported OS: ${Properties.osName}")

View file

@ -18,6 +18,7 @@ import org.bitcoins.rpc.client.v18.V18AssortedRpc
import org.bitcoins.rpc.client.v20.V20MultisigRpc import org.bitcoins.rpc.client.v20.V20MultisigRpc
import org.bitcoins.rpc.client.v25.BitcoindV25RpcClient import org.bitcoins.rpc.client.v25.BitcoindV25RpcClient
import org.bitcoins.rpc.client.v26.BitcoindV26RpcClient import org.bitcoins.rpc.client.v26.BitcoindV26RpcClient
import org.bitcoins.rpc.client.v27.BitcoindV27RpcClient
import org.bitcoins.rpc.config._ import org.bitcoins.rpc.config._
import java.io.File import java.io.File
@ -345,6 +346,7 @@ object BitcoindRpcClient {
val bitcoind = version match { val bitcoind = version match {
case BitcoindVersion.V25 => BitcoindV25RpcClient.withActorSystem(instance) case BitcoindVersion.V25 => BitcoindV25RpcClient.withActorSystem(instance)
case BitcoindVersion.V26 => BitcoindV26RpcClient.withActorSystem(instance) case BitcoindVersion.V26 => BitcoindV26RpcClient.withActorSystem(instance)
case BitcoindVersion.V27 => BitcoindV27RpcClient.withActorSystem(instance)
case BitcoindVersion.Unknown => case BitcoindVersion.Unknown =>
sys.error( sys.error(
s"Cannot create a Bitcoin Core RPC client: unsupported version" s"Cannot create a Bitcoin Core RPC client: unsupported version"
@ -368,10 +370,10 @@ object BitcoindVersion
with BitcoinSLogger { with BitcoinSLogger {
/** The newest version of `bitcoind` we support */ /** The newest version of `bitcoind` we support */
val newest: BitcoindVersion = V26 val newest: BitcoindVersion = V27
val standard: Vector[BitcoindVersion] = val standard: Vector[BitcoindVersion] =
Vector(V25) Vector(V27, V26, V25)
val known: Vector[BitcoindVersion] = standard val known: Vector[BitcoindVersion] = standard
@ -383,6 +385,10 @@ object BitcoindVersion
override def toString: String = "v26" override def toString: String = "v26"
} }
case object V27 extends BitcoindVersion {
override def toString: String = "v27"
}
case object Unknown extends BitcoindVersion { case object Unknown extends BitcoindVersion {
override def toString: String = "Unknown" override def toString: String = "Unknown"
} }
@ -402,6 +408,8 @@ object BitcoindVersion
// need to translate the int 210100 (as an example) to a BitcoindVersion // need to translate the int 210100 (as an example) to a BitcoindVersion
int.toString.substring(0, 2) match { int.toString.substring(0, 2) match {
case "25" => V25 case "25" => V25
case "26" => V26
case "27" => V27
case _ => case _ =>
logger.warn( logger.warn(
s"Unsupported Bitcoin Core version: $int. The latest supported version is ${BitcoindVersion.newest}" s"Unsupported Bitcoin Core version: $int. The latest supported version is ${BitcoindVersion.newest}"

View file

@ -4,7 +4,6 @@ import org.bitcoins.commons.jsonmodels.bitcoind._
import org.bitcoins.commons.serializers.JsonSerializers._ import org.bitcoins.commons.serializers.JsonSerializers._
import org.bitcoins.core.protocol.BitcoinAddress import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.script.ScriptPubKey import org.bitcoins.core.protocol.script.ScriptPubKey
import org.bitcoins.rpc.client.common.BitcoindVersion._
import play.api.libs.json.{JsString, Json} import play.api.libs.json.{JsString, Json}
import scala.concurrent.Future import scala.concurrent.Future
@ -24,27 +23,20 @@ trait UtilRpc { self: Client =>
} }
def decodeScript(script: ScriptPubKey): Future[DecodeScriptResult] = { def decodeScript(script: ScriptPubKey): Future[DecodeScriptResult] = {
self.version.flatMap { case V25 | V26 | Unknown => bitcoindCall[DecodeScriptResultV22](
bitcoindCall[DecodeScriptResultV22]( "decodescript",
"decodescript", List(Json.toJson(script))
List(Json.toJson(script)) )
)
}
} }
def getIndexInfo: Future[Map[String, IndexInfoResult]] = { def getIndexInfo: Future[Map[String, IndexInfoResult]] = {
version.flatMap { case V25 | V26 | Unknown => bitcoindCall[Map[String, IndexInfoResult]]("getindexinfo")
bitcoindCall[Map[String, IndexInfoResult]]("getindexinfo")
}
} }
def getIndexInfo(indexName: String): Future[IndexInfoResult] = { def getIndexInfo(indexName: String): Future[IndexInfoResult] = {
version.flatMap { case V25 | V26 | Unknown => bitcoindCall[Map[String, IndexInfoResult]](
bitcoindCall[Map[String, IndexInfoResult]]( "getindexinfo",
"getindexinfo", List(JsString(indexName))
List(JsString(indexName)) ).map(_.head._2)
).map(_.head._2)
}
} }
} }

View file

@ -14,7 +14,6 @@ import org.bitcoins.core.protocol.blockchain.MerkleBlock
import org.bitcoins.core.protocol.transaction.{Transaction, TransactionInput} import org.bitcoins.core.protocol.transaction.{Transaction, TransactionInput}
import org.bitcoins.core.psbt.PSBT import org.bitcoins.core.psbt.PSBT
import org.bitcoins.crypto._ import org.bitcoins.crypto._
import org.bitcoins.rpc.client.common.BitcoindVersion._
import play.api.libs.json._ import play.api.libs.json._
import scala.concurrent.Future import scala.concurrent.Future
@ -160,14 +159,10 @@ trait WalletRpc { self: Client =>
def getWalletInfo( def getWalletInfo(
walletName: String walletName: String
): Future[GetWalletInfoResult] = { ): Future[GetWalletInfoResult] = {
self.version.flatMap { bitcoindCall[GetWalletInfoResultPostV22](
case BitcoindVersion.V25 | BitcoindVersion.V26 | "getwalletinfo",
BitcoindVersion.Unknown => uriExtensionOpt = Some(walletExtension(walletName))
bitcoindCall[GetWalletInfoResultPostV22]( )
"getwalletinfo",
uriExtensionOpt = Some(walletExtension(walletName))
)
}
} }
def getWalletInfo: Future[GetWalletInfoResult] = { def getWalletInfo: Future[GetWalletInfoResult] = {
@ -381,32 +376,19 @@ trait WalletRpc { self: Client =>
passphrase: String = "", passphrase: String = "",
avoidReuse: Boolean = false, avoidReuse: Boolean = false,
descriptors: Boolean = true descriptors: Boolean = true
): Future[CreateWalletResult] = ): Future[CreateWalletResult] = {
self.version.flatMap { bitcoindCall[CreateWalletResult](
case V25 | V26 => "createwallet",
bitcoindCall[CreateWalletResult]( List(
"createwallet", JsString(walletName),
List( JsBoolean(disablePrivateKeys),
JsString(walletName), JsBoolean(blank),
JsBoolean(disablePrivateKeys), if (passphrase.isEmpty) JsNull else JsString(passphrase),
JsBoolean(blank), JsBoolean(avoidReuse),
if (passphrase.isEmpty) JsNull else JsString(passphrase), JsBoolean(descriptors)
JsBoolean(avoidReuse), )
JsBoolean(descriptors) )
) }
)
case Unknown =>
bitcoindCall[CreateWalletResult](
"createwallet",
List(
JsString(walletName),
JsBoolean(disablePrivateKeys),
JsBoolean(blank),
JsString(passphrase),
JsBoolean(avoidReuse)
)
)
}
def getAddressInfo( def getAddressInfo(
address: BitcoinAddress, address: BitcoinAddress,

View file

@ -0,0 +1,48 @@
package org.bitcoins.rpc.client.v27
import org.apache.pekko.actor.ActorSystem
import org.bitcoins.rpc.client.common.{BitcoindRpcClient, BitcoindVersion}
import org.bitcoins.rpc.config.BitcoindInstance
import scala.concurrent.Future
import scala.util.Try
class BitcoindV27RpcClient(override val instance: BitcoindInstance)(implicit
actorSystem: ActorSystem
) extends BitcoindRpcClient(instance) {
override lazy val version: Future[BitcoindVersion] =
Future.successful(BitcoindVersion.V27)
}
object BitcoindV27RpcClient {
/** 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): BitcoindV27RpcClient = {
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
): BitcoindV27RpcClient =
new BitcoindV27RpcClient(instance)(system)
def fromUnknownVersion(
rpcClient: BitcoindRpcClient
): Try[BitcoindV27RpcClient] =
Try {
new BitcoindV27RpcClient(rpcClient.instance)(rpcClient.system)
}
}

View file

@ -57,17 +57,16 @@ sealed trait BitcoindInstanceLocal extends BitcoindInstance {
Seq(binaryPath, "--version").!!.split(Properties.lineSeparator).head Seq(binaryPath, "--version").!!.split(Properties.lineSeparator).head
.split(" ") .split(" ")
.last .last
BitcoindVersion.known
foundVersion match { .find(v => foundVersion.startsWith(v.toString))
case _: String .getOrElse {
if foundVersion.startsWith(BitcoindVersion.V25.toString) => println(
BitcoindVersion.V25 s"Unsupported Bitcoin Core version: $foundVersion. The latest supported version is ${BitcoindVersion.newest}")
case _: String =>
logger.warn( logger.warn(
s"Unsupported Bitcoin Core version: $foundVersion. The latest supported version is ${BitcoindVersion.newest}" s"Unsupported Bitcoin Core version: $foundVersion. The latest supported version is ${BitcoindVersion.newest}"
) )
BitcoindVersion.newest BitcoindVersion.newest
} }
} }
versionT match { versionT match {

View file

@ -38,7 +38,7 @@ trait LndFixture extends BitcoinSFixture with CachedBitcoindNewest {
} }
/** A trait that is useful if you need Lnd fixtures for your test suite */ /** A trait that is useful if you need Lnd fixtures for your test suite */
trait DualLndFixture extends BitcoinSFixture with CachedBitcoindNewest { trait DualLndFixture extends BitcoinSFixture with CachedBitcoindV26 {
override type FixtureParam = (BitcoindRpcClient, LndRpcClient, LndRpcClient) override type FixtureParam = (BitcoindRpcClient, LndRpcClient, LndRpcClient)

View file

@ -124,6 +124,15 @@ trait BitcoindFixturesCachedPair[T <: BitcoindRpcClient]
} else { } else {
Future.unit Future.unit
} }
isNodeAdded <- BitcoindRpcTestUtil.isNodeAdded(bitcoinds)
_ <-
if (isNodeAdded) {
val node1 = bitcoinds.node1
val node2Uri = bitcoinds.node2.getDaemon.uri
node1.addNode(node2Uri, AddNodeArgument.Remove)
} else {
Future.unit
}
} yield { } yield {
nodePair nodePair
} }

View file

@ -32,6 +32,7 @@ import org.bitcoins.rpc.client.common.BitcoindVersion._
import org.bitcoins.rpc.client.common.{BitcoindRpcClient, BitcoindVersion} import org.bitcoins.rpc.client.common.{BitcoindRpcClient, BitcoindVersion}
import org.bitcoins.rpc.client.v25.BitcoindV25RpcClient import org.bitcoins.rpc.client.v25.BitcoindV25RpcClient
import org.bitcoins.rpc.client.v26.BitcoindV26RpcClient import org.bitcoins.rpc.client.v26.BitcoindV26RpcClient
import org.bitcoins.rpc.client.v27.BitcoindV27RpcClient
import org.bitcoins.rpc.config._ import org.bitcoins.rpc.config._
import org.bitcoins.rpc.util.{NodePair, RpcUtil} import org.bitcoins.rpc.util.{NodePair, RpcUtil}
import org.bitcoins.testkit.util.{BitcoindRpcTestClient, FileUtil, TorUtil} import org.bitcoins.testkit.util.{BitcoindRpcTestClient, FileUtil, TorUtil}
@ -103,7 +104,6 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
|regtest=1 |regtest=1
|server=1 |server=1
|daemon=$isDaemon |daemon=$isDaemon
|v2transport=1
|[regtest] |[regtest]
|rpcuser=$username |rpcuser=$username
|rpcpassword=$pass |rpcpassword=$pass
@ -178,7 +178,7 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
version match { version match {
// default to newest version // default to newest version
case Unknown => getBinary(BitcoindVersion.newest, binaryDirectory) case Unknown => getBinary(BitcoindVersion.newest, binaryDirectory)
case known @ (V25 | V26) => case known @ (V25 | V26 | V27) =>
val fileList = Files val fileList = Files
.list(binaryDirectory) .list(binaryDirectory)
.iterator() .iterator()
@ -274,6 +274,22 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
binaryDirectory = binaryDirectory binaryDirectory = binaryDirectory
) )
def v27Instance(
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.V27),
binaryDirectory = binaryDirectory
)
/** Gets an instance of bitcoind with the given version */ /** Gets an instance of bitcoind with the given version */
def getInstance( def getInstance(
bitcoindVersion: BitcoindVersion, bitcoindVersion: BitcoindVersion,
@ -300,6 +316,14 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
pruneMode, pruneMode,
binaryDirectory = binaryDirectory binaryDirectory = binaryDirectory
) )
case BitcoindVersion.V27 =>
BitcoindRpcTestUtil.v27Instance(
port,
rpcPort,
zmqConfig,
pruneMode,
binaryDirectory = binaryDirectory
)
case BitcoindVersion.Unknown => case BitcoindVersion.Unknown =>
sys.error( sys.error(
s"Could not create a bitcoind version with version=${BitcoindVersion.Unknown}" s"Could not create a bitcoind version with version=${BitcoindVersion.Unknown}"
@ -642,6 +666,9 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
case BitcoindVersion.V26 => case BitcoindVersion.V26 =>
BitcoindV26RpcClient.withActorSystem( BitcoindV26RpcClient.withActorSystem(
BitcoindRpcTestUtil.v26Instance()) BitcoindRpcTestUtil.v26Instance())
case BitcoindVersion.V27 =>
BitcoindV27RpcClient.withActorSystem(
BitcoindRpcTestUtil.v27Instance())
} }
// this is safe as long as this method is never // this is safe as long as this method is never
@ -728,14 +755,18 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
connectNodes(pair.node1, pair.node2) connectNodes(pair.node1, pair.node2)
} }
def disconnectNodes( def disconnectNodes(first: BitcoindRpcClient, second: BitcoindRpcClient)(
first: BitcoindRpcClient, implicit system: ActorSystem): Future[Unit] = {
second: BitcoindRpcClient): Future[Unit] = { import system.dispatcher
first.disconnectNode(second.getDaemon.uri) val disconnectF = first.disconnectNode(second.getDaemon.uri)
for {
_ <- disconnectF
_ <- awaitDisconnected(first, second)
} yield ()
} }
def disconnectNodes[T <: BitcoindRpcClient]( def disconnectNodes[T <: BitcoindRpcClient](nodePair: NodePair[T])(implicit
nodePair: NodePair[T]): Future[Unit] = { system: ActorSystem): Future[Unit] = {
disconnectNodes(nodePair.node1, nodePair.node2) disconnectNodes(nodePair.node1, nodePair.node2)
} }

View file

@ -94,6 +94,17 @@ trait CachedBitcoindFunded[T <: BitcoindRpcClient] extends CachedBitcoind[T] {
} }
} }
trait CachedBitcoindV26 extends CachedBitcoindFunded[BitcoindRpcClient] {
_: BitcoinSPekkoAsyncTest =>
override protected lazy val cachedBitcoindWithFundsF
: Future[BitcoindRpcClient] = {
val _ = isBitcoindUsed.set(true)
BitcoinSFixture
.createBitcoindWithFunds(Some(BitcoindVersion.V26))
}
}
trait CachedBitcoindNewest extends CachedBitcoindFunded[BitcoindRpcClient] { trait CachedBitcoindNewest extends CachedBitcoindFunded[BitcoindRpcClient] {
_: BitcoinSPekkoAsyncTest => _: BitcoinSPekkoAsyncTest =>