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,
to = otherFreshClient,
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
} yield {
assert(info.exists(_.transport_protocol_type == "v2"))

View file

@ -23,7 +23,7 @@ TaskKeys.downloadBitcoind := {
}
val versions =
List("26.1", "25.2")
List("27.0", "26.1", "25.2")
logger.debug(
s"(Maybe) downloading Bitcoin Core binaries for versions: ${versions.mkString(",")}")
@ -95,7 +95,8 @@ TaskKeys.downloadBitcoind := {
if (Properties.isLinux)
Map(
"25.2" -> "8d8c387e597e0edfc256f0bbace1dac3ad1ebf4a3c06da3e2975fda333817dea",
"26.1" -> "a5b7d206384a8100058d3f2e2f02123a8e49e83f523499e70e86e121a4897d5b"
"26.1" -> "a5b7d206384a8100058d3f2e2f02123a8e49e83f523499e70e86e121a4897d5b",
"27.0" -> "2a6974c5486f528793c79d42694b5987401e4a43c97f62b1383abf35bcee44a8"
)
else if (Properties.isMac)
Map(
@ -105,13 +106,18 @@ TaskKeys.downloadBitcoind := {
"e06ba379f6039ca99bc32d3e7974d420a31363498936f88aac7bab6f239de0f5"),
"26.1" -> (if (System.getProperty("os.arch") == "aarch64")
"8a8e415763b7ffd5988153cf03967d812eca629016dd3b0ddf6da3ab6f4a3621"
else
""),
"27.0" -> (if (System.getProperty("os.arch") == "aarch64")
"1d9d9b837297a73fc7a3b1cfed376644e3fa25c4e1672fbc143d5946cb52431d"
else
"")
)
else if (Properties.isWin)
Map(
"25.2" -> "c2ac84f55ee879caefd4414868d318a741c52a7286da190bf7233d86a2ffca69",
"26.1" -> "7bd0849e47472aeff99a0ea2c0cefd98f5be829e5a2d3b0168b5a54456cc638a"
"26.1" -> "7bd0849e47472aeff99a0ea2c0cefd98f5be829e5a2d3b0168b5a54456cc638a",
"27.0" -> "ca75babeaa3fb75f5a166f544adaa93fd7c1f06cf20d4e2c8c2a8b010f4c7603"
)
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.v25.BitcoindV25RpcClient
import org.bitcoins.rpc.client.v26.BitcoindV26RpcClient
import org.bitcoins.rpc.client.v27.BitcoindV27RpcClient
import org.bitcoins.rpc.config._
import java.io.File
@ -345,6 +346,7 @@ object BitcoindRpcClient {
val bitcoind = version match {
case BitcoindVersion.V25 => BitcoindV25RpcClient.withActorSystem(instance)
case BitcoindVersion.V26 => BitcoindV26RpcClient.withActorSystem(instance)
case BitcoindVersion.V27 => BitcoindV27RpcClient.withActorSystem(instance)
case BitcoindVersion.Unknown =>
sys.error(
s"Cannot create a Bitcoin Core RPC client: unsupported version"
@ -368,10 +370,10 @@ object BitcoindVersion
with BitcoinSLogger {
/** The newest version of `bitcoind` we support */
val newest: BitcoindVersion = V26
val newest: BitcoindVersion = V27
val standard: Vector[BitcoindVersion] =
Vector(V25)
Vector(V27, V26, V25)
val known: Vector[BitcoindVersion] = standard
@ -383,6 +385,10 @@ object BitcoindVersion
override def toString: String = "v26"
}
case object V27 extends BitcoindVersion {
override def toString: String = "v27"
}
case object Unknown extends BitcoindVersion {
override def toString: String = "Unknown"
}
@ -402,6 +408,8 @@ object BitcoindVersion
// need to translate the int 210100 (as an example) to a BitcoindVersion
int.toString.substring(0, 2) match {
case "25" => V25
case "26" => V26
case "27" => V27
case _ =>
logger.warn(
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.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.script.ScriptPubKey
import org.bitcoins.rpc.client.common.BitcoindVersion._
import play.api.libs.json.{JsString, Json}
import scala.concurrent.Future
@ -24,27 +23,20 @@ trait UtilRpc { self: Client =>
}
def decodeScript(script: ScriptPubKey): Future[DecodeScriptResult] = {
self.version.flatMap { case V25 | V26 | Unknown =>
bitcoindCall[DecodeScriptResultV22](
"decodescript",
List(Json.toJson(script))
)
}
bitcoindCall[DecodeScriptResultV22](
"decodescript",
List(Json.toJson(script))
)
}
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] = {
version.flatMap { case V25 | V26 | Unknown =>
bitcoindCall[Map[String, IndexInfoResult]](
"getindexinfo",
List(JsString(indexName))
).map(_.head._2)
}
bitcoindCall[Map[String, IndexInfoResult]](
"getindexinfo",
List(JsString(indexName))
).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.psbt.PSBT
import org.bitcoins.crypto._
import org.bitcoins.rpc.client.common.BitcoindVersion._
import play.api.libs.json._
import scala.concurrent.Future
@ -160,14 +159,10 @@ trait WalletRpc { self: Client =>
def getWalletInfo(
walletName: String
): Future[GetWalletInfoResult] = {
self.version.flatMap {
case BitcoindVersion.V25 | BitcoindVersion.V26 |
BitcoindVersion.Unknown =>
bitcoindCall[GetWalletInfoResultPostV22](
"getwalletinfo",
uriExtensionOpt = Some(walletExtension(walletName))
)
}
bitcoindCall[GetWalletInfoResultPostV22](
"getwalletinfo",
uriExtensionOpt = Some(walletExtension(walletName))
)
}
def getWalletInfo: Future[GetWalletInfoResult] = {
@ -381,32 +376,19 @@ trait WalletRpc { self: Client =>
passphrase: String = "",
avoidReuse: Boolean = false,
descriptors: Boolean = true
): Future[CreateWalletResult] =
self.version.flatMap {
case V25 | V26 =>
bitcoindCall[CreateWalletResult](
"createwallet",
List(
JsString(walletName),
JsBoolean(disablePrivateKeys),
JsBoolean(blank),
if (passphrase.isEmpty) JsNull else JsString(passphrase),
JsBoolean(avoidReuse),
JsBoolean(descriptors)
)
)
case Unknown =>
bitcoindCall[CreateWalletResult](
"createwallet",
List(
JsString(walletName),
JsBoolean(disablePrivateKeys),
JsBoolean(blank),
JsString(passphrase),
JsBoolean(avoidReuse)
)
)
}
): Future[CreateWalletResult] = {
bitcoindCall[CreateWalletResult](
"createwallet",
List(
JsString(walletName),
JsBoolean(disablePrivateKeys),
JsBoolean(blank),
if (passphrase.isEmpty) JsNull else JsString(passphrase),
JsBoolean(avoidReuse),
JsBoolean(descriptors)
)
)
}
def getAddressInfo(
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
.split(" ")
.last
foundVersion match {
case _: String
if foundVersion.startsWith(BitcoindVersion.V25.toString) =>
BitcoindVersion.V25
case _: String =>
BitcoindVersion.known
.find(v => foundVersion.startsWith(v.toString))
.getOrElse {
println(
s"Unsupported Bitcoin Core version: $foundVersion. The latest supported version is ${BitcoindVersion.newest}")
logger.warn(
s"Unsupported Bitcoin Core version: $foundVersion. The latest supported version is ${BitcoindVersion.newest}"
)
BitcoindVersion.newest
}
}
}
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 */
trait DualLndFixture extends BitcoinSFixture with CachedBitcoindNewest {
trait DualLndFixture extends BitcoinSFixture with CachedBitcoindV26 {
override type FixtureParam = (BitcoindRpcClient, LndRpcClient, LndRpcClient)

View file

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