Implement bitcoind RPC 26.x (#5550)

* Get build started for 26.1

* Start introducing v26 data structures

* Switch BitcoindVersion.newest = V26

* Fix GetTxOutSetInfoResult to use hash_serialized_3

* Fix WalletRpcTest

* Refactor PeerInfoResponse to add transport_protocol_type and session_id fields in v25

* Add ServiceIdentifier.NODE_P2P_V2, add support for v2transport to addnode, add default config setting for bitcoind to have v2transport=1 in test framework, add unit test to make sure we can establish a v2 connection

* Remove V20AssortedRpc

* Add loadtxoutset

* Implement getchainstates

* Add getprioritisedtransactions

* Add getaddrmaninfo

* Add importmempool

* Cleanup println
This commit is contained in:
Chris Stewart 2024-04-27 14:51:45 -05:00 committed by GitHub
parent 5dc5cca9cf
commit 7ef6086673
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 357 additions and 182 deletions

View file

@ -27,9 +27,18 @@ case class DumpTxOutSetResult(
coins_written: Int,
base_hash: DoubleSha256DigestBE,
base_height: Int,
path: Path
path: Path,
txoutset_hash: DoubleSha256DigestBE,
nchaintx: Long
) extends BlockchainResult
case class LoadTxOutSetResult(
coins_loaded: Long,
tip_hash: DoubleSha256DigestBE,
base_height: Long,
path: Path)
extends BlockchainResult
case class GetBlockResult(
hash: DoubleSha256DigestBE,
confirmations: Int,
@ -357,7 +366,7 @@ case class GetTxOutSetInfoResult(
transactions: Int,
txouts: Int,
bogosize: Int,
hash_serialized_2: DoubleSha256DigestBE,
hash_serialized_3: DoubleSha256DigestBE,
disk_size: Int,
total_amount: Bitcoins
) extends BlockchainResult
@ -420,3 +429,13 @@ case class ScanInProgress(progress: BigDecimal, current_height: Int)
extends StatusScanBlocksResult
case class ScanBlocksAbortResult(aborted: Boolean) extends ScanBlocksResult
case class ChainState(
blocks: Int,
bestblockhash: DoubleSha256DigestBE,
difficulty: BigDecimal,
verificationprogress: BigDecimal,
coins_db_cache_bytes: Long,
coins_tip_cache_bytes: Long,
validated: Boolean)
case class ChainStateResult(headers: Int, chainstates: Vector[ChainState])

View file

@ -98,12 +98,13 @@ case class Network(
case class NetworkAddress(address: String, port: Int, score: Int)
extends NetworkResult
sealed trait Peer extends NetworkResult {
sealed trait PeerInfoResponse extends NetworkResult {
def id: Int
def networkInfo: PeerNetworkInfo
def version: Int
def subver: String
def inbound: Boolean
def connection_type: String
def addnode: Boolean
def startingheight: Int
def synced_headers: Int
@ -114,25 +115,7 @@ sealed trait Peer extends NetworkResult {
def minfeefilter: Option[SatoshisPerKiloByte]
}
case class PeerPostV21(
id: Int,
networkInfo: PeerNetworkInfoPostV21,
version: Int,
subver: String,
inbound: Boolean,
connection_type: String,
startingheight: Int,
synced_headers: Int,
synced_blocks: Int,
inflight: Vector[Int],
bytessent_per_msg: Map[String, Int],
bytesrecv_per_msg: Map[String, Int],
minfeefilter: Option[SatoshisPerKiloByte]
) extends Peer {
override val addnode: Boolean = connection_type == "manual"
}
case class PeerV22(
case class PeerInfoResponseV25(
id: Int,
networkInfo: PeerNetworkInfoPostV21,
version: Int,
@ -148,8 +131,10 @@ case class PeerV22(
minfeefilter: Option[SatoshisPerKiloByte],
bip152_hb_to: Boolean,
bip152_hb_from: Boolean,
permissions: Vector[String]
) extends Peer {
permissions: Vector[String],
transport_protocol_type: String,
session_id: String
) extends PeerInfoResponse {
override val addnode: Boolean = connection_type == "manual"
}
@ -246,3 +231,12 @@ case class GetNodeAddressesResultPostV22(
port: Int,
network: String
) extends GetNodeAddressesResult
case class AddrManInfo(`new`: Int, tried: Int, total: Int)
case class GetAddrmanInfoResponse(
ipv4: AddrManInfo,
ipv6: AddrManInfo,
onion: AddrManInfo,
cjdns: AddrManInfo,
all_networks: AddrManInfo)
extends NetworkResult

View file

@ -1,7 +1,7 @@
package org.bitcoins.commons.jsonmodels.bitcoind
import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts.LabelPurpose
import org.bitcoins.core.currency.Bitcoins
import org.bitcoins.core.currency.{Bitcoins, Satoshis}
import org.bitcoins.core.hd.BIP32Path
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.BitcoinAddress
@ -391,3 +391,5 @@ case class ImportDescriptorResult(
success: Boolean,
warnings: Option[Vector[String]]
) extends WalletResult
case class PrioritisedTransaction(fee_delta: Satoshis, in_mempool: Boolean)

View file

@ -232,19 +232,25 @@ object JsonSerializers {
implicit val peerNetworkInfoPostV21Reads: Reads[PeerNetworkInfoPostV21] =
Json.reads[PeerNetworkInfoPostV21]
implicit val peerPostV21Reads: Reads[PeerPostV21] = ((__ \ "id").read[Int] and
__.read[PeerNetworkInfoPostV21] and
(__ \ "version").read[Int] and
(__ \ "subver").read[String] and
(__ \ "inbound").read[Boolean] and
(__ \ "connection_type").read[String] and
(__ \ "startingheight").read[Int] and
(__ \ "synced_headers").read[Int] and
(__ \ "synced_blocks").read[Int] and
(__ \ "inflight").read[Vector[Int]] and
(__ \ "bytessent_per_msg").read[Map[String, Int]] and
(__ \ "bytesrecv_per_msg").read[Map[String, Int]] and
(__ \ "minfeefilter").readNullable[SatoshisPerKiloByte])(PeerPostV21)
implicit val peerPostV25Reads: Reads[PeerInfoResponseV25] =
((__ \ "id").read[Int] and
__.read[PeerNetworkInfoPostV21] and
(__ \ "version").read[Int] and
(__ \ "subver").read[String] and
(__ \ "inbound").read[Boolean] and
(__ \ "connection_type").read[String] and
(__ \ "startingheight").read[Int] and
(__ \ "synced_headers").read[Int] and
(__ \ "synced_blocks").read[Int] and
(__ \ "inflight").read[Vector[Int]] and
(__ \ "bytessent_per_msg").read[Map[String, Int]] and
(__ \ "bytesrecv_per_msg").read[Map[String, Int]] and
(__ \ "minfeefilter").readNullable[SatoshisPerKiloByte] and
(__ \ "bip152_hb_to").read[Boolean] and
(__ \ "bip152_hb_from").read[Boolean] and
(__ \ "permissions").read[Vector[String]] and
(__ \ "transport_protocol_type").read[String] and
(__ \ "session_id").read[String])(PeerInfoResponseV25)
implicit val nodeBanPostV22Reads: Reads[NodeBanPostV22] =
Json.reads[NodeBanPostV22]
@ -256,6 +262,10 @@ object JsonSerializers {
implicit val dumpTxOutSetResultReads: Reads[DumpTxOutSetResult] =
Json.reads[DumpTxOutSetResult]
implicit val loadTxOutSetResultReads: Reads[LoadTxOutSetResult] = {
Json.reads[LoadTxOutSetResult]
}
implicit val getBlockResultReads: Reads[GetBlockResult] =
Json.reads[GetBlockResult]
@ -885,4 +895,18 @@ object JsonSerializers {
}
}
}
implicit val chainStateReads: Reads[ChainState] = Json.reads[ChainState]
implicit val chainStateResultReads: Reads[ChainStateResult] =
Json.reads[ChainStateResult]
implicit val prioritisedTransactionReads: Reads[PrioritisedTransaction] = {
Json.reads[PrioritisedTransaction]
}
implicit val addrManInfoReads: Reads[AddrManInfo] = Json.reads[AddrManInfo]
implicit val getAddrmanInfoResponseReads: Reads[GetAddrmanInfoResponse] = {
Json.reads[GetAddrmanInfoResponse]
}
}

View file

@ -7,8 +7,8 @@ class BitcoindVersionTest extends BitcoindRpcTest {
behavior of "BitcoindVersion"
it should "return version 25" in {
val version = BitcoindVersion.fromNetworkVersion(250100)
assert(version.equals(BitcoindVersion.V25))
val version = BitcoindVersion.fromNetworkVersion(260100)
assert(version.equals(BitcoindVersion.V26))
}
}

View file

@ -291,4 +291,19 @@ class BlockchainRpcTest extends BitcoindFixturesCachedPairNewest {
assert(!response2.asInstanceOf[ScanBlocksAbortResult].aborted)
}
}
it must "be able to getchainstates" in { case nodePair =>
val client = nodePair.node1
val bestBlockHashF = client.getBestBlockHash()
val blockCountF = client.getBlockCount()
for {
bestBlockHash <- bestBlockHashF
blockCount <- blockCountF
chainStateResult <- client.getChainStates()
} yield {
assert(chainStateResult.headers == blockCount)
assert(chainStateResult.chainstates.size == 1)
assert(chainStateResult.chainstates.head.bestblockhash == bestBlockHash)
}
}
}

View file

@ -138,4 +138,20 @@ class DisconnectedPeersRpcTest
} yield assert(newBestHash == bestHash1)
}
it must "connect to a peer with v2transport" in { nodePair =>
val freshClient = nodePair.node1
val otherFreshClient = nodePair.node2
val uri = otherFreshClient.getDaemon.uri
for {
_ <- freshClient.addNode(uri, AddNodeArgument.OneTry, v2transport = true)
_ <- BitcoindRpcTestUtil.awaitConnection(from = freshClient,
to = otherFreshClient,
interval = 1.second)
info <- freshClient.getPeerInfo
} yield {
assert(info.exists(_.transport_protocol_type == "v2"))
}
}
}

View file

@ -121,9 +121,14 @@ class MempoolRpcTest extends BitcoindFixturesCachedPairNewest {
txid <-
BitcoindRpcTestUtil
.fundMemPoolTransaction(client, address, Bitcoins(3.2))
tt <- client.prioritiseTransaction(txid, Bitcoins(1).satoshis)
tt <- client.prioritiseTransaction(txid, Bitcoins.one.satoshis)
txs <- client.getPrioritisedTransactions()
} yield {
assert(tt)
assert(txs.exists(_._1 == txid))
val p = txs(txid)
assert(p.in_mempool)
assert(p.fee_delta == Bitcoins.one)
}
}
@ -192,6 +197,8 @@ class MempoolRpcTest extends BitcoindFixturesCachedPairNewest {
assert(!regTest.list().contains("mempool.dat"))
for {
_ <- client.saveMemPool()
mempoolPath = regTest.toPath.resolve("mempool.dat")
_ <- client.importMempool(mempoolPath)
} yield assert(regTest.list().contains("mempool.dat"))
}

View file

@ -1,13 +1,9 @@
package org.bitcoins.rpc.common
import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts
import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts.AddressType
import org.bitcoins.core.currency.{Bitcoins, Satoshis}
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.P2PKHAddress
import org.bitcoins.core.protocol.transaction._
import org.bitcoins.core.wallet.fee.SatoshisPerByte
import org.bitcoins.crypto.ECPrivateKey
import org.bitcoins.rpc._
import org.bitcoins.rpc.client.common._
import org.bitcoins.rpc.config.{BitcoindInstanceLocal, BitcoindInstanceRemote}
@ -196,16 +192,6 @@ class MultiWalletRpcTest extends BitcoindFixturesCachedPairNewest {
}
}
it should "be able to refill the keypool" in { nodePair =>
val client = nodePair.node2
for {
_ <- client.walletPassphrase(password, 1000, walletName)
info <- client.getWalletInfo(walletName)
_ <- client.keyPoolRefill(info.keypoolsize + 1, walletName)
newInfo <- client.getWalletInfo(walletName)
} yield assert(newInfo.keypoolsize == info.keypoolsize + 1)
}
it should "be able to change the wallet password" in { nodePair =>
val walletClient = nodePair.node2
val newPass = "new_password"
@ -284,49 +270,6 @@ class MultiWalletRpcTest extends BitcoindFixturesCachedPairNewest {
}
}
it should "be able to import multiple addresses with importMulti" in {
nodePair =>
val client = nodePair.node2
val privKey = ECPrivateKey.freshPrivateKey
val address1 = P2PKHAddress(privKey.publicKey, networkParam)
val privKey1 = ECPrivateKey.freshPrivateKey
val privKey2 = ECPrivateKey.freshPrivateKey
for {
firstResult <-
client
.createMultiSig(
2,
Vector(privKey1.publicKey, privKey2.publicKey),
AddressType.Bech32,
walletNameOpt = Some(walletName)
)
address2 = firstResult.address
secondResult <-
client
.importMulti(
Vector(
RpcOpts.ImportMultiRequest(
RpcOpts.ImportMultiAddress(address1),
UInt32(0)
),
RpcOpts.ImportMultiRequest(
RpcOpts.ImportMultiAddress(address2),
UInt32(0)
)
),
rescan = false,
walletName = walletName
)
} yield {
assert(secondResult.length == 2)
assert(secondResult(0).success)
assert(secondResult(1).success)
}
}
it should "be able to set the tx fee" in { nodePair =>
val client = nodePair.node2
for {

View file

@ -13,12 +13,15 @@ class NodeRpcTest extends BitcoindFixturesFundedCachedNewest {
it should "be able to abort a rescan of the blockchain" in { case client =>
// generate some extra blocks so rescan isn't too quick
client.getNewAddress
.flatMap(client.generateToAddress(3000, _))
.flatMap(client.generateToAddress(500, _))
.flatMap { _ =>
val rescanFailedF =
recoverToSucceededIf[MiscError](client.rescanBlockChain())
system.scheduler.scheduleOnce(100.millis) {
client.abortRescan()
client
.abortRescan()
.failed
.foreach(err => logger.error(s"NodeRpc.err abort rescan", err))
()
}
rescanFailedF

View file

@ -116,4 +116,17 @@ class P2PRpcTest extends BitcoindFixturesCachedPairNewest {
succeed
}
}
it should "getaddrmaninfo" in { case nodePair =>
val client = nodePair.node1
for {
result <- client.getAddrManInfo()
} yield {
assert(result.ipv4.total == 0)
assert(result.ipv6.total == 0)
assert(result.onion.total == 0)
assert(result.cjdns.total == 0)
assert(result.all_networks.total == 0)
}
}
}

View file

@ -70,19 +70,33 @@ class UTXORpcTest extends BitcoindFixturesFundedCachedNewest {
}
}
it should "correctly dump tx out set" in { case client =>
it should "correctly dump tx out set and then load it" in { case client =>
val path = new File("utxo.dat").toPath
for {
hash <- client.getBestBlockHash()
height <- client.getBestHashBlockHeight()
result <- client.dumpTxOutSet(new File("utxo.dat").toPath)
result <- client.dumpTxOutSet(path)
// now attempt to load it
// unfortunately it seems this cannot be properly tested
// we end up with this error:
// Unable to load UTXO snapshot, assumeutxo block hash in snapshot metadata not recognized
// see: https://bitcoin.stackexchange.com/questions/121006/anyone-tried-assumeutxo-yet
// loadResult <- client.loadTxOutSet(path)
} yield {
assert(Files.exists(result.path))
// Mild clean up
Files.delete(result.path)
assert(result.base_hash == hash)
assert(result.base_height == height)
assert(result.coins_written > 0)
// assert(loadResult.path == path)
// assert(loadResult.tip_hash == hash)
// assert(loadResult.base_height == height)
// assert(loadResult.coins_loaded > 0)
// Mild clean up
Files.delete(result.path)
succeed
}
}
}

View file

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

View file

@ -15,8 +15,9 @@ import org.bitcoins.core.util.{FutureUtil, NetworkUtil}
import org.bitcoins.core.wallet.fee.FeeUnit
import org.bitcoins.crypto.{DoubleSha256DigestBE, StringFactory}
import org.bitcoins.rpc.client.v18.V18AssortedRpc
import org.bitcoins.rpc.client.v20.{V20AssortedRpc, V20MultisigRpc}
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.config._
import java.io.File
@ -53,8 +54,7 @@ class BitcoindRpcClient(override val instance: BitcoindInstance)(implicit
with UtilRpc
with V18AssortedRpc
with DescriptorRpc
with V20MultisigRpc
with V20AssortedRpc {
with V20MultisigRpc {
private val syncing = new AtomicBoolean(false)
@ -344,6 +344,7 @@ object BitcoindRpcClient {
): BitcoindRpcClient = {
val bitcoind = version match {
case BitcoindVersion.V25 => BitcoindV25RpcClient.withActorSystem(instance)
case BitcoindVersion.V26 => BitcoindV26RpcClient.withActorSystem(instance)
case BitcoindVersion.Unknown =>
sys.error(
s"Cannot create a Bitcoin Core RPC client: unsupported version"
@ -367,7 +368,7 @@ object BitcoindVersion
with BitcoinSLogger {
/** The newest version of `bitcoind` we support */
val newest: BitcoindVersion = V25
val newest: BitcoindVersion = V26
val standard: Vector[BitcoindVersion] =
Vector(V25)
@ -378,6 +379,10 @@ object BitcoindVersion
override def toString: String = "v25"
}
case object V26 extends BitcoindVersion {
override def toString: String = "v26"
}
case object Unknown extends BitcoindVersion {
override def toString: String = "Unknown"
}

View file

@ -436,4 +436,8 @@ trait BlockchainRpc extends ChainApi { self: Client =>
bitcoindCall("scanblocks", request.params)(
JsonSerializers.ScanBlocksResultReads)
}
def getChainStates(): Future[ChainStateResult] = {
bitcoindCall("getchainstates")(JsonSerializers.chainStateResultReads)
}
}

View file

@ -8,6 +8,7 @@ import org.bitcoins.crypto.{DoubleSha256Digest, DoubleSha256DigestBE}
import org.bitcoins.rpc.BitcoindException
import play.api.libs.json.{JsArray, JsBoolean, JsString, Json}
import java.nio.file.Path
import scala.concurrent.Future
/** This trait defines RPC calls related to the mempool of a Bitcoin Core node.
@ -129,6 +130,10 @@ trait MempoolRpc { self: Client =>
bitcoindCall[Unit]("savemempool")
}
def importMempool(path: Path): Future[Unit] = {
bitcoindCall[Unit]("importmempool", List(JsString(path.toString)))
}
def testMempoolAccept(
transaction: Vector[Transaction],
maxFeeRate: Double = 0.10

View file

@ -4,6 +4,7 @@ import org.bitcoins.commons.jsonmodels.bitcoind.{
GenerateBlockResult,
GetBlockTemplateResult,
GetMiningInfoResult,
PrioritisedTransaction,
RpcOpts
}
import org.bitcoins.commons.serializers.JsonReaders._
@ -93,4 +94,21 @@ trait MiningRpc { self: Client with BlockchainRpc =>
): Future[Boolean] = {
prioritiseTransaction(txid.flip, feeDelta)
}
def getPrioritisedTransactions()
: Future[Map[DoubleSha256DigestBE, PrioritisedTransaction]] = {
bitcoindCall[Map[DoubleSha256DigestBE, PrioritisedTransaction]](
"getprioritisedtransactions")
}
def generateToDescriptor(
numBlocks: Int,
descriptor: String,
maxTries: Long = 1000000
): Future[Vector[DoubleSha256DigestBE]] = {
bitcoindCall[Vector[DoubleSha256DigestBE]](
"generatetodescriptor",
List(JsNumber(numBlocks), JsString(descriptor), JsNumber(maxTries))
)
}
}

View file

@ -27,7 +27,7 @@ trait MultisigRpc { self: Client =>
keys: Vector[Either[ECPublicKey, P2PKHAddress]],
account: String = "",
addressType: Option[AddressType],
walletNameOpt: Option[String] = None
walletName: String = BitcoindRpcClient.DEFAULT_WALLET_NAME
): Future[MultiSigResult] = {
def keyToString(key: Either[ECPublicKey, P2PKHAddress]): JsString =
key match {
@ -45,7 +45,7 @@ trait MultisigRpc { self: Client =>
bitcoindCall[MultiSigResultPostV20](
"addmultisigaddress",
params,
uriExtensionOpt = walletNameOpt.map(walletExtension)
uriExtensionOpt = Some(walletExtension(walletName))
)
}
@ -81,7 +81,7 @@ trait MultisigRpc { self: Client =>
minSignatures: Int,
keys: Vector[ECPublicKey],
addressType: AddressType,
walletNameOpt: Option[String] = None
walletName: String = BitcoindRpcClient.DEFAULT_WALLET_NAME
): Future[MultiSigResult] = {
bitcoindCall[MultiSigResultPostV20](
"createmultisig",
@ -90,7 +90,7 @@ trait MultisigRpc { self: Client =>
Json.toJson(keys.map(_.hex)),
Json.toJson(addressType)
),
uriExtensionOpt = walletNameOpt.map(walletExtension)
uriExtensionOpt = Some(walletExtension(walletName))
)
}
}

View file

@ -5,6 +5,7 @@ import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts.{
SetBanCommand
}
import org.bitcoins.commons.jsonmodels.bitcoind._
import org.bitcoins.commons.serializers.JsonSerializers
import org.bitcoins.commons.serializers.JsonSerializers._
import org.bitcoins.core.protocol.blockchain.Block
import play.api.libs.json.{JsBoolean, JsNumber, JsString}
@ -17,10 +18,15 @@ import scala.concurrent.Future
*/
trait P2PRpc { self: Client =>
def addNode(address: URI, command: AddNodeArgument): Future[Unit] = {
def addNode(
address: URI,
command: AddNodeArgument,
v2transport: Boolean = true): Future[Unit] = {
bitcoindCall[Unit](
"addnode",
List(JsString(address.getAuthority), JsString(command.toString))
List(JsString(address.getAuthority),
JsString(command.toString),
JsBoolean(v2transport))
)
}
@ -62,8 +68,8 @@ trait P2PRpc { self: Client =>
}
}
def getPeerInfo: Future[Vector[Peer]] = {
bitcoindCall[Vector[PeerPostV21]]("getpeerinfo")
def getPeerInfo: Future[Vector[PeerInfoResponseV25]] = {
bitcoindCall[Vector[PeerInfoResponseV25]]("getpeerinfo")
}
def listBanned: Future[Vector[NodeBan]] = {
@ -93,6 +99,10 @@ trait P2PRpc { self: Client =>
def submitBlock(block: Block): Future[Unit] = {
bitcoindCall[Unit]("submitblock", List(JsString(block.hex)))
}
def getAddrManInfo(): Future[GetAddrmanInfoResponse] = {
bitcoindCall[GetAddrmanInfoResponse]("getaddrmaninfo")(
JsonSerializers.getAddrmanInfoResponseReads)
}
}

View file

@ -1,11 +1,18 @@
package org.bitcoins.rpc.client.common
import org.bitcoins.commons.jsonmodels.bitcoind.{RpcOpts, UnspentOutput}
import org.bitcoins.commons.jsonmodels.bitcoind.{
DumpTxOutSetResult,
LoadTxOutSetResult,
RpcOpts,
UnspentOutput
}
import org.bitcoins.commons.serializers.JsonSerializers
import org.bitcoins.commons.serializers.JsonSerializers._
import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.transaction.TransactionOutPoint
import play.api.libs.json._
import java.nio.file.Path
import scala.concurrent.Future
/** This trait defines functionality related to UTXOs (unspent transaction
@ -70,4 +77,18 @@ trait UTXORpc { self: Client =>
)
}
def dumpTxOutSet(path: Path): Future[DumpTxOutSetResult] = {
bitcoindCall[DumpTxOutSetResult](
"dumptxoutset",
List(Json.toJson(path.toString))
)
}
def loadTxOutSet(path: Path): Future[LoadTxOutSetResult] = {
bitcoindCall[LoadTxOutSetResult](
"loadtxoutset",
List(Json.toJson(path.toString))
)(JsonSerializers.loadTxOutSetResultReads)
}
}

View file

@ -24,7 +24,7 @@ trait UtilRpc { self: Client =>
}
def decodeScript(script: ScriptPubKey): Future[DecodeScriptResult] = {
self.version.flatMap { case V25 | Unknown =>
self.version.flatMap { case V25 | V26 | Unknown =>
bitcoindCall[DecodeScriptResultV22](
"decodescript",
List(Json.toJson(script))
@ -34,13 +34,13 @@ trait UtilRpc { self: Client =>
}
def getIndexInfo: Future[Map[String, IndexInfoResult]] = {
version.flatMap { case V25 | Unknown =>
version.flatMap { case V25 | V26 | Unknown =>
bitcoindCall[Map[String, IndexInfoResult]]("getindexinfo")
}
}
def getIndexInfo(indexName: String): Future[IndexInfoResult] = {
version.flatMap { case V25 | Unknown =>
version.flatMap { case V25 | V26 | Unknown =>
bitcoindCall[Map[String, IndexInfoResult]](
"getindexinfo",
List(JsString(indexName))

View file

@ -160,11 +160,13 @@ trait WalletRpc { self: Client =>
def getWalletInfo(
walletName: String
): Future[GetWalletInfoResult] = {
self.version.flatMap { case BitcoindVersion.V25 | BitcoindVersion.Unknown =>
bitcoindCall[GetWalletInfoResultPostV22](
"getwalletinfo",
uriExtensionOpt = Some(walletExtension(walletName))
)
self.version.flatMap {
case BitcoindVersion.V25 | BitcoindVersion.V26 |
BitcoindVersion.Unknown =>
bitcoindCall[GetWalletInfoResultPostV22](
"getwalletinfo",
uriExtensionOpt = Some(walletExtension(walletName))
)
}
}
@ -378,10 +380,10 @@ trait WalletRpc { self: Client =>
blank: Boolean = false,
passphrase: String = "",
avoidReuse: Boolean = false,
descriptors: Boolean = false
descriptors: Boolean = true
): Future[CreateWalletResult] =
self.version.flatMap {
case V25 =>
case V25 | V26 =>
bitcoindCall[CreateWalletResult](
"createwallet",
List(
@ -410,13 +412,11 @@ trait WalletRpc { self: Client =>
address: BitcoinAddress,
walletName: String = DEFAULT_WALLET
): Future[AddressInfoResult] = {
self.version.flatMap { case V25 | Unknown =>
bitcoindCall[AddressInfoResultPostV21](
"getaddressinfo",
List(JsString(address.value)),
uriExtensionOpt = Some(walletExtension(walletName))
)
}
bitcoindCall[AddressInfoResultPostV21](
"getaddressinfo",
List(JsString(address.value)),
uriExtensionOpt = Some(walletExtension(walletName))
)
}
def sendMany(

View file

@ -1,34 +0,0 @@
package org.bitcoins.rpc.client.v20
import java.nio.file.Path
import org.bitcoins.commons.jsonmodels.bitcoind.DumpTxOutSetResult
import org.bitcoins.commons.serializers.JsonSerializers._
import org.bitcoins.crypto.DoubleSha256DigestBE
import org.bitcoins.rpc.client.common.Client
import play.api.libs.json.{JsNumber, JsString, Json}
import scala.concurrent.Future
/** Assorted Rpc calls for Bitcoin V20
*/
trait V20AssortedRpc { self: Client =>
def dumpTxOutSet(path: Path): Future[DumpTxOutSetResult] = {
bitcoindCall[DumpTxOutSetResult](
"dumptxoutset",
List(Json.toJson(path.toString))
)
}
def generateToDescriptor(
numBlocks: Int,
descriptor: String,
maxTries: Long = 1000000
): Future[Vector[DoubleSha256DigestBE]] = {
bitcoindCall[Vector[DoubleSha256DigestBE]](
"generatetodescriptor",
List(JsNumber(numBlocks), JsString(descriptor), JsNumber(maxTries))
)
}
}

View file

@ -6,7 +6,7 @@ import org.bitcoins.commons.serializers.JsonSerializers._
import org.bitcoins.commons.serializers.JsonWriters._
import org.bitcoins.core.protocol.P2PKHAddress
import org.bitcoins.crypto.ECPublicKey
import org.bitcoins.rpc.client.common.{Client, MultisigRpc}
import org.bitcoins.rpc.client.common.{BitcoindRpcClient, Client, MultisigRpc}
import play.api.libs.json.{JsArray, JsNumber, JsString, Json}
import scala.concurrent.Future
@ -74,7 +74,7 @@ trait V20MultisigRpc extends MultisigRpc { self: Client =>
minSignatures: Int,
keys: Vector[ECPublicKey],
addressType: AddressType,
walletNameOpt: Option[String] = None
walletName: String = BitcoindRpcClient.DEFAULT_WALLET_NAME
): Future[MultiSigResultPostV20] = {
bitcoindCall[MultiSigResultPostV20](
"createmultisig",
@ -83,7 +83,7 @@ trait V20MultisigRpc extends MultisigRpc { self: Client =>
Json.toJson(keys.map(_.hex)),
Json.toJson(addressType)
),
uriExtensionOpt = walletNameOpt.map(walletExtension)
uriExtensionOpt = Some(walletExtension(walletName))
)
}
}

View file

@ -0,0 +1,48 @@
package org.bitcoins.rpc.client.v26
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 BitcoindV26RpcClient(override val instance: BitcoindInstance)(implicit
actorSystem: ActorSystem
) extends BitcoindRpcClient(instance) {
override lazy val version: Future[BitcoindVersion] =
Future.successful(BitcoindVersion.V26)
}
object BitcoindV26RpcClient {
/** 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): BitcoindV26RpcClient = {
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
): BitcoindV26RpcClient =
new BitcoindV26RpcClient(instance)(system)
def fromUnknownVersion(
rpcClient: BitcoindRpcClient
): Try[BitcoindV26RpcClient] =
Try {
new BitcoindV26RpcClient(rpcClient.instance)(rpcClient.system)
}
}

View file

@ -38,6 +38,10 @@ class ServiceIdentifierTest extends BitcoinSUnitTest {
assert(ServiceIdentifier.NODE_NETWORK_LIMITED.nodeNetworkLimited)
}
it must "parse P2P_V2" in {
assert(ServiceIdentifier.NODE_P2P_V2.nodeP2PV2)
}
it must "correctly get a ServiceIdentifier from string" in {
assert(
ServiceIdentifier.fromString("NETWORK") == ServiceIdentifier.NODE_NETWORK
@ -63,6 +67,10 @@ class ServiceIdentifierTest extends BitcoinSUnitTest {
assert(
ServiceIdentifier.fromString("XTHIN") == ServiceIdentifier.NODE_XTHIN
)
assert(
ServiceIdentifier.fromString("P2P_V2") == ServiceIdentifier.NODE_P2P_V2
)
assertThrows[IllegalArgumentException](
ServiceIdentifier.fromString("this is invalid")
)

View file

@ -86,11 +86,14 @@ sealed abstract class ServiceIdentifier extends NetworkElement {
*/
lazy val nodeNetworkLimited: Boolean = reversedBits(10) // 1 << 10
lazy val nodeP2PV2: Boolean = reversedBits(11) // 1 << 1
override def toString: String = {
val innerText =
if (nodeNone) "none"
else
s"network=$nodeNetwork,compactFilters=$nodeCompactFilters,getUtxo=$nodeGetUtxo,bloom=$nodeBloom,witness=$nodeWitness,xthin=$nodeXthin,networkLimited=$nodeNetworkLimited"
s"network=$nodeNetwork,compactFilters=$nodeCompactFilters,getUtxo=$nodeGetUtxo,bloom=$nodeBloom," +
s"witness=$nodeWitness,xthin=$nodeXthin,networkLimited=$nodeNetworkLimited,v2transport=$nodeP2PV2"
s"ServiceIdentifier($innerText)"
}
@ -165,13 +168,15 @@ object ServiceIdentifier
*/
val NODE_NETWORK_LIMITED: ServiceIdentifier = ServiceIdentifier(1 << 10)
val NODE_P2P_V2: ServiceIdentifier = ServiceIdentifier(1 << 11)
private case class ServiceIdentifierImpl(num: UInt64)
extends ServiceIdentifier
def fromBytes(bytes: ByteVector): ServiceIdentifier =
RawServiceIdentifierSerializer.read(bytes)
override def fromString(string: String): ServiceIdentifier =
override def fromString(string: String): ServiceIdentifier = {
string match {
case "NETWORK" => NODE_NETWORK
case "NETWORK_LIMITED" => NODE_NETWORK_LIMITED
@ -180,11 +185,13 @@ object ServiceIdentifier
case "GETUTXO" => NODE_GET_UTXO
case "COMPACT_FILTERS" => NODE_COMPACT_FILTERS
case "XTHIN" => NODE_XTHIN
case "P2P_V2" => NODE_P2P_V2
case _: String =>
throw new IllegalArgumentException(
s""""$string" does not represent a ServiceIdentifier"""
s"$string does not represent a ServiceIdentifier"
)
}
}
def apply(num: BigInt): ServiceIdentifier = ServiceIdentifier(UInt64(num))

View file

@ -31,6 +31,7 @@ import org.bitcoins.rpc.BitcoindException
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.config._
import org.bitcoins.rpc.util.{NodePair, RpcUtil}
import org.bitcoins.testkit.util.{BitcoindRpcTestClient, FileUtil, TorUtil}
@ -102,6 +103,7 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
|regtest=1
|server=1
|daemon=$isDaemon
|v2transport=1
|[regtest]
|rpcuser=$username
|rpcpassword=$pass
@ -176,7 +178,7 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
version match {
// default to newest version
case Unknown => getBinary(BitcoindVersion.newest, binaryDirectory)
case known @ (V25) =>
case known @ (V25 | V26) =>
val fileList = Files
.list(binaryDirectory)
.iterator()
@ -256,6 +258,22 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
binaryDirectory = binaryDirectory
)
def v26Instance(
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.V26),
binaryDirectory = binaryDirectory
)
/** Gets an instance of bitcoind with the given version */
def getInstance(
bitcoindVersion: BitcoindVersion,
@ -274,6 +292,14 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
pruneMode,
binaryDirectory = binaryDirectory
)
case BitcoindVersion.V26 =>
BitcoindRpcTestUtil.v26Instance(
port,
rpcPort,
zmqConfig,
pruneMode,
binaryDirectory = binaryDirectory
)
case BitcoindVersion.Unknown =>
sys.error(
s"Could not create a bitcoind version with version=${BitcoindVersion.Unknown}"
@ -286,10 +312,8 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
)(implicit ec: ExecutionContext): Future[Unit] = {
val startedServersF = Future.traverse(servers) { server =>
server.start().flatMap { res =>
val descriptors = true
val createWalletF = for {
_ <- res.createWallet(BitcoindRpcClient.DEFAULT_WALLET_NAME,
descriptors = descriptors)
_ <- res.createWallet(BitcoindRpcClient.DEFAULT_WALLET_NAME)
} yield res
createWalletF
@ -615,6 +639,9 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
case BitcoindVersion.V25 =>
BitcoindV25RpcClient.withActorSystem(
BitcoindRpcTestUtil.v25Instance())
case BitcoindVersion.V26 =>
BitcoindV26RpcClient.withActorSystem(
BitcoindRpcTestUtil.v26Instance())
}
// this is safe as long as this method is never