2024 10 07 v28 bitcoind (#5696)

* Update to bitcoind v28

* Add macOS arm64 hash

* Add 28.0 version for windows

* Fix windows hash

* Replace 'port' with 'bind' in bitcoin.conf

* Fix BitcoindConfig.bind, add -deprecatedrpc=warnings

* Add version specific warnings handling for V28

* Add pattern matching based on version to support new json format for warnings fields in Info RPCs

* Fix pattern match on V28

* Remove infinite loop w/ 'getnetworkinfo' when determing the version of a BitcoindRpcClient

* Pin lndRpcTest to bitcoind v27 until v28 is supported

* Add mempoolconflicts to 'gettransaction' RPC

* init 'gethdkeys' rpc

* WIP: createwalletdescriptor

* Get createwalletdescriptor test passing

* Revert files
This commit is contained in:
Chris Stewart 2024-10-13 09:05:14 -05:00 committed by GitHub
parent 7dc58666b9
commit 38f0f4d692
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 463 additions and 97 deletions

View file

@ -115,7 +115,6 @@ sealed trait GetBlockChainInfoResult extends BlockchainResult {
def size_on_disk: Long
def pruned: Boolean
def pruneheight: Option[Int]
def warnings: String
}
// adds time field removes softforks field
@ -136,6 +135,23 @@ case class GetBlockChainInfoResultPostV23(
warnings: String
) extends GetBlockChainInfoResult
case class GetBlockChainInfoResultPostV27(
chain: NetworkParameters,
blocks: Int,
headers: Int,
bestblockhash: DoubleSha256DigestBE,
difficulty: BigDecimal,
time: Int,
mediantime: Int,
verificationprogress: BigDecimal,
initialblockdownload: Boolean,
chainwork: String, // How should this be handled?
size_on_disk: Long,
pruned: Boolean,
pruneheight: Option[Int],
warnings: Vector[String]
) extends GetBlockChainInfoResult
case class SoftforkPreV19(
id: String,
version: Int,

View file

@ -34,7 +34,7 @@ case class NetTarget(
time_left_in_cycle: UInt32
) extends NetworkResult
trait GetNetworkInfoResult extends NetworkResult {
sealed trait GetNetworkInfoResult extends NetworkResult {
def version: Int
def subversion: String
def protocolversion: Int
@ -44,14 +44,15 @@ trait GetNetworkInfoResult extends NetworkResult {
def timeoffset: Int
def networkactive: Boolean
def connections: Int
def connections_in: Int
def connections_out: Int
def networks: Vector[Network]
def relayfee: Bitcoins
def incrementalfee: Bitcoins
def localaddresses: Vector[NetworkAddress]
def warnings: String
}
case class GetNetworkInfoResultPreV21(
case class GetNetworkInfoResultV28(
version: Int,
subversion: String,
protocolversion: Int,
@ -61,11 +62,13 @@ case class GetNetworkInfoResultPreV21(
timeoffset: Int,
networkactive: Boolean,
connections: Int,
connections_in: Int,
connections_out: Int,
networks: Vector[Network],
relayfee: Bitcoins,
incrementalfee: Bitcoins,
localaddresses: Vector[NetworkAddress],
warnings: String
warnings: Vector[String]
) extends GetNetworkInfoResult
case class GetNetworkInfoResultPostV21(

View file

@ -54,7 +54,17 @@ case class BlockTransaction(
required: Option[Boolean]
) extends OtherResult
case class GetMiningInfoResult(
sealed abstract class GetMiningInfoResult extends OtherResult {
def blocks: Int
def currentblockweight: Option[Int]
def currentblocktx: Option[Int]
def difficulty: BigDecimal
def networkhashps: BigDecimal
def pooledtx: Int
def chain: String
}
case class GetMiningInfoResultPre28(
blocks: Int,
currentblockweight: Option[Int],
currentblocktx: Option[Int],
@ -62,8 +72,19 @@ case class GetMiningInfoResult(
networkhashps: BigDecimal,
pooledtx: Int,
chain: String,
warnings: String
) extends OtherResult
warnings: String)
extends GetMiningInfoResult
case class GetMiningInfoResultV28(
blocks: Int,
currentblockweight: Option[Int],
currentblocktx: Option[Int],
difficulty: BigDecimal,
networkhashps: BigDecimal,
pooledtx: Int,
chain: String,
warnings: Vector[String])
extends GetMiningInfoResult
case class GetMemoryInfoResult(locked: MemoryManager) extends OtherResult

View file

@ -1,5 +1,6 @@
package org.bitcoins.commons.jsonmodels.bitcoind
import org.bitcoins.commons.serializers.JsonWriters
import org.bitcoins.core.currency.Bitcoins
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.BitcoinAddress
@ -8,7 +9,8 @@ import org.bitcoins.core.protocol.transaction.{
TransactionInput,
TransactionOutPoint
}
import org.bitcoins.commons.serializers.JsonWriters._
import org.bitcoins.commons.serializers.JsonWriters.*
import org.bitcoins.core.crypto.ExtKey
import org.bitcoins.crypto.{
DoubleSha256DigestBE,
ECPrivateKeyBytes,
@ -267,4 +269,12 @@ object RpcOpts {
override val action: String = "abort"
}
}
case class CreateWalletDescriptorOptions(internal: Boolean, hdkey: ExtKey)
implicit val createWalletDescriptorOptionsWrites
: Writes[CreateWalletDescriptorOptions] = {
import JsonWriters.ExtKeyWrites
Json.writes[CreateWalletDescriptorOptions]
}
}

View file

@ -2,6 +2,7 @@ package org.bitcoins.commons.jsonmodels.bitcoind
import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts.LabelPurpose
import org.bitcoins.commons.rpc.BitcoindException
import org.bitcoins.core.crypto.{ExtPrivateKey, ExtPublicKey}
import org.bitcoins.core.currency.{Bitcoins, Satoshis}
import org.bitcoins.core.hd.BIP32Path
import org.bitcoins.core.number.UInt32
@ -42,7 +43,34 @@ case class BumpFeeResult(
errors: Vector[String]
) extends WalletResult
case class GetTransactionResult(
case class HDKeyDescriptor(desc: Descriptor, active: Boolean)
case class GetHDKeysResult(
xpub: ExtPublicKey,
has_private: Boolean,
xprv: Option[ExtPrivateKey],
descriptors: Vector[HDKeyDescriptor])
extends WalletResult
sealed trait GetTransactionResult extends WalletResult {
def amount: Bitcoins
def fee: Option[Bitcoins]
def confirmations: Int
def generated: Option[Boolean]
def blockhash: Option[DoubleSha256DigestBE]
def blockindex: Option[Int]
def blocktime: Option[UInt32]
def txid: DoubleSha256DigestBE
def walletconflicts: Vector[DoubleSha256DigestBE]
def time: UInt32
def timereceived: UInt32
def bip125_replaceable: Option[String]
def comment: Option[String]
def to: Option[String]
def details: Vector[TransactionDetails]
def hex: Transaction
}
case class GetTransactionResultPreV28(
amount: Bitcoins,
fee: Option[Bitcoins],
confirmations: Int,
@ -54,12 +82,32 @@ case class GetTransactionResult(
walletconflicts: Vector[DoubleSha256DigestBE],
time: UInt32,
timereceived: UInt32,
bip125_replaceable: String,
bip125_replaceable: Option[String],
comment: Option[String],
to: Option[String],
details: Vector[TransactionDetails],
hex: Transaction
) extends WalletResult
) extends GetTransactionResult
case class GetTransactionResultV28(
amount: Bitcoins,
fee: Option[Bitcoins],
confirmations: Int,
generated: Option[Boolean],
blockhash: Option[DoubleSha256DigestBE],
blockindex: Option[Int],
blocktime: Option[UInt32],
txid: DoubleSha256DigestBE,
walletconflicts: Vector[DoubleSha256DigestBE],
time: UInt32,
timereceived: UInt32,
bip125_replaceable: Option[String],
comment: Option[String],
to: Option[String],
details: Vector[TransactionDetails],
hex: Transaction,
mempoolconflicts: Vector[Transaction]
) extends GetTransactionResult
case class SetWalletFlagResult(
flag_name: String,
@ -395,3 +443,5 @@ case class ImportDescriptorResult(
) extends WalletResult
case class PrioritisedTransaction(fee_delta: Satoshis, in_mempool: Boolean)
case class CreateWalletDescriptorResult(descs: Vector[Descriptor])

View file

@ -1743,4 +1743,9 @@ object JsonReaders {
}
}
implicit val createWalletDescriptorReads
: Reads[CreateWalletDescriptorResult] = {
Json.reads[CreateWalletDescriptorResult]
}
}

View file

@ -190,12 +190,16 @@ object JsonSerializers {
implicit val networkAddressReads: Reads[NetworkAddress] =
Json.reads[NetworkAddress]
implicit val geNetworkInfoPreV21Reads: Reads[GetNetworkInfoResultPreV21] =
Json.reads[GetNetworkInfoResultPreV21]
implicit val getNetworkInfoV28Reads: Reads[GetNetworkInfoResultV28] =
Json.reads[GetNetworkInfoResultV28]
implicit val geNetworkInfoPostV21Reads: Reads[GetNetworkInfoResultPostV21] =
implicit val getNetworkInfoPostV21Reads: Reads[GetNetworkInfoResultPostV21] =
Json.reads[GetNetworkInfoResultPostV21]
implicit val getNetworkInfoReads: Reads[GetNetworkInfoResult] = {
Json.reads[GetNetworkInfoResult]
}
implicit val satsPerKbReads: Reads[SatoshisPerKiloByte] =
new Reads[SatoshisPerKiloByte] {
@ -298,6 +302,15 @@ object JsonSerializers {
: Reads[GetBlockChainInfoResultPostV23] =
Json.reads[GetBlockChainInfoResultPostV23]
implicit val getBlockChainInfoResultPost27Reads
: Reads[GetBlockChainInfoResultPostV27] = {
Json.reads[GetBlockChainInfoResultPostV27]
}
implicit val getBlockchainInfoResult: Reads[GetBlockChainInfoResult] = {
Json.reads[GetBlockChainInfoResult]
}
implicit val blockHeaderFormattedReads: Reads[GetBlockHeaderResult] =
Json.reads[GetBlockHeaderResult]
@ -393,23 +406,21 @@ object JsonSerializers {
implicit val TransactionDetailsReads: Reads[TransactionDetails] =
Json.reads[TransactionDetails]
implicit val getTransactionResultReads: Reads[GetTransactionResult] =
((__ \ "amount").read[Bitcoins] and
(__ \ "fee").readNullable[Bitcoins] and
(__ \ "confirmations").read[Int] and
(__ \ "generated").readNullable[Boolean] and
(__ \ "blockhash").readNullable[DoubleSha256DigestBE] and
(__ \ "blockindex").readNullable[Int] and
(__ \ "blocktime").readNullable[UInt32] and
(__ \ "txid").read[DoubleSha256DigestBE] and
(__ \ "walletconflicts").read[Vector[DoubleSha256DigestBE]] and
(__ \ "time").read[UInt32] and
(__ \ "timereceived").read[UInt32] and
(__ \ "bip125-replaceable").read[String] and
(__ \ "comment").readNullable[String] and
(__ \ "to").readNullable[String] and
(__ \ "details").read[Vector[TransactionDetails]] and
(__ \ "hex").read[Transaction])(GetTransactionResult.apply)
implicit val hdKeyDescriptor: Reads[HDKeyDescriptor] =
Json.reads[HDKeyDescriptor]
implicit val getHDKeysReads: Reads[GetHDKeysResult] = {
Json.reads[GetHDKeysResult]
}
implicit val getTranasctionResultPreV28: Reads[GetTransactionResultPreV28] = {
Json.reads[GetTransactionResultPreV28]
}
implicit val getTransactionResultV28: Reads[GetTransactionResultV28] = {
Json.reads[GetTransactionResultV28]
}
implicit val getTransactionResultReads: Reads[GetTransactionResult] = {
Json.reads[GetTransactionResult]
}
implicit val getWalletInfoResultReadsPostV22
: Reads[GetWalletInfoResultPostV22] =
@ -537,6 +548,12 @@ object JsonSerializers {
implicit val getBlockTemplateResultReads: Reads[GetBlockTemplateResult] =
Json.reads[GetBlockTemplateResult]
implicit val miningInfoResultPre28: Reads[GetMiningInfoResultPre28] = {
Json.reads[GetMiningInfoResultPre28]
}
implicit val miningInfoResultV28: Reads[GetMiningInfoResultV28] = {
Json.reads[GetMiningInfoResultV28]
}
implicit val miningInfoReads: Reads[GetMiningInfoResult] =
Json.reads[GetMiningInfoResult]

View file

@ -1,21 +1,22 @@
package org.bitcoins.commons.serializers
import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts._
import org.bitcoins.core.currency._
import org.bitcoins.core.number._
import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts.*
import org.bitcoins.core.crypto.{ExtKey, ExtPrivateKey, ExtPublicKey}
import org.bitcoins.core.currency.*
import org.bitcoins.core.number.*
import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.ln.LnInvoice
import org.bitcoins.core.protocol.ln.currency.MilliSatoshis
import org.bitcoins.core.protocol.script._
import org.bitcoins.core.protocol.script.*
import org.bitcoins.core.protocol.script.descriptor.Descriptor
import org.bitcoins.core.protocol.transaction._
import org.bitcoins.core.psbt._
import org.bitcoins.core.protocol.transaction.*
import org.bitcoins.core.psbt.*
import org.bitcoins.core.script.ScriptType
import org.bitcoins.core.serializers.PicklerKeys
import org.bitcoins.core.util.BytesUtil
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
import org.bitcoins.crypto._
import play.api.libs.json._
import org.bitcoins.crypto.*
import play.api.libs.json.*
import java.net.URL
import scala.collection.mutable
@ -119,6 +120,16 @@ object JsonWriters {
}
}
implicit object ExtKeyWrites extends Writes[ExtKey] {
override def writes(key: ExtKey): JsValue = {
val str = key match {
case xpub: ExtPublicKey => JsString(xpub.toString)
case xprv: ExtPrivateKey => JsString(xprv.toStringSensitive)
}
str
}
}
implicit object TransactionInputWrites extends Writes[TransactionInput] {
override def writes(o: TransactionInput): JsValue =

View file

@ -67,7 +67,7 @@ class BitcoindInstanceTest extends BitcoindRpcTest {
|rpcuser=foobar
|rpcpassword=barfoo
|[regtest]
|port=${RpcUtil.randomPort}
|bind=127.0.0.1:${RpcUtil.randomPort}
|rpcport=${RpcUtil.randomPort}
""".stripMargin
@ -98,7 +98,7 @@ class BitcoindInstanceTest extends BitcoindRpcTest {
|rpcauth=bitcoin-s:6d7580be1deb4ae52bc4249871845b09$$82b282e7c6493f6982a5a7af9fbb1b671bab702e2f31bbb1c016bb0ea1cc27ca
|regtest=1
|[regtest]
|port=${port}
|bind=127.0.0.1:${port}
|rpcport=${rpcPort}
""".stripMargin
@ -129,7 +129,7 @@ class BitcoindInstanceTest extends BitcoindRpcTest {
|rpcauth=bitcoin-s:6d7580be1deb4ae52bc4249871845b09$$82b282e7c6493f6982a5a7af9fbb1b671bab702e2f31bbb1c016bb0ea1cc27ca
|regtest=1
|[regtest]
|port=${port}
|bind=127.0.0.1:${port}
|rpcport=${rpcPort}
""".stripMargin

View file

@ -38,8 +38,8 @@ class BlockchainRpcTest extends BitcoindFixturesCachedPairNewest {
info <- client.getBlockChainInfo
bestHash <- client.getBestBlockHash()
} yield {
assert(info.isInstanceOf[GetBlockChainInfoResultPostV23])
val postV23 = info.asInstanceOf[GetBlockChainInfoResultPostV23]
assert(info.isInstanceOf[GetBlockChainInfoResultPostV27])
val postV23 = info.asInstanceOf[GetBlockChainInfoResultPostV27]
assert(postV23.chain == RegTest)
assert(postV23.bestblockhash == bestHash)
}

View file

@ -37,7 +37,7 @@ class MultiWalletRpcTest extends BitcoindFixturesCachedPairNewest {
new FutureOutcome(f)
}
private val cachedSetupClientsF: Future[NodePair[BitcoindRpcClient]] = {
private lazy val cachedSetupClientsF: Future[NodePair[BitcoindRpcClient]] = {
clientsF.flatMap(setupWalletClient)
}

View file

@ -12,13 +12,16 @@ import org.bitcoins.commons.jsonmodels.bitcoind.{
GetWalletInfoResultPostV22
}
import org.bitcoins.core.config.RegTest
import org.bitcoins.core.crypto.ExtPrivateKey
import org.bitcoins.core.currency.{Bitcoins, CurrencyUnit, Satoshis}
import org.bitcoins.core.hd.BIP32Path
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.script._
import org.bitcoins.core.protocol.script.descriptor.{
Descriptor,
P2SHDescriptor,
P2WPKHDescriptor
P2WPKHDescriptor,
XprvECPublicKeyExpression
}
import org.bitcoins.core.protocol.transaction._
import org.bitcoins.core.protocol.{
@ -49,6 +52,7 @@ import org.scalatest.{FutureOutcome, Outcome}
import java.io.File
import java.time.Instant
import java.util.concurrent.atomic.AtomicBoolean
import scala.concurrent.duration.DurationInt
import scala.concurrent.{Await, Future}
@ -64,6 +68,7 @@ class WalletRpcTest extends BitcoindFixturesCachedPairNewest {
new FutureOutcome(f)
}
private val isWalletClientUsed: AtomicBoolean = new AtomicBoolean(false)
// This client's wallet is encrypted
lazy val walletClientF: Future[BitcoindRpcClient] = clientsF.flatMap { _ =>
val instance =
@ -71,6 +76,7 @@ class WalletRpcTest extends BitcoindFixturesCachedPairNewest {
val walletClient =
BitcoindRpcClient(instance)
isWalletClientUsed.set(true)
for {
_ <- startClient(walletClient)
_ <- walletClient.generate(101)
@ -830,12 +836,54 @@ class WalletRpcTest extends BitcoindFixturesCachedPairNewest {
info <- client.getAddressInfo(addr)
} yield assert(info.address == addr)
}
it should "be able to createwalletdescriptor/gethdkeys" in { nodePair =>
val client = nodePair.node1
val walletName = "v28-createwalletdescriptor"
val path = BIP32Path.fromString("m/44'/0'/0'/0")
val xpriv = ExtPrivateKey.fromString(
"tprv8ZgxMBicQKsPds78rqEk8ATTSqn2fw1zXkwzcsUVattUrjDK8EEDJ3aGXanbUTwBKDmEKatJFNqy6XDt11Na18ZVwEtcxRuLVC5RCEkcNGP")
val keyExpression =
XprvECPublicKeyExpression(extKey = xpriv,
originOpt = None,
pathOpt = Some(path),
childrenHardenedOpt = Some(None))
val desc = P2WPKHDescriptor(keyExpression)
val importDesc = DescriptorsResult(desc = desc,
timestamp = Instant.now().getEpochSecond,
active = true,
internal = None,
range = Some(Vector(0, 5)),
next = None)
for {
_ <- client.createWallet(walletName = walletName, blank = true)
initHdKeys <- client.getHDKeys(walletName)
_ = assert(initHdKeys.isEmpty, s"initHdKeys=$initHdKeys")
importDescResult <- client.importDescriptor(importDesc,
walletName = walletName)
_ = assert(importDescResult.success, s"${importDescResult.error}")
result <- client.createWalletDescriptor(addressType = AddressType.Bech32m,
options = None,
walletName = walletName)
hdKeys <- client.getHDKeys(walletName = walletName)
} yield {
assert(result.descs.nonEmpty)
assert(hdKeys.nonEmpty)
}
}
def startClient(client: BitcoindRpcClient): Future[Unit] = {
BitcoindRpcTestUtil.startServers(Vector(client))
}
override def afterAll(): Unit = {
val stopF = walletClientF.map(BitcoindRpcTestUtil.stopServer)
val stopF = {
if (isWalletClientUsed.get) {
walletClientF.map(BitcoindRpcTestUtil.stopServer)
} else {
Future.unit
}
}
Await.result(stopF, 30.seconds)
super.afterAll()
}

View file

@ -1,3 +0,0 @@
package org.bitcoins.rpc.v25
class BitcoindV25RpcClientTest {}

View file

@ -23,7 +23,7 @@ TaskKeys.downloadBitcoind := {
}
val versions =
List("27.1", "26.1", "25.2")
List("28.0", "27.1", "26.1", "25.2")
logger.debug(
s"(Maybe) downloading Bitcoin Core binaries for versions: ${versions.mkString(",")}")
@ -96,7 +96,8 @@ TaskKeys.downloadBitcoind := {
Map(
"25.2" -> "8d8c387e597e0edfc256f0bbace1dac3ad1ebf4a3c06da3e2975fda333817dea",
"26.1" -> "a5b7d206384a8100058d3f2e2f02123a8e49e83f523499e70e86e121a4897d5b",
"27.1" -> "c9840607d230d65f6938b81deaec0b98fe9cb14c3a41a5b13b2c05d044a48422"
"27.1" -> "c9840607d230d65f6938b81deaec0b98fe9cb14c3a41a5b13b2c05d044a48422",
"28.0" -> "7fe294b02b25b51acb8e8e0a0eb5af6bbafa7cd0c5b0e5fcbb61263104a82fbc"
)
else if (Properties.isMac)
Map(
@ -111,13 +112,18 @@ TaskKeys.downloadBitcoind := {
"27.1" -> (if (System.getProperty("os.arch") == "aarch64")
"ad4a3fd484077224a82dd56d194efb6e614467f413ab1dfb8776da4d08a4c227"
else
"e1efd8c4605b2aabc876da93b6eee2bedd868ce7d1f02b0220c1001f903b3e2c")
"e1efd8c4605b2aabc876da93b6eee2bedd868ce7d1f02b0220c1001f903b3e2c"),
"28.0" -> (if (System.getProperty("os.arch") == "aarch64")
"c8108f30dfcc7ddffab33f5647d745414ef9d3298bfe67d243fe9b9cb4df4c12"
else
"e1efd8c4605b2aabc876da93b6eee2bedd868ce7d1f02b0220c1001f903b3e2c")
)
else if (Properties.isWin)
Map(
"25.2" -> "c2ac84f55ee879caefd4414868d318a741c52a7286da190bf7233d86a2ffca69",
"26.1" -> "7bd0849e47472aeff99a0ea2c0cefd98f5be829e5a2d3b0168b5a54456cc638a",
"27.1" -> "9719871a2c9a45c741e33d670d2319dcd3f8f52a6059e9c435a9a2841188b932"
"27.1" -> "9719871a2c9a45c741e33d670d2319dcd3f8f52a6059e9c435a9a2841188b932",
"28.0" -> "85282f4ec1bcb0cfe8db0f195e8e0f6fb77cfbe89242a81fff2bc2e9292f7acf"
)
else sys.error(s"Unsupported OS: ${Properties.osName}")

View file

@ -3,6 +3,10 @@ package org.bitcoins.rpc.client.common
import org.apache.pekko.Done
import org.apache.pekko.actor.ActorSystem
import org.apache.pekko.stream.scaladsl.{Keep, RunnableGraph, Sink, Source}
import org.bitcoins.commons.jsonmodels.bitcoind.{
GetNetworkInfoResultPostV21,
GetNetworkInfoResultV28
}
import org.bitcoins.commons.util.BitcoinSLogger
import org.bitcoins.core.api.chain.db.BlockHeaderDb
import org.bitcoins.core.api.chain.{ChainApi, FilterSyncMarker}
@ -21,6 +25,7 @@ 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.client.v28.BitcoindV28RpcClient
import org.bitcoins.rpc.config.*
import java.io.File
@ -62,11 +67,21 @@ class BitcoindRpcClient(override val instance: BitcoindInstance)(implicit
private val syncing = new AtomicBoolean(false)
override def version: Future[BitcoindVersion] = {
override lazy val version: Future[BitcoindVersion] = {
import org.bitcoins.commons.serializers.JsonSerializers.{
getNetworkInfoV28Reads,
getNetworkInfoPostV21Reads
}
instance match {
case _: BitcoindInstanceRemote =>
getNetworkInfo.map(info =>
BitcoindVersion.fromNetworkVersion(info.version))
// work around for version specific calls to 'getnetworkinfo'
// the return payload is slightly different pre28 and post 28
// this can be removed in the future when we drop support for v27 of bitcoind
bitcoindCall[GetNetworkInfoResultV28]("getnetworkinfo")
.recoverWith { _ =>
bitcoindCall[GetNetworkInfoResultPostV21]("getnetworkinfo")
}
.map(result => BitcoindVersion.fromNetworkVersion(result.version))
case local: BitcoindInstanceLocal =>
Future.successful(local.getVersion)
}
@ -341,6 +356,7 @@ object BitcoindRpcClient {
case BitcoindVersion.V25 => BitcoindV25RpcClient(instance)
case BitcoindVersion.V26 => BitcoindV26RpcClient(instance)
case BitcoindVersion.V27 => BitcoindV27RpcClient(instance)
case BitcoindVersion.V28 => BitcoindV28RpcClient(instance)
case BitcoindVersion.Unknown =>
sys.error(
s"Cannot create a Bitcoin Core RPC client: unsupported version"
@ -361,6 +377,7 @@ object BitcoindRpcClient {
case BitcoindVersion.V25 => new BitcoindV25RpcClient(instance)
case BitcoindVersion.V26 => new BitcoindV26RpcClient(instance)
case BitcoindVersion.V27 => new BitcoindV27RpcClient(instance)
case BitcoindVersion.V28 => new BitcoindV28RpcClient(instance)
case BitcoindVersion.Unknown =>
sys.error(
s"Cannot create a Bitcoin Core RPC client: unsupported version"
@ -377,10 +394,10 @@ object BitcoindVersion
with BitcoinSLogger {
/** The newest version of `bitcoind` we support */
val newest: BitcoindVersion = V27
val newest: BitcoindVersion = V28
val standard: Vector[BitcoindVersion] =
Vector(V27, V26, V25)
Vector(V28, V27, V26, V25)
val known: Vector[BitcoindVersion] = standard
@ -396,6 +413,10 @@ object BitcoindVersion
override def toString: String = "v27.1"
}
case object V28 extends BitcoindVersion {
override def toString: String = "v28.0"
}
case object Unknown extends BitcoindVersion {
override def toString: String = "Unknown"
}
@ -417,6 +438,7 @@ object BitcoindVersion
case "25" => V25
case "26" => V26
case "27" => V27
case "28" => V28
case _ =>
logger.warn(
s"Unsupported Bitcoin Core version: $int. The latest supported version is ${BitcoindVersion.newest}"

View file

@ -1,8 +1,8 @@
package org.bitcoins.rpc.client.common
import org.bitcoins.commons.jsonmodels.bitcoind._
import org.bitcoins.commons.jsonmodels.bitcoind.*
import org.bitcoins.commons.serializers.JsonSerializers
import org.bitcoins.commons.serializers.JsonSerializers._
import org.bitcoins.commons.serializers.JsonSerializers.*
import org.bitcoins.core.api.chain.ChainQueryApi.FilterResponse
import org.bitcoins.core.api.chain.db.{
CompactFilterDb,
@ -14,7 +14,14 @@ import org.bitcoins.core.gcs.{BlockFilter, FilterHeader, FilterType}
import org.bitcoins.core.protocol.blockchain.{Block, BlockHeader}
import org.bitcoins.core.util.FutureUtil
import org.bitcoins.crypto.{DoubleSha256Digest, DoubleSha256DigestBE}
import play.api.libs.json._
import org.bitcoins.rpc.client.common.BitcoindVersion.{
Unknown,
V25,
V26,
V27,
V28
}
import play.api.libs.json.*
import scala.concurrent.Future
@ -39,7 +46,13 @@ trait BlockchainRpc extends ChainApi { self: Client =>
}
def getBlockChainInfo: Future[GetBlockChainInfoResult] = {
bitcoindCall[GetBlockChainInfoResultPostV23]("getblockchaininfo")
self.version.flatMap {
case V25 | V26 | V27 | Unknown =>
bitcoindCall[GetBlockChainInfoResultPostV23]("getblockchaininfo")
case V28 =>
bitcoindCall[GetBlockChainInfoResultPostV27]("getblockchaininfo")
}
}
override def getBlockCount(): Future[Int] = {

View file

@ -4,15 +4,24 @@ import org.bitcoins.commons.jsonmodels.bitcoind.{
GenerateBlockResult,
GetBlockTemplateResult,
GetMiningInfoResult,
GetMiningInfoResultPre28,
GetMiningInfoResultV28,
PrioritisedTransaction,
RpcOpts
}
import org.bitcoins.commons.serializers.JsonReaders._
import org.bitcoins.commons.serializers.JsonSerializers._
import org.bitcoins.commons.serializers.JsonReaders.*
import org.bitcoins.commons.serializers.JsonSerializers.*
import org.bitcoins.core.currency.Satoshis
import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.transaction.Transaction
import org.bitcoins.crypto.{DoubleSha256Digest, DoubleSha256DigestBE}
import org.bitcoins.rpc.client.common.BitcoindVersion.{
Unknown,
V25,
V26,
V27,
V28
}
import play.api.libs.json.{JsArray, JsNumber, JsString, Json}
import scala.concurrent.Future
@ -75,7 +84,12 @@ trait MiningRpc { self: Client with BlockchainRpc =>
}
def getMiningInfo: Future[GetMiningInfoResult] = {
bitcoindCall[GetMiningInfoResult]("getmininginfo")
self.version.flatMap {
case V25 | V26 | V27 | Unknown =>
bitcoindCall[GetMiningInfoResultPre28]("getmininginfo")
case V28 =>
bitcoindCall[GetMiningInfoResultV28]("getmininginfo")
}
}
def prioritiseTransaction(

View file

@ -4,10 +4,17 @@ import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts.{
AddNodeArgument,
SetBanCommand
}
import org.bitcoins.commons.jsonmodels.bitcoind._
import org.bitcoins.commons.jsonmodels.bitcoind.*
import org.bitcoins.commons.serializers.JsonSerializers
import org.bitcoins.commons.serializers.JsonSerializers._
import org.bitcoins.commons.serializers.JsonSerializers.*
import org.bitcoins.core.protocol.blockchain.Block
import org.bitcoins.rpc.client.common.BitcoindVersion.{
Unknown,
V25,
V26,
V27,
V28
}
import play.api.libs.json.{JsBoolean, JsNumber, JsString}
import java.net.URI
@ -62,10 +69,12 @@ trait P2PRpc { self: Client =>
}
def getNetworkInfo: Future[GetNetworkInfoResult] = {
bitcoindCall[GetNetworkInfoResultPostV21]("getnetworkinfo")
.recoverWith { case _ =>
bitcoindCall[GetNetworkInfoResultPreV21]("getnetworkinfo")
}
self.version.flatMap {
case V25 | V26 | V27 | Unknown =>
bitcoindCall[GetNetworkInfoResultPostV21]("getnetworkinfo")
case V28 =>
bitcoindCall[GetNetworkInfoResultV28]("getnetworkinfo")
}
}
def getPeerInfo: Future[Vector[PeerInfoResponseV25]] = {

View file

@ -1,12 +1,13 @@
package org.bitcoins.rpc.client.common
import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts.FeeEstimationMode
import org.bitcoins.commons.jsonmodels.bitcoind._
import org.bitcoins.commons.serializers.JsonSerializers._
import org.bitcoins.commons.jsonmodels.bitcoind.*
import org.bitcoins.commons.serializers.JsonSerializers.*
import org.bitcoins.core.currency.Satoshis
import org.bitcoins.core.protocol.blockchain.MerkleBlock
import org.bitcoins.crypto.{DoubleSha256Digest, DoubleSha256DigestBE}
import play.api.libs.json._
import org.bitcoins.rpc.client.common.BitcoindVersion.{V25, V26, V27, V28}
import play.api.libs.json.*
import scala.concurrent.Future
@ -77,11 +78,20 @@ trait TransactionRpc { self: Client =>
watchOnly: Boolean = false,
walletName: String = BitcoindRpcClient.DEFAULT_WALLET_NAME
): Future[GetTransactionResult] = {
bitcoindCall[GetTransactionResult](
"gettransaction",
List(JsString(txid.hex), JsBoolean(watchOnly)),
uriExtensionOpt = Some(walletExtension(walletName))
)
self.version.flatMap {
case V25 | V26 | V27 | BitcoindVersion.Unknown =>
bitcoindCall[GetTransactionResultPreV28](
"gettransaction",
List(JsString(txid.hex), JsBoolean(watchOnly)),
uriExtensionOpt = Some(walletExtension(walletName))
)
case V28 =>
bitcoindCall[GetTransactionResultV28](
"gettransaction",
List(JsString(txid.hex), JsBoolean(watchOnly)),
uriExtensionOpt = Some(walletExtension(walletName))
)
}
}
def getTxOut(

View file

@ -2,19 +2,21 @@ package org.bitcoins.rpc.client.common
import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts.{
AddressType,
CreateWalletDescriptorOptions,
WalletCreateFundedPsbtOptions,
WalletFlag
}
import org.bitcoins.commons.jsonmodels.bitcoind._
import org.bitcoins.commons.serializers.JsonSerializers._
import org.bitcoins.commons.serializers.JsonWriters._
import org.bitcoins.commons.jsonmodels.bitcoind.*
import org.bitcoins.commons.serializers.JsonReaders
import org.bitcoins.commons.serializers.JsonSerializers.*
import org.bitcoins.commons.serializers.JsonWriters.*
import org.bitcoins.core.currency.{Bitcoins, CurrencyUnit}
import org.bitcoins.core.protocol.BitcoinAddress
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 play.api.libs.json._
import org.bitcoins.crypto.*
import play.api.libs.json.*
import scala.concurrent.Future
@ -55,6 +57,13 @@ trait WalletRpc { self: Client =>
)
}
def getHDKeys(
walletName: String = DEFAULT_WALLET): Future[Vector[GetHDKeysResult]] = {
bitcoindCall[Vector[GetHDKeysResult]]("gethdkeys",
uriExtensionOpt =
Some(walletExtension(walletName)))
}
def getReceivedByAddress(
address: BitcoinAddress,
minConfirmations: Int = 1,
@ -390,6 +399,18 @@ trait WalletRpc { self: Client =>
)
}
def createWalletDescriptor(
addressType: AddressType,
options: Option[CreateWalletDescriptorOptions] = None,
walletName: String = DEFAULT_WALLET)
: Future[CreateWalletDescriptorResult] = {
import JsonReaders.createWalletDescriptorReads
bitcoindCall[CreateWalletDescriptorResult](
"createwalletdescriptor",
List(Json.toJson(addressType), Json.toJson(options)),
uriExtensionOpt = Some(walletExtension(walletName)))
}
def getAddressInfo(
address: BitcoinAddress,
walletName: String = DEFAULT_WALLET

View file

@ -0,0 +1,33 @@
package org.bitcoins.rpc.client.v28
import org.apache.pekko.actor.ActorSystem
import org.bitcoins.rpc.client.common.{BitcoindRpcClient, BitcoindVersion}
import org.bitcoins.rpc.config.{
BitcoindInstance,
BitcoindInstanceLocal,
BitcoindRpcAppConfig
}
import scala.concurrent.Future
class BitcoindV28RpcClient(override val instance: BitcoindInstance)(implicit
actorSystem: ActorSystem,
bitcoindRpcAppConfig: BitcoindRpcAppConfig
) extends BitcoindRpcClient(instance) {
override lazy val version: Future[BitcoindVersion] =
Future.successful(BitcoindVersion.V28)
}
object BitcoindV28RpcClient {
/** 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 apply(instance: BitcoindInstanceLocal)(implicit
system: ActorSystem
): BitcoindV28RpcClient =
new BitcoindV28RpcClient(instance)(system, instance.bitcoindRpcAppConfig)
}

View file

@ -223,7 +223,7 @@ case class BitcoindConfig(
lazy val zmqpubhashtx: Option[InetSocketAddress] =
getValue("zmqpubhashtx").map(toInetSocketAddress)
lazy val port: Int = getValue("port").map(_.toInt).getOrElse(network.port)
lazy val portOpt: Option[Int] = getValue("port").map(_.toInt)
/** Defaults to localhost */
lazy val bind: URI = new URI({
@ -233,7 +233,7 @@ case class BitcoindConfig(
})
lazy val uri: URI = new URI(s"$bind:$port")
lazy val uri: URI = new URI(s"$bind")
lazy val rpcport: Int =
getValue("rpcport").map(_.toInt).getOrElse(network.rpcPort)

View file

@ -3,7 +3,7 @@ package org.bitcoins.core.protocol.script.descriptor
import org.bitcoins.core.config.NetworkParameters
import org.bitcoins.core.number.{UInt64, UInt8}
import org.bitcoins.core.protocol.Bech32Address
import org.bitcoins.core.protocol.script._
import org.bitcoins.core.protocol.script.*
import org.bitcoins.core.util.Bech32
import org.bitcoins.crypto.{ECPrivateKey, PublicKey, StringFactory}
@ -214,6 +214,13 @@ object P2WPKHDescriptor
val checksum = Descriptor.createChecksum(noChecksum)
P2WPKHDescriptor(p2wpkhExpression, Some(checksum))
}
def apply(keyExpression: ExtECPublicKeyExpression): P2WPKHDescriptor = {
val p2wpkhExpression = P2WPKHExpression(keyExpression)
val noChecksum = P2WPKHDescriptor(p2wpkhExpression, None)
val checksum = Descriptor.createChecksum(noChecksum)
P2WPKHDescriptor(p2wpkhExpression, Some(checksum))
}
}
object P2WSHDescriptor

View file

@ -10,7 +10,7 @@ import org.scalatest.FutureOutcome
import scala.io.Source
/** A trait that is useful if you need Lnd fixtures for your test suite */
trait LndFixture extends BitcoinSFixture with CachedBitcoindNewest {
trait LndFixture extends BitcoinSFixture with CachedBitcoindV27 {
override type FixtureParam = LndRpcClient
@ -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 CachedBitcoindV27 {
override type FixtureParam = (BitcoindRpcClient, LndRpcClient, LndRpcClient)
@ -66,7 +66,7 @@ trait DualLndFixture extends BitcoinSFixture with CachedBitcoindNewest {
}
}
trait RemoteLndFixture extends BitcoinSFixture with CachedBitcoindNewest {
trait RemoteLndFixture extends BitcoinSFixture with CachedBitcoindV27 {
override type FixtureParam = LndRpcClient

View file

@ -9,6 +9,7 @@ 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.{LndInstanceLocal, LndInstanceRemote}
import org.bitcoins.rpc.client.common.BitcoindVersion.{V25, V26, V27, V28}
import org.bitcoins.rpc.client.common.{BitcoindRpcClient, BitcoindVersion}
import org.bitcoins.rpc.config.{
BitcoindAuthCredentials,
@ -47,6 +48,18 @@ trait LndRpcTestUtil extends BitcoinSLogger {
BitcoindRpcTestUtil.startedBitcoindRpcClient(instanceOpt, Vector.newBuilder)
}
def startedBitcoindRpcClient(version: BitcoindVersion)(implicit
system: ActorSystem): Future[BitcoindRpcClient] = {
val instance = version match {
case V25 => BitcoindRpcTestUtil.v25Instance()
case V26 => BitcoindRpcTestUtil.v26Instance()
case V27 => BitcoindRpcTestUtil.v27Instance()
case V28 => BitcoindRpcTestUtil.v28Instance()
case BitcoindVersion.Unknown => BitcoindRpcTestUtil.instance()
}
startedBitcoindRpcClient(Some(instance))
}
/** Creates a bitcoind instance with the given parameters */
def bitcoindInstance(
port: Int = RpcUtil.randomPort,

View file

@ -32,7 +32,8 @@ 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.client.v28.BitcoindV28RpcClient
import org.bitcoins.rpc.config.*
import org.bitcoins.rpc.util.{NodePair, RpcUtil}
import org.bitcoins.testkit.util.{BitcoindRpcTestClient, FileUtil, TorUtil}
import org.bitcoins.util.ListUtil
@ -41,10 +42,10 @@ import java.io.File
import java.net.{InetSocketAddress, URI}
import java.nio.file.{Files, Path}
import scala.collection.mutable
import scala.concurrent._
import scala.concurrent.*
import scala.concurrent.duration.{DurationInt, FiniteDuration}
import scala.jdk.CollectionConverters.IteratorHasAsScala
import scala.util._
import scala.util.*
//noinspection AccessorLikeMethodIsEmptyParen
trait BitcoindRpcTestUtil extends BitcoinSLogger {
@ -108,7 +109,7 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
|rpcuser=$username
|rpcpassword=$pass
|rpcport=${rpcUri.getPort}
|port=${uri.getPort}
|bind=127.0.0.1:${uri.getPort}
|debug=$isDebug
|walletbroadcast=1
|mempoolfullrbf=1
@ -178,7 +179,7 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
version match {
// default to newest version
case Unknown => getBinary(BitcoindVersion.newest, binaryDirectory)
case known @ (V25 | V26 | V27) =>
case known @ (V25 | V26 | V27 | V28) =>
val fileList: List[(Path, String)] = Files
.list(binaryDirectory)
.iterator()
@ -301,6 +302,22 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
binaryDirectory = binaryDirectory
)
def v28Instance(
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.V28),
binaryDirectory = binaryDirectory
)
/** Gets an instance of bitcoind with the given version */
def getInstance(
bitcoindVersion: BitcoindVersion,
@ -335,6 +352,14 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
pruneMode,
binaryDirectory = binaryDirectory
)
case BitcoindVersion.V28 =>
BitcoindRpcTestUtil.v28Instance(
port,
rpcPort,
zmqConfig,
pruneMode,
binaryDirectory = binaryDirectory
)
case BitcoindVersion.Unknown =>
sys.error(
s"Could not create a bitcoind version with version=${BitcoindVersion.Unknown}"
@ -686,6 +711,9 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
case BitcoindVersion.V27 =>
val instance = BitcoindRpcTestUtil.v27Instance()
BitcoindV27RpcClient(instance)
case BitcoindVersion.V28 =>
val instance = BitcoindRpcTestUtil.v28Instance()
BitcoindV28RpcClient(instance)
}
// this is safe as long as this method is never

View file

@ -1,6 +1,7 @@
package org.bitcoins.testkit.rpc
import org.bitcoins.rpc.client.common.{BitcoindRpcClient, BitcoindVersion}
import org.bitcoins.rpc.client.v27.BitcoindV27RpcClient
import org.bitcoins.rpc.util.{NodePair, NodeTriple}
import org.bitcoins.testkit.fixtures.BitcoinSFixture
import org.bitcoins.testkit.util.BitcoinSPekkoAsyncTest
@ -105,6 +106,17 @@ trait CachedBitcoindV26 extends CachedBitcoindFunded[BitcoindRpcClient] {
}
}
trait CachedBitcoindV27 extends CachedBitcoindFunded[BitcoindV27RpcClient] {
_: BitcoinSPekkoAsyncTest =>
override protected lazy val cachedBitcoindWithFundsF
: Future[BitcoindRpcClient] = {
val _ = isBitcoindUsed.set(true)
BitcoinSFixture
.createBitcoindWithFunds(Some(BitcoindVersion.V27))
}
}
trait CachedBitcoindNewest extends CachedBitcoindFunded[BitcoindRpcClient] {
_: BitcoinSPekkoAsyncTest =>