Bitcoind v19 RPC (#910)

* bitcoind v19 new RPC calls and tests (#863)

* bitcoind v19 new RPC calls and tests

* Code review changes

* Review part 2

* Rename variable to be more descriptive

* Explanitory comment

* Ignore broken test cases

* Add missing signing functions

* Add test to check avoid_reuse flag is on (#870)

* Add test to check avoid_reuse flag is on

* Add test to make sure flags weren't set

* bitcoind v19 Update mempool RPCs and tests (#868)

* Update mempool RPC calls to bitcoind v19 compatibility

* Typo fix

* Add parameter name to calls

* Fix remaining rpc calls

* Formatting

* scaladoc for param

* Change param to correct type

* Clarify on scaladoc

* Add missing fees parmater to mempool rpcs (#875)

* Add weight field to mempool entries after v19 (#876)

* Move DescriptorRpc to be able to be used by future versions of bitcoind (#878)

* Add window_final_block_height to GetChainTxStatsResult (#880)

* Add passphrase argument to createwallet for later versions (#883)

* Add passphrase argument to createwallet for later versions

* Scaladoc + empty passphrase requirement

* Error message

* Add new services names parameter to P2P rpcs (#874)

* Add new services names parameter to P2P rpcs

* Add ServiceIdentifier Reads

* Add fallback case

* Address review

* Change to Try

* Move PsbtRpc to be able to be used by future versions of bitcoind (#877)

* Move PsbtRpc to be able to be used by future versions of bitcoind

* Add test

* Address comment

* Enable bloom filters for v19

* Enable bip 61 for tests

* Change to official binaries

* Force v18 for Spv Tests

* Remove unused config line
This commit is contained in:
Ben Carman 2019-12-04 07:44:44 -06:00 committed by Chris Stewart
parent dd4787f2af
commit 5206353a4a
25 changed files with 652 additions and 86 deletions

View File

@ -7,3 +7,4 @@ zmppubrawtx=tcp://127.0.0.1:29001
rpcport=18500
port=9000
daemon=1
blockfilterindex=1

View File

@ -5,11 +5,9 @@ import java.io.File
import org.bitcoins.core.currency.Bitcoins
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.script.ScriptSignature
import org.bitcoins.core.protocol.transaction.{
TransactionInput,
TransactionOutPoint
}
import org.bitcoins.core.protocol.transaction.{TransactionInput, TransactionOutPoint}
import org.bitcoins.rpc.client.common.BitcoindRpcClient
import org.bitcoins.rpc.client.common.BitcoindVersion.V18
import org.bitcoins.rpc.config.BitcoindInstance
import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil
import org.bitcoins.testkit.util.BitcoindRpcTest
@ -31,7 +29,7 @@ class MempoolRpcTest extends BitcoindRpcTest {
val instanceWithoutBroadcast =
BitcoindInstance.fromConfig(configNoBroadcast,
BitcoindRpcTestUtil.newestBitcoindBinary)
BitcoindRpcTestUtil.getBinary(V18))
val clientWithoutBroadcast =
BitcoindRpcClient.withActorSystem(instanceWithoutBroadcast)
@ -137,7 +135,7 @@ class MempoolRpcTest extends BitcoindRpcTest {
.createRawTransaction(Vector(input), Map(address2 -> Bitcoins.one))
}
signedTx <- BitcoindRpcTestUtil.signRawTransaction(client, createdTx)
txid2 <- client.sendRawTransaction(signedTx.hex, allowHighFees = true)
txid2 <- client.sendRawTransaction(signedTx.hex, maxfeerate = 0)
descendantsTxid1 <- client.getMemPoolDescendants(txid1)
verboseDescendantsTxid1 <- client.getMemPoolDescendantsVerbose(txid1)
@ -152,9 +150,9 @@ class MempoolRpcTest extends BitcoindRpcTest {
verboseAncestorsTxid2 <- client.getMemPoolAncestorsVerbose(txid2)
_ = {
assert(ancestorsTxid2.head == txid1)
val (txid, mempoolreults) = verboseAncestorsTxid2.head
val (txid, mempoolresults) = verboseAncestorsTxid2.head
assert(txid == txid1)
assert(mempoolreults.descendantcount == 2)
assert(mempoolresults.descendantcount == 2)
}
} yield {

View File

@ -125,8 +125,7 @@ class RawTransactionRpcTest extends BitcoindRpcTest {
_ <- client.getNewAddress.flatMap(client.generateToAddress(100, _)) // Can't spend coinbase until depth 100
_ <- client.sendRawTransaction(signedTransaction.hex,
allowHighFees = true)
_ <- client.sendRawTransaction(signedTransaction.hex, maxfeerate = 0)
} yield succeed
}

View File

@ -31,9 +31,9 @@ import scala.async.Async.{async, await}
import scala.concurrent.Future
class WalletRpcTest extends BitcoindRpcTest {
lazy val clientsF
: Future[(BitcoindRpcClient, BitcoindRpcClient, BitcoindRpcClient)] =
BitcoindRpcTestUtil.createNodeTripleV17(clientAccum = clientAccum)
lazy val clientsF: Future[
(BitcoindRpcClient, BitcoindRpcClient, BitcoindRpcClient)] =
BitcoindRpcTestUtil.createNodeTripleV19(clientAccum = clientAccum)
// This client's wallet is encrypted
lazy val walletClientF: Future[BitcoindRpcClient] = clientsF.flatMap { _ =>
@ -514,7 +514,7 @@ class WalletRpcTest extends BitcoindRpcTest {
client.createRawTransaction(inputs, outputs)
}
stx <- BitcoindRpcTestUtil.signRawTransaction(client, rawTx)
txid <- client.sendRawTransaction(stx.hex, allowHighFees = true)
txid <- client.sendRawTransaction(stx.hex, 0)
tx <- client.getTransaction(txid)
bumpedTx <- client.bumpFee(txid)
} yield assert(tx.fee.get < bumpedTx.fee)

View File

@ -0,0 +1,111 @@
package org.bitcoins.rpc.v19
import org.bitcoins.core.gcs.{BlockFilter, FilterType}
import org.bitcoins.rpc.client.common.BitcoindVersion
import org.bitcoins.rpc.client.common.RpcOpts.WalletFlag
import org.bitcoins.rpc.client.v19.BitcoindV19RpcClient
import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil
import org.bitcoins.testkit.util.BitcoindRpcTest
import scala.concurrent.Future
class BitcoindV19RpcClientTest extends BitcoindRpcTest {
lazy val clientF: Future[BitcoindV19RpcClient] = {
val client = new BitcoindV19RpcClient(BitcoindRpcTestUtil.v19Instance())
val clientIsStartedF = BitcoindRpcTestUtil.startServers(Vector(client))
clientIsStartedF.map(_ => client)
}
lazy val clientPairF: Future[(BitcoindV19RpcClient, BitcoindV19RpcClient)] =
BitcoindRpcTestUtil.createNodePairV19(clientAccum)
clientF.foreach(c => clientAccum.+=(c))
behavior of "BitcoindV19RpcClient"
it should "be able to start a V19 bitcoind instance" in {
clientF.map { client =>
assert(client.version == BitcoindVersion.V19)
}
}
it should "get a block filter given a block hash" in {
for {
(client, _) <- clientPairF
blocks <- client.getNewAddress.flatMap(client.generateToAddress(1, _))
blockFilter <- client.getBlockFilter(blocks.head, FilterType.Basic)
block <- client.getBlockRaw(blocks.head)
txs <- Future.sequence(
block.transactions
.filterNot(_.isCoinbase)
.map(tx => client.getTransaction(tx.txIdBE)))
prevFilter <- client.getBlockFilter(block.blockHeader.previousBlockHashBE,
FilterType.Basic)
} yield {
val pubKeys = txs.flatMap(_.hex.outputs.map(_.scriptPubKey)).toVector
val filter = BlockFilter(block, pubKeys)
assert(filter.hash == blockFilter.filter.hash)
assert(
blockFilter.header == filter
.getHeader(prevFilter.header.flip)
.hash
.flip)
}
}
it should "be able to get the balances" in {
for {
(client, _) <- clientPairF
immatureBalance <- client.getBalances
_ <- client.getNewAddress.flatMap(client.generateToAddress(1, _))
newImmatureBalance <- client.getBalances
} yield {
val blockReward = 12.5
assert(immatureBalance.mine.immature.toBigDecimal >= 0)
assert(
immatureBalance.mine.immature.toBigDecimal + blockReward == newImmatureBalance.mine.immature.toBigDecimal)
}
}
it should "be able to set the wallet flag 'avoid_reuse'" in {
for {
(client, _) <- clientPairF
unspentPre <- client.listUnspent
result <- client.setWalletFlag(WalletFlag.AvoidReuse, value = true)
unspentPost <- client.listUnspent
} yield {
assert(result.flag_name == "avoid_reuse")
assert(result.flag_state)
assert(unspentPre.forall(utxo => utxo.reused.isEmpty))
assert(unspentPost.forall(utxo => utxo.reused.isDefined))
}
}
it should "create a wallet with a passphrase" in {
for {
(client, _) <- clientPairF
_ <- client.createWallet("suredbits", passphrase = "stackingsats")
wallets <- client.listWallets
} yield {
assert(wallets.contains("suredbits"))
}
}
it should "check to see if the utxoUpdate input has been updated" in {
val descriptor =
"pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)"
val psbt =
"cHNidP8BACoCAAAAAAFAQg8AAAAAABepFG6Rty1Vk+fUOR4v9E6R6YXDFkHwhwAAAAAAAA=="
val updatedF =
clientF.flatMap(client => client.utxoUpdatePsbt(psbt, Seq(descriptor)))
updatedF.map { result =>
assert(result.contains(psbt))
}
}
}

View File

@ -24,9 +24,10 @@ TaskKeys.downloadBitcoind := {
Files.createDirectories(binaryDir)
}
val experimentalVersion = "0.18.99"
val experimentalVersion = "0.18.99" // TODO: change this when new version compiled on suredbits server
val versions = List("0.18.1", "0.17.0.1", "0.16.3", experimentalVersion)
val versions =
List("0.19.0.1", "0.18.1", "0.17.0.1", "0.16.3", experimentalVersion)
logger.debug(
s"(Maybe) downloading Bitcoin Core binaries for versions: ${versions.mkString(",")}")

View File

@ -88,7 +88,7 @@ sealed trait BitcoindVersion
object BitcoindVersion {
/** The newest version of `bitcoind` we support */
val newest = V18
val newest = V19
case object V16 extends BitcoindVersion {
override def toString: String = "v0.16"
@ -102,6 +102,10 @@ object BitcoindVersion {
override def toString: String = "v0.18"
}
case object V19 extends BitcoindVersion {
override def toString: String = "v0.19"
}
case object Experimental extends BitcoindVersion {
override def toString: String = "v0.18.99"
}

View File

@ -1,5 +1,5 @@
package org.bitcoins.rpc.client.v18
import org.bitcoins.rpc.client.common.Client
package org.bitcoins.rpc.client.common
import org.bitcoins.rpc.jsonmodels.{
DeriveAddressesResult,
GetDescriptorInfoResult
@ -14,15 +14,16 @@ import scala.concurrent.Future
* @see [[https://bitcoincore.org/en/doc/0.18.0/rpc/util/deriveaddresses/]]
* @see [[https://bitcoincore.org/en/doc/0.18.0/rpc/util/getdescriptorinfo/]]
*/
trait V18DescriptorRpc {
trait DescriptorRpc {
self: Client =>
def deriveAddresses(
descriptor: String,
range: Option[Vector[Double]]): Future[DeriveAddressesResult] = {
bitcoindCall[DeriveAddressesResult](
"deriveaddresses",
List(JsString(descriptor), Json.toJson(range)))
val params =
if (range.isDefined) List(JsString(descriptor), Json.toJson(range))
else List(JsString(descriptor))
bitcoindCall[DeriveAddressesResult]("deriveaddresses", params)
}
def getDescriptorInfo(descriptor: String): Future[GetDescriptorInfoResult] = {

View File

@ -1,10 +1,15 @@
package org.bitcoins.rpc.client.common
import org.bitcoins.core.crypto.{DoubleSha256Digest, DoubleSha256DigestBE}
import org.bitcoins.rpc.client.common.BitcoindVersion._
import org.bitcoins.rpc.jsonmodels.{
GetMemPoolEntryResult,
GetMemPoolEntryResultPostV19,
GetMemPoolEntryResultPreV19,
GetMemPoolInfoResult,
GetMemPoolResult
GetMemPoolResult,
GetMemPoolResultPostV19,
GetMemPoolResultPreV19
}
import org.bitcoins.rpc.serializers.JsonReaders._
import org.bitcoins.rpc.serializers.JsonSerializers._
@ -17,7 +22,7 @@ import scala.concurrent.Future
* the mempool of a Bitcoin Core node. The
* mempool contains all unconfirmed transactions.
*/
trait MempoolRpc { selc: Client =>
trait MempoolRpc { self: Client =>
def getMemPoolAncestors(
txid: DoubleSha256DigestBE): Future[Vector[DoubleSha256DigestBE]] = {
@ -33,9 +38,17 @@ trait MempoolRpc { selc: Client =>
def getMemPoolAncestorsVerbose(txid: DoubleSha256DigestBE): Future[
Map[DoubleSha256DigestBE, GetMemPoolResult]] = {
bitcoindCall[Map[DoubleSha256DigestBE, GetMemPoolResult]](
"getmempoolancestors",
List(JsString(txid.hex), JsBoolean(true)))
self.version match {
case V19 | Experimental | Unknown =>
bitcoindCall[Map[DoubleSha256DigestBE, GetMemPoolResultPostV19]](
"getmempoolancestors",
List(JsString(txid.hex), JsBoolean(true)))
case V16 | V17 | V18 =>
bitcoindCall[Map[DoubleSha256DigestBE, GetMemPoolResultPreV19]](
"getmempoolancestors",
List(JsString(txid.hex), JsBoolean(true)))
}
}
def getMemPoolAncestorsVerbose(txid: DoubleSha256Digest): Future[
@ -57,9 +70,16 @@ trait MempoolRpc { selc: Client =>
def getMemPoolDescendantsVerbose(txid: DoubleSha256DigestBE): Future[
Map[DoubleSha256DigestBE, GetMemPoolResult]] = {
bitcoindCall[Map[DoubleSha256DigestBE, GetMemPoolResult]](
"getmempooldescendants",
List(JsString(txid.hex), JsBoolean(true)))
self.version match {
case V19 | Experimental | Unknown =>
bitcoindCall[Map[DoubleSha256DigestBE, GetMemPoolResultPostV19]](
"getmempooldescendants",
List(JsString(txid.hex), JsBoolean(true)))
case V16 | V17 | V18 =>
bitcoindCall[Map[DoubleSha256DigestBE, GetMemPoolResultPreV19]](
"getmempooldescendants",
List(JsString(txid.hex), JsBoolean(true)))
}
}
def getMemPoolDescendantsVerbose(txid: DoubleSha256Digest): Future[
@ -69,8 +89,16 @@ trait MempoolRpc { selc: Client =>
def getMemPoolEntry(
txid: DoubleSha256DigestBE): Future[GetMemPoolEntryResult] = {
bitcoindCall[GetMemPoolEntryResult]("getmempoolentry",
List(JsString(txid.hex)))
self.version match {
case V19 | Experimental | Unknown =>
bitcoindCall[GetMemPoolEntryResultPostV19]("getmempoolentry",
List(JsString(txid.hex)))
case V16 | V17 | V18 =>
bitcoindCall[GetMemPoolEntryResultPreV19]("getmempoolentry",
List(JsString(txid.hex)))
}
}
def getMemPoolEntry(
@ -89,9 +117,18 @@ trait MempoolRpc { selc: Client =>
def getRawMemPoolWithTransactions: Future[
Map[DoubleSha256DigestBE, GetMemPoolResult]] = {
bitcoindCall[Map[DoubleSha256DigestBE, GetMemPoolResult]](
"getrawmempool",
List(JsBoolean(true)))
self.version match {
case V19 | Experimental | Unknown =>
bitcoindCall[Map[DoubleSha256DigestBE, GetMemPoolResultPostV19]](
"getrawmempool",
List(JsBoolean(true)))
case V16 | V17 | V18 =>
bitcoindCall[Map[DoubleSha256DigestBE, GetMemPoolResultPreV19]](
"getrawmempool",
List(JsBoolean(true)))
}
}
def saveMemPool(): Future[Unit] = {

View File

@ -1,19 +1,19 @@
package org.bitcoins.rpc.client.v18
package org.bitcoins.rpc.client.common
import org.bitcoins.rpc.client.common.Client
import org.bitcoins.rpc.jsonmodels.AnalyzePsbtResult
import org.bitcoins.rpc.serializers.JsonSerializers._
import play.api.libs.json._
import play.api.libs.json.{JsString, Json}
import scala.concurrent.Future
/**
* Set of utilities to analyze, join, and update existing PSBTs
*
* @see [[https://bitcoincore.org/en/doc/0.18.0/rpc/rawtransactions/analyzepsbt/]]
* @see [[https://bitcoincore.org/en/doc/0.18.0/rpc/rawtransactions/joinpsbts/]]
* @see [[https://bitcoincore.org/en/doc/0.18.0/rpc/rawtransactions/utxoupdatepsbt/]]
*/
trait V18PsbtRpc {
trait PsbtRpc {
self: Client =>
def analyzePsbt(psbt: String): Future[AnalyzePsbtResult] = {
@ -28,4 +28,9 @@ trait V18PsbtRpc {
bitcoindCall[String]("utxoupdatepsbt", List(JsString(psbt)))
}
def utxoUpdatePsbt(psbt: String, descriptors: Seq[String]): Future[String] = {
bitcoindCall[String]("utxoupdatepsbt",
List(JsString(psbt), Json.toJson(descriptors)))
}
}

View File

@ -4,6 +4,7 @@ import org.bitcoins.core.crypto.DoubleSha256DigestBE
import org.bitcoins.core.currency.Bitcoins
import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.transaction.{Transaction, TransactionInput}
import org.bitcoins.rpc.client.common.BitcoindVersion._
import org.bitcoins.rpc.jsonmodels.{
FundRawTransactionResult,
GetRawTransactionResult,
@ -88,12 +89,23 @@ trait RawTransactionRpc { self: Client =>
bitcoindCall[Transaction]("getrawtransaction", params)
}
/**
* @param maxfeerate Set to 0 if you want to enable allowhighfees
*/
def sendRawTransaction(
transaction: Transaction,
allowHighFees: Boolean = false): Future[DoubleSha256DigestBE] = {
maxfeerate: Double = 0.10): Future[DoubleSha256DigestBE] = {
val feeParameter = self.version match {
case V19 | Experimental | Unknown =>
JsNumber(maxfeerate)
case V16 | V17 | V18 =>
JsBoolean(maxfeerate == 0)
}
bitcoindCall[DoubleSha256DigestBE](
"sendrawtransaction",
List(JsString(transaction.hex), JsBoolean(allowHighFees)))
List(JsString(transaction.hex), feeParameter))
}
}

View File

@ -127,6 +127,14 @@ object RpcOpts {
}
sealed trait WalletFlag
object WalletFlag {
case object AvoidReuse extends WalletFlag {
override def toString: String = "avoid_reuse"
}
}
sealed trait AddressType
object AddressType {

View File

@ -10,6 +10,7 @@ import org.bitcoins.core.currency.Bitcoins
import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.blockchain.MerkleBlock
import org.bitcoins.core.protocol.transaction.Transaction
import org.bitcoins.rpc.client.common.BitcoindVersion._
import org.bitcoins.rpc.client.common.RpcOpts.AddressType
import org.bitcoins.rpc.jsonmodels._
import org.bitcoins.rpc.serializers.JsonSerializers._
@ -185,13 +186,31 @@ trait WalletRpc { self: Client =>
List(JsString(currentPassphrase), JsString(newPassphrase)))
}
/**
*
* @param blank Not available to versions before v19
* @param passphrase Not available to versions before v19
* @return
*/
def createWallet(
walletName: String,
disablePrivateKeys: Boolean = false): Future[CreateWalletResult] = {
bitcoindCall[CreateWalletResult](
"createwallet",
List(JsString(walletName), Json.toJson(disablePrivateKeys)))
}
disablePrivateKeys: Boolean = false,
blank: Boolean = false,
passphrase: String = ""): Future[CreateWalletResult] =
self.version match {
case V19 | Experimental | Unknown =>
bitcoindCall[CreateWalletResult]("createwallet",
List(JsString(walletName),
JsBoolean(disablePrivateKeys),
JsBoolean(blank),
JsString(passphrase)))
case V16 | V17 | V18 =>
require(passphrase.isEmpty,
"passphrase should not be set for versions before v19")
bitcoindCall[CreateWalletResult](
"createwallet",
List(JsString(walletName), JsBoolean(disablePrivateKeys)))
}
def getAddressInfo(address: BitcoinAddress): Future[AddressInfoResult] = {
bitcoindCall[AddressInfoResult]("getaddressinfo",

View File

@ -1,16 +1,28 @@
package org.bitcoins.rpc.client.v18
import akka.actor.ActorSystem
import org.bitcoins.rpc.client.common.{
BitcoindRpcClient,
BitcoindVersion,
DescriptorRpc,
RpcOpts
}
import org.bitcoins.rpc.client.common.{BitcoindRpcClient, BitcoindVersion}
import org.bitcoins.rpc.client.common.{
BitcoindRpcClient,
BitcoindVersion,
PsbtRpc,
RpcOpts
}
import org.bitcoins.rpc.config.BitcoindInstance
import scala.util.Try
import org.bitcoins.core.protocol.transaction.Transaction
import org.bitcoins.rpc.client.common.RpcOpts
import org.bitcoins.core.crypto.ECPrivateKey
import org.bitcoins.core.script.crypto.HashType
import org.bitcoins.rpc.jsonmodels.SignRawTransactionResult
import play.api.libs.json.Json
import play.api.libs.json.JsString
import scala.concurrent.Future
import org.bitcoins.rpc.serializers.JsonSerializers._
import org.bitcoins.rpc.serializers.JsonWriters._
@ -24,8 +36,8 @@ class BitcoindV18RpcClient(override val instance: BitcoindInstance)(
implicit
actorSystem: ActorSystem)
extends BitcoindRpcClient(instance)
with V18PsbtRpc
with V18DescriptorRpc
with DescriptorRpc
with PsbtRpc
with V18AssortedRpc {
override lazy val version: BitcoindVersion = BitcoindVersion.V18
@ -83,7 +95,7 @@ object BitcoindV18RpcClient {
/**
* Creates an RPC client from the given instance,
* together with the given actor system. This is for
* advanced users, wher you need fine grained control
* advanced users, where you need fine grained control
* over the RPC client.
*/
def withActorSystem(instance: BitcoindInstance)(

View File

@ -0,0 +1,124 @@
package org.bitcoins.rpc.client.v19
import akka.actor.ActorSystem
import org.bitcoins.core.crypto.ECPrivateKey
import org.bitcoins.core.protocol.transaction.Transaction
import org.bitcoins.core.script.crypto.HashType
import org.bitcoins.rpc.client.common.RpcOpts.WalletFlag
import org.bitcoins.rpc.client.common.{
BitcoindRpcClient,
BitcoindVersion,
DescriptorRpc,
PsbtRpc,
RpcOpts
}
import org.bitcoins.rpc.config.BitcoindInstance
import org.bitcoins.rpc.jsonmodels.{
GetBalancesResult,
SetWalletFlagResult,
SignRawTransactionResult
}
import play.api.libs.json.Json
import play.api.libs.json.JsString
import org.bitcoins.rpc.serializers.JsonSerializers._
import org.bitcoins.rpc.serializers.JsonWriters._
import scala.concurrent.Future
import scala.util.Try
/**
* Class for creating a BitcoindV19 instance that can access RPCs
*/
class BitcoindV19RpcClient(override val instance: BitcoindInstance)(
implicit
actorSystem: ActorSystem)
extends BitcoindRpcClient(instance)
with DescriptorRpc
with PsbtRpc
with V19BlockFilterRpc {
override lazy val version: BitcoindVersion = BitcoindVersion.V19
/**
* $signRawTx
*
* This RPC call signs the raw transaction with keys found in
* the Bitcoin Core wallet.
*/
def signRawTransactionWithWallet(
transaction: Transaction,
utxoDeps: Vector[RpcOpts.SignRawTransactionOutputParameter] = Vector.empty,
sigHash: HashType = HashType.sigHashAll
): Future[SignRawTransactionResult] =
bitcoindCall[SignRawTransactionResult]("signrawtransactionwithwallet",
List(JsString(transaction.hex),
Json.toJson(utxoDeps),
Json.toJson(sigHash)))
/**
* $signRawTx
*
* This RPC call signs the raw transaction with keys provided
* manually.
*/
def signRawTransactionWithKey(
transaction: Transaction,
keys: Vector[ECPrivateKey],
utxoDeps: Vector[RpcOpts.SignRawTransactionOutputParameter] = Vector.empty,
sigHash: HashType = HashType.sigHashAll
): Future[SignRawTransactionResult] =
bitcoindCall[SignRawTransactionResult]("signrawtransactionwithkey",
List(JsString(transaction.hex),
Json.toJson(keys),
Json.toJson(utxoDeps),
Json.toJson(sigHash)))
/**
* Change the state of the given wallet flag for a wallet.
*/
def setWalletFlag(
flag: WalletFlag,
value: Boolean
): Future[SetWalletFlagResult] =
bitcoindCall[SetWalletFlagResult](
"setwalletflag",
List(JsString(flag.toString), Json.toJson(value)))
def getBalances: Future[GetBalancesResult] = {
bitcoindCall[GetBalancesResult]("getbalances")
}
}
object BitcoindV19RpcClient {
/**
* 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): BitcoindV19RpcClient = {
implicit val system =
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): BitcoindV19RpcClient =
new BitcoindV19RpcClient(instance)(system)
def fromUnknownVersion(
rpcClient: BitcoindRpcClient): Try[BitcoindV19RpcClient] =
Try {
new BitcoindV19RpcClient(rpcClient.instance)(rpcClient.system)
}
}

View File

@ -0,0 +1,44 @@
package org.bitcoins.rpc.client.v19
import org.bitcoins.core.crypto.DoubleSha256DigestBE
import org.bitcoins.core.gcs.{BlockFilter, FilterType}
import org.bitcoins.rpc.client.common.Client
import org.bitcoins.rpc.jsonmodels.GetBlockFilterResult
import org.bitcoins.rpc.serializers.JsonReaders.DoubleSha256DigestBEReads
import play.api.libs.json._
import scala.concurrent.Future
/**
* Gets the BIP158 filter for the specified block.
* This RPC is only enabled if block filters have been created using the -blockfilterindex configuration option
* @see [[https://bitcoincore.org/en/doc/0.19.0/rpc/blockchain/getblockfilter]]
*/
trait V19BlockFilterRpc {
self: Client =>
/**
* This is needed because we need the block hash to create a GolombFilter.
* We use an intermediary data type to hold our data so we can add the block hash
* we were given after the RPC call
*/
private case class TempBlockFilterResult(
filter: String,
header: DoubleSha256DigestBE)
implicit private val tempBlockFilterResultReads: Reads[
TempBlockFilterResult] = Json.reads[TempBlockFilterResult]
def getBlockFilter(
blockhash: DoubleSha256DigestBE,
filtertype: FilterType): Future[GetBlockFilterResult] = {
bitcoindCall[TempBlockFilterResult](
"getblockfilter",
List(JsString(blockhash.hex), JsString(filtertype.toString.toLowerCase)))
.map { tempBlockFilterResult =>
GetBlockFilterResult(
BlockFilter.fromHex(tempBlockFilterResult.filter, blockhash.flip),
tempBlockFilterResult.header)
}
}
}

View File

@ -59,6 +59,8 @@ sealed trait BitcoindInstance extends BitcoinSLogger {
BitcoindVersion.V17
case _: String if foundVersion.startsWith(BitcoindVersion.V18.toString) =>
BitcoindVersion.V18
case _: String if foundVersion.startsWith(BitcoindVersion.V19.toString) =>
BitcoindVersion.V19
case _: String => BitcoindVersion.Unknown
}
}

View File

@ -6,6 +6,7 @@ import org.bitcoins.core.number.{Int32, UInt32}
import org.bitcoins.core.protocol.blockchain.BlockHeader
import org.bitcoins.core.wallet.fee.BitcoinFeeUnit
import org.bitcoins.core.config.NetworkParameters
import org.bitcoins.core.gcs.GolombFilter
sealed abstract class BlockchainResult
@ -139,12 +140,30 @@ case class GetChainTxStatsResult(
time: UInt32,
txcount: Int,
window_block_count: Int,
window_final_block_height: Option[Int],
window_tx_count: Option[Int],
window_interval: Option[UInt32],
txrate: Option[BigDecimal])
extends BlockchainResult
case class GetMemPoolResult(
sealed trait GetMemPoolResult extends BlockchainResult {
def size: Int
def fee: Option[Bitcoins]
def modifiedfee: Option[Bitcoins]
def time: UInt32
def height: Int
def descendantcount: Int
def descendantsize: Int
def descendantfees: Option[Bitcoins]
def ancestorcount: Int
def ancestorsize: Int
def ancestorfees: Option[Bitcoins]
def wtxid: DoubleSha256DigestBE
def fees: FeeInfo
def depends: Vector[DoubleSha256DigestBE]
}
case class GetMemPoolResultPreV19(
size: Int,
fee: Option[Bitcoins],
modifiedfee: Option[Bitcoins],
@ -157,10 +176,54 @@ case class GetMemPoolResult(
ancestorsize: Int,
ancestorfees: Option[Bitcoins],
wtxid: DoubleSha256DigestBE,
fees: FeeInfo,
depends: Vector[DoubleSha256DigestBE])
extends BlockchainResult
extends GetMemPoolResult
case class GetMemPoolEntryResult(
case class GetMemPoolResultPostV19(
vsize: Int,
fee: Option[Bitcoins],
modifiedfee: Option[Bitcoins],
time: UInt32,
height: Int,
descendantcount: Int,
descendantsize: Int,
descendantfees: Option[Bitcoins],
ancestorcount: Int,
ancestorsize: Int,
ancestorfees: Option[Bitcoins],
wtxid: DoubleSha256DigestBE,
fees: FeeInfo,
depends: Vector[DoubleSha256DigestBE])
extends GetMemPoolResult {
override def size: Int = vsize
}
case class FeeInfo(
base: BitcoinFeeUnit,
modified: BitcoinFeeUnit,
ancestor: BitcoinFeeUnit,
descendant: BitcoinFeeUnit
)
sealed trait GetMemPoolEntryResult extends BlockchainResult {
def size: Int
def fee: Bitcoins
def modifiedfee: Bitcoins
def time: UInt32
def height: Int
def descendantcount: Int
def descendantsize: Int
def descendantfees: BitcoinFeeUnit
def ancestorcount: Int
def ancestorsize: Int
def ancestorfees: BitcoinFeeUnit
def wtxid: DoubleSha256DigestBE
def fees: FeeInfo
def depends: Option[Vector[DoubleSha256DigestBE]]
}
case class GetMemPoolEntryResultPreV19(
size: Int,
fee: Bitcoins,
modifiedfee: Bitcoins,
@ -168,12 +231,34 @@ case class GetMemPoolEntryResult(
height: Int,
descendantcount: Int,
descendantsize: Int,
descendantfees: Bitcoins, // Should be BitcoinFeeUnit
descendantfees: BitcoinFeeUnit,
ancestorcount: Int,
ancestorsize: Int,
ancestorfees: Bitcoins, // Should be BitcoinFeeUnit
ancestorfees: BitcoinFeeUnit,
wtxid: DoubleSha256DigestBE,
fees: FeeInfo,
depends: Option[Vector[DoubleSha256DigestBE]])
extends BlockchainResult
extends GetMemPoolEntryResult
case class GetMemPoolEntryResultPostV19(
vsize: Int,
fee: Bitcoins,
weight: Int,
modifiedfee: Bitcoins,
time: UInt32,
height: Int,
descendantcount: Int,
descendantsize: Int,
descendantfees: BitcoinFeeUnit,
ancestorcount: Int,
ancestorsize: Int,
ancestorfees: BitcoinFeeUnit,
wtxid: DoubleSha256DigestBE,
fees: FeeInfo,
depends: Option[Vector[DoubleSha256DigestBE]])
extends GetMemPoolEntryResult {
override def size: Int = vsize
}
case class GetMemPoolInfoResult(
size: Int,
@ -202,3 +287,8 @@ case class GetTxOutSetInfoResult(
disk_size: Int,
total_amount: Bitcoins)
extends BlockchainResult
case class GetBlockFilterResult(
filter: GolombFilter,
header: DoubleSha256DigestBE)
extends BlockchainResult

View File

@ -4,6 +4,7 @@ import java.net.URI
import org.bitcoins.core.currency.Bitcoins
import org.bitcoins.core.number.{UInt32, UInt64}
import org.bitcoins.core.p2p.ServiceIdentifier
import org.bitcoins.core.wallet.fee.SatoshisPerKiloByte
import scala.concurrent.duration.FiniteDuration
@ -39,6 +40,7 @@ case class GetNetworkInfoResult(
subversion: String,
protocolversion: Int,
localservices: String,
localservicesnames: Option[Vector[ServiceIdentifier]],
localrelay: Boolean,
timeoffset: Int,
networkactive: Boolean,
@ -84,6 +86,7 @@ case class PeerNetworkInfo(
addrbind: URI,
addrlocal: Option[URI],
services: String,
servicesnames: Option[Vector[ServiceIdentifier]],
relaytxes: Boolean,
lastsend: UInt32,
lastrecv: UInt32,

View File

@ -164,6 +164,7 @@ final case class DeriveAddressesResult(addresses: Vector[BitcoinAddress])
final case class GetDescriptorInfoResult(
descriptor: String,
checksum: Option[String],
isrange: Boolean,
issolvable: Boolean,
hasprivatekeys: Boolean

View File

@ -50,6 +50,20 @@ case class GetTransactionResult(
hex: Transaction)
extends WalletResult
case class SetWalletFlagResult(
flag_name: String,
flag_state: Boolean,
warnings: Option[String])
extends WalletResult
case class GetBalancesResult(mine: BalanceInfo, watchonly: Option[BalanceInfo])
extends WalletResult
case class BalanceInfo(
trusted: Bitcoins,
untrusted_pending: Bitcoins,
immature: Bitcoins)
case class TransactionDetails(
involvesWatchonly: Option[Boolean],
account: Option[String],
@ -102,7 +116,7 @@ case class RescanBlockChainResult(start_height: Int, stop_height: Int)
case class ReceivedAddress(
involvesWatchonly: Option[Boolean],
address: BitcoinAddress,
account: String,
account: Option[String],
amount: Bitcoins,
confirmations: Int,
label: String,
@ -186,7 +200,8 @@ case class UnspentOutput(
amount: Bitcoins,
confirmations: Int,
spendable: Boolean,
solvable: Boolean)
solvable: Boolean,
reused: Option[Boolean])
extends WalletResult
case class AddressInfoResult(

View File

@ -200,11 +200,21 @@ object JsonSerializers {
implicit val getChainTxStatsResultReads: Reads[GetChainTxStatsResult] =
Json.reads[GetChainTxStatsResult]
implicit val getMemPoolResultReads: Reads[GetMemPoolResult] =
Json.reads[GetMemPoolResult]
implicit val feeInfoReads: Reads[FeeInfo] = Json.reads[FeeInfo]
implicit val getMemPoolEntryResultReads: Reads[GetMemPoolEntryResult] =
Json.reads[GetMemPoolEntryResult]
implicit val getMemPoolResultPreV19Reads: Reads[GetMemPoolResultPreV19] =
Json.reads[GetMemPoolResultPreV19]
implicit val getMemPoolResultPostV19Reads: Reads[GetMemPoolResultPostV19] =
Json.reads[GetMemPoolResultPostV19]
implicit val getMemPoolEntryResultPreV19Reads: Reads[
GetMemPoolEntryResultPreV19] =
Json.reads[GetMemPoolEntryResultPreV19]
implicit val getMemPoolEntryResultPostV19Reads: Reads[
GetMemPoolEntryResultPostV19] =
Json.reads[GetMemPoolEntryResultPostV19]
implicit val getMemPoolInfoResultReads: Reads[GetMemPoolInfoResult] =
Json.reads[GetMemPoolInfoResult]
@ -230,6 +240,13 @@ object JsonSerializers {
implicit val bumpFeeReads: Reads[BumpFeeResult] = Json.reads[BumpFeeResult]
implicit val setWalletFlagResultReads: Reads[SetWalletFlagResult] =
Json.reads[SetWalletFlagResult]
implicit val balanceInfoReads: Reads[BalanceInfo] = Json.reads[BalanceInfo]
implicit val getBalancesResultReads: Reads[GetBalancesResult] =
Json.reads[GetBalancesResult]
implicit val TransactionDetailsReads: Reads[TransactionDetails] =
Json.reads[TransactionDetails]
implicit val getTransactionResultReads: Reads[GetTransactionResult] =
@ -443,14 +460,24 @@ object JsonSerializers {
Json.reads[CreateWalletResult]
// Map stuff
implicit def mapDoubleSha256DigestReads: Reads[
Map[DoubleSha256Digest, GetMemPoolResult]] =
Reads.mapReads[DoubleSha256Digest, GetMemPoolResult](s =>
implicit def mapDoubleSha256DigestReadsPreV19: Reads[
Map[DoubleSha256Digest, GetMemPoolResultPreV19]] =
Reads.mapReads[DoubleSha256Digest, GetMemPoolResultPreV19](s =>
JsSuccess(DoubleSha256Digest.fromHex(s)))
implicit def mapDoubleSha256DigestBEReads: Reads[
Map[DoubleSha256DigestBE, GetMemPoolResult]] =
Reads.mapReads[DoubleSha256DigestBE, GetMemPoolResult](s =>
implicit def mapDoubleSha256DigestReadsPostV19: Reads[
Map[DoubleSha256Digest, GetMemPoolResultPostV19]] =
Reads.mapReads[DoubleSha256Digest, GetMemPoolResultPostV19](s =>
JsSuccess(DoubleSha256Digest.fromHex(s)))
implicit def mapDoubleSha256DigestBEReadsPreV19: Reads[
Map[DoubleSha256DigestBE, GetMemPoolResultPreV19]] =
Reads.mapReads[DoubleSha256DigestBE, GetMemPoolResultPreV19](s =>
JsSuccess(DoubleSha256DigestBE.fromHex(s)))
implicit def mapDoubleSha256DigestBEReadsPostV19: Reads[
Map[DoubleSha256DigestBE, GetMemPoolResultPostV19]] =
Reads.mapReads[DoubleSha256DigestBE, GetMemPoolResultPostV19](s =>
JsSuccess(DoubleSha256DigestBE.fromHex(s)))
implicit def mapAddressesByLabelReads: Reads[

View File

@ -173,6 +173,19 @@ object ServiceIdentifier extends Factory[ServiceIdentifier] {
def fromBytes(bytes: ByteVector): ServiceIdentifier =
RawServiceIdentifierSerializer.read(bytes)
def fromString(string: String): ServiceIdentifier = string match {
case "NETWORK" => NODE_NETWORK
case "NETWORK_LIMITED" => NODE_NETWORK_LIMITED
case "WITNESS" => NODE_WITNESS
case "BLOOM" => NODE_BLOOM
case "GETUTXO" => NODE_GET_UTXO
case "COMPACT_FILTERS" => NODE_COMPACT_FILTERS
case "XTHIN" => NODE_XTHIN
case _: String =>
throw new IllegalArgumentException(
s""""$string" does not represent a ServiceIdentifier""")
}
def apply(num: BigInt): ServiceIdentifier = ServiceIdentifier(UInt64(num))
def apply(uInt64: UInt64): ServiceIdentifier = ServiceIdentifierImpl(uInt64)

View File

@ -16,6 +16,7 @@ import org.bitcoins.node.networking.peer.{
PeerMessageReceiverState,
PeerMessageSender
}
import org.bitcoins.rpc.client.common.BitcoindVersion.V18
import org.bitcoins.rpc.client.common.{BitcoindRpcClient, BitcoindVersion}
import org.bitcoins.server.BitcoinSAppConfig
import org.bitcoins.server.BitcoinSAppConfig._
@ -110,8 +111,9 @@ trait NodeUnitTest extends BitcoinSFixture {
makeDependentFixture(
build = () =>
NodeUnitTest.createSpvNodeFundedWalletBitcoind(callbacks)(system,
appConfig),
NodeUnitTest.createSpvNodeFundedWalletBitcoind(callbacks, Option(V18))(
system, // Force V18 because Spv is disabled on versions after
appConfig),
destroy = NodeUnitTest.destroyNodeFundedWalletBitcoind(
_: NodeFundedWalletBitcoind)(system, appConfig)
)(test)

View File

@ -40,6 +40,7 @@ import org.bitcoins.rpc.client.common.{
import org.bitcoins.rpc.client.v16.BitcoindV16RpcClient
import org.bitcoins.rpc.client.v17.BitcoindV17RpcClient
import org.bitcoins.rpc.client.v18.BitcoindV18RpcClient
import org.bitcoins.rpc.client.v19.BitcoindV19RpcClient
import org.bitcoins.rpc.config.{
BitcoindAuthCredentials,
BitcoindConfig,
@ -115,6 +116,7 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
|port=${uri.getPort}
|debug=1
|walletbroadcast=1
|peerbloomfilters=1
|txindex=${if (pruneMode) 0 else 1 /* pruning and txindex are not compatible */}
|zmqpubhashtx=tcp://127.0.0.1:$zmqPort
|zmqpubhashblock=tcp://127.0.0.1:$zmqPort
@ -180,7 +182,7 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
def getBinary(version: BitcoindVersion): File = version match {
// default to newest version
case Unknown => getBinary(BitcoindVersion.newest)
case known @ (Experimental | V16 | V17 | V18) =>
case known @ (Experimental | V16 | V17 | V18 | V19) =>
val fileList = Files
.list(binaryDirectory)
.iterator()
@ -223,12 +225,14 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
versionOpt: Option[BitcoindVersion] = None): BitcoindInstance = {
val uri = new URI("http://localhost:" + port)
val rpcUri = new URI("http://localhost:" + rpcPort)
val configFile = writtenConfig(
uri,
rpcUri,
zmqPort,
pruneMode,
blockFilterIndex = versionOpt.contains(BitcoindVersion.Experimental))
val hasNeutrinoSupport = versionOpt.contains(BitcoindVersion.V19) || versionOpt
.contains(BitcoindVersion.Experimental)
val configFile =
writtenConfig(uri,
rpcUri,
zmqPort,
pruneMode,
blockFilterIndex = hasNeutrinoSupport)
val conf = BitcoindConfig(configFile)
val auth = BitcoindAuthCredentials.fromConfig(conf)
val binary: File = versionOpt match {
@ -292,6 +296,18 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
pruneMode = pruneMode,
versionOpt = Some(BitcoindVersion.V18))
def v19Instance(
port: Int = RpcUtil.randomPort,
rpcPort: Int = RpcUtil.randomPort,
zmqPort: Int = RpcUtil.randomPort,
pruneMode: Boolean = false
): BitcoindInstance =
instance(port = port,
rpcPort = rpcPort,
zmqPort = zmqPort,
pruneMode = pruneMode,
versionOpt = Some(BitcoindVersion.V19))
def vExperimentalInstance(
port: Int = RpcUtil.randomPort,
rpcPort: Int = RpcUtil.randomPort,
@ -595,8 +611,11 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
case BitcoindVersion.V18 =>
BitcoindV18RpcClient.withActorSystem(
BitcoindRpcTestUtil.v18Instance())
case BitcoindVersion.V19 =>
BitcoindV19RpcClient.withActorSystem(
BitcoindRpcTestUtil.v19Instance())
case BitcoindVersion.Experimental =>
BitcoindV18RpcClient.withActorSystem(
BitcoindV19RpcClient.withActorSystem(
BitcoindRpcTestUtil.vExperimentalInstance())
}
@ -672,6 +691,15 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
(BitcoindV18RpcClient, BitcoindV18RpcClient)] =
createNodePairInternal(BitcoindVersion.V18, clientAccum)
/**
* Returns a pair of [[org.bitcoins.rpc.client.v19.BitcoindV19RpcClient BitcoindV19RpcClient]]
* that are connected with some blocks in the chain
*/
def createNodePairV19(clientAccum: RpcClientAccum = Vector.newBuilder)(
implicit system: ActorSystem): Future[
(BitcoindV19RpcClient, BitcoindV19RpcClient)] =
createNodePairInternal(BitcoindVersion.V19, clientAccum)
/**
* Returns a triple of [[org.bitcoins.rpc.client.common.BitcoindRpcClient BitcoindRpcClient]]
* that are connected with some blocks in the chain
@ -718,6 +746,13 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
createNodeTripleInternal(BitcoindVersion.V18, clientAccum)
}
def createNodeTripleV19(
clientAccum: RpcClientAccum = Vector.newBuilder
)(implicit system: ActorSystem): Future[
(BitcoindV19RpcClient, BitcoindV19RpcClient, BitcoindV19RpcClient)] = {
createNodeTripleInternal(BitcoindVersion.V19, clientAccum)
}
def createRawCoinbaseTransaction(
sender: BitcoindRpcClient,
receiver: BitcoindRpcClient,
@ -769,17 +804,20 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
val v16T = BitcoindV16RpcClient.fromUnknownVersion(unknown)
val v17T = BitcoindV17RpcClient.fromUnknownVersion(unknown)
val v18T = BitcoindV18RpcClient.fromUnknownVersion(unknown)
(v16T, v17T, v18T) match {
case (Failure(_), Failure(_), Failure(_)) =>
val v19T = BitcoindV19RpcClient.fromUnknownVersion(unknown)
(v16T, v17T, v18T, v19T) match {
case (Failure(_), Failure(_), Failure(_), Failure(_)) =>
throw new RuntimeException(
"Could not figure out version of provided bitcoind RPC client!" +
"This should not happen, managed to construct different versioned RPC clients from one single client")
case (Success(v16), _, _) =>
case (Success(v16), _, _, _) =>
v16.signRawTransaction(transaction, utxoDeps)
case (_, Success(v17), _) =>
case (_, Success(v17), _, _) =>
v17.signRawTransactionWithWallet(transaction, utxoDeps)
case (_, _, Success(v18)) =>
case (_, _, Success(v18), _) =>
v18.signRawTransactionWithWallet(transaction, utxoDeps)
case (_, _, _, Success(v19)) =>
v19.signRawTransactionWithWallet(transaction, utxoDeps)
}
}
@ -820,8 +858,7 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
addr <- sender.getNewAddress
_ <- sender.generateToAddress(100, addr)
// Can't spend coinbase until depth 100
transactionHash <- sender.sendRawTransaction(signedtx.hex,
allowHighFees = true)
transactionHash <- sender.sendRawTransaction(signedtx.hex, maxfeerate = 0)
transaction <- sender.getTransaction(transactionHash)
} yield transaction
}