Bitcoind v0.20 updated rpcs & tests (#2061)

* Bitcoind v0.20 updated rpcs & tests

* Test sorted multi
This commit is contained in:
Ben Carman 2020-09-28 14:25:28 -05:00 committed by GitHub
parent 77ba9fd8b9
commit 920e30e5a2
11 changed files with 372 additions and 18 deletions

View File

@ -1,5 +1,7 @@
package org.bitcoins.commons.jsonmodels.bitcoind
import java.nio.file.Path
import org.bitcoins.core.config.NetworkParameters
import org.bitcoins.core.currency.Bitcoins
import org.bitcoins.core.gcs.GolombFilter
@ -10,6 +12,13 @@ import org.bitcoins.crypto.DoubleSha256DigestBE
sealed abstract class BlockchainResult
case class DumpTxOutSetResult(
coins_written: Int,
base_hash: DoubleSha256DigestBE,
base_height: Int,
path: Path)
extends BlockchainResult
case class GetBlockResult(
hash: DoubleSha256DigestBE,
confirmations: Int,

View File

@ -21,8 +21,21 @@ import org.bitcoins.crypto.{
sealed abstract class WalletResult
case class MultiSigResult(address: BitcoinAddress, redeemScript: ScriptPubKey)
extends WalletResult
trait MultiSigResult extends WalletResult {
def address: BitcoinAddress
def redeemScript: ScriptPubKey
}
case class MultiSigResultPreV20(
address: BitcoinAddress,
redeemScript: ScriptPubKey)
extends MultiSigResult
case class MultiSigResultPostV20(
address: BitcoinAddress,
redeemScript: ScriptPubKey,
descriptor: String)
extends MultiSigResult
case class BumpFeeResult(
txid: DoubleSha256DigestBE,

View File

@ -2,6 +2,7 @@ package org.bitcoins.commons.serializers
import java.io.File
import java.net.{InetAddress, InetSocketAddress, URI}
import java.nio.file.Path
import java.time._
import java.util.UUID
@ -607,6 +608,12 @@ object JsonReaders {
SerializerUtil.processJsString[File](new File(_))(json)
}
implicit object PathReads extends Reads[Path] {
override def reads(json: JsValue): JsResult[Path] =
SerializerUtil.processJsString[Path](new File(_).toPath)(json)
}
implicit object URIReads extends Reads[URI] {
override def reads(json: JsValue): JsResult[URI] =

View File

@ -211,6 +211,9 @@ object JsonSerializers {
Json.reads[NodeBanPreV20]
// Blockchain Models
implicit val dumpTxOutSetResultReads: Reads[DumpTxOutSetResult] =
Json.reads[DumpTxOutSetResult]
implicit val getBlockResultReads: Reads[GetBlockResult] =
Json.reads[GetBlockResult]
@ -288,8 +291,11 @@ object JsonSerializers {
}
// Wallet Models
implicit val multiSigReads: Reads[MultiSigResult] =
Json.reads[MultiSigResult]
implicit val multiSigPreV20Reads: Reads[MultiSigResultPreV20] =
Json.reads[MultiSigResultPreV20]
implicit val multiSigPostV20Reads: Reads[MultiSigResultPostV20] =
Json.reads[MultiSigResultPostV20]
implicit val bumpFeeReads: Reads[BumpFeeResult] = Json.reads[BumpFeeResult]

View File

@ -0,0 +1,176 @@
package org.bitcoins.rpc.v20
import java.io.File
import java.nio.file.Files
import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts.WalletFlag
import org.bitcoins.commons.jsonmodels.bitcoind._
import org.bitcoins.core.config.RegTest
import org.bitcoins.core.gcs.{BlockFilter, FilterType}
import org.bitcoins.core.protocol.transaction.EmptyTransaction
import org.bitcoins.crypto.ECPublicKey
import org.bitcoins.rpc.client.common.BitcoindVersion
import org.bitcoins.rpc.client.v20.BitcoindV20RpcClient
import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil
import org.bitcoins.testkit.util.BitcoindRpcTest
import scala.concurrent.Future
class BitcoindV20RpcClientTest extends BitcoindRpcTest {
lazy val clientPairF: Future[(BitcoindV20RpcClient, BitcoindV20RpcClient)] =
BitcoindRpcTestUtil.createNodePairV20(clientAccum)
lazy val clientF: Future[BitcoindV20RpcClient] = clientPairF.map(_._1)
clientF.foreach(c => clientAccum.+=(c))
behavior of "BitcoindV20RpcClient"
it should "be able to start a V20 bitcoind instance" in {
clientF.map { client =>
assert(client.version == BitcoindVersion.V20)
}
}
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 get blockchain info" in {
for {
(client, _) <- clientPairF
info <- client.getBlockChainInfo
bestHash <- client.getBestBlockHash
} yield {
assert(info.isInstanceOf[GetBlockChainInfoResultPostV19])
val preV19Info = info.asInstanceOf[GetBlockChainInfoResultPostV19]
assert(preV19Info.chain == RegTest)
assert(preV19Info.softforks.size >= 5)
assert(
preV19Info.softforks.values.exists(_.isInstanceOf[Bip9SoftforkPostV19]))
assert(preV19Info.bestblockhash == bestHash)
}
}
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=="
for {
(client, _) <- clientPairF
result <- client.utxoUpdatePsbt(psbt, Seq(descriptor))
} yield {
assert(result.contains(psbt))
}
}
it should "correct create multisig and get its descriptor" in {
val pubKey1 = ECPublicKey.freshPublicKey
val pubKey2 = ECPublicKey.freshPublicKey
for {
client <- clientF
multiSigResult <- client.createMultiSig(2, Vector(pubKey1, pubKey2))
} yield {
// just validate we are able to receive a sane descriptor
// no need to check checksum
assert(
multiSigResult.descriptor.startsWith(
s"sh(multi(2,${pubKey1.hex},${pubKey2.hex}))#"))
}
}
it should "correctly dump tx out set" in {
for {
client <- clientF
hash <- client.getBestBlockHash
height <- client.getBestHashBlockHeight()
result <- client.dumpTxOutSet(new File("utxo.dat").toPath)
} 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)
}
}
it should "correct generate to a descriptor" in {
// 2-of-2 multisig descriptor
val descriptor =
"sh(sortedmulti(2,023f720438186fbdfde0c0a403e770a0f32a2d198623a8a982c47b621f8b307640,03ed261094d609d5e02ba6553c2d91e4fd056006ce2fe64aace72b69cb5be3ab9c))#nj9wx7up"
val numBlocks = 10
for {
client <- clientF
hashes <- client.generateToDescriptor(numBlocks, descriptor)
} yield assert(hashes.size == numBlocks)
}
}

View File

@ -1,11 +1,16 @@
package org.bitcoins.rpc.client.common
import org.bitcoins.commons.jsonmodels.bitcoind.MultiSigResult
import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts.AddressType
import org.bitcoins.commons.jsonmodels.bitcoind.{
MultiSigResult,
MultiSigResultPostV20,
MultiSigResultPreV20
}
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.BitcoindVersion._
import play.api.libs.json.{JsArray, JsNumber, JsString, Json}
import scala.concurrent.Future
@ -35,7 +40,12 @@ trait MultisigRpc { self: Client =>
JsArray(keys.map(keyToString)),
JsString(account)) ++ addressType.map(Json.toJson(_)).toList
bitcoindCall[MultiSigResult]("addmultisigaddress", params)
self.version match {
case V20 | Unknown =>
bitcoindCall[MultiSigResultPostV20]("addmultisigaddress", params)
case V16 | V17 | V18 | V19 | Experimental =>
bitcoindCall[MultiSigResultPreV20]("addmultisigaddress", params)
}
}
def addMultiSigAddress(
@ -65,9 +75,15 @@ trait MultisigRpc { self: Client =>
def createMultiSig(
minSignatures: Int,
keys: Vector[ECPublicKey]): Future[MultiSigResult] = {
bitcoindCall[MultiSigResult](
"createmultisig",
List(JsNumber(minSignatures), Json.toJson(keys.map(_.hex))))
self.version match {
case V20 | Unknown =>
bitcoindCall[MultiSigResultPostV20](
"createmultisig",
List(JsNumber(minSignatures), Json.toJson(keys.map(_.hex))))
case V16 | V17 | V18 | V19 | Experimental =>
bitcoindCall[MultiSigResultPreV20](
"createmultisig",
List(JsNumber(minSignatures), Json.toJson(keys.map(_.hex))))
}
}
}

View File

@ -2,12 +2,7 @@ package org.bitcoins.rpc.client.v20
import akka.actor.ActorSystem
import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts.WalletFlag
import org.bitcoins.commons.jsonmodels.bitcoind.{
GetBalancesResult,
RpcOpts,
SetWalletFlagResult,
SignRawTransactionResult
}
import org.bitcoins.commons.jsonmodels.bitcoind._
import org.bitcoins.commons.serializers.JsonSerializers._
import org.bitcoins.commons.serializers.JsonWriters._
import org.bitcoins.core.api.chain.ChainQueryApi
@ -38,7 +33,9 @@ class BitcoindV20RpcClient(override val instance: BitcoindInstance)(implicit
extends BitcoindRpcClient(instance)
with DescriptorRpc
with PsbtRpc
with V19BlockFilterRpc {
with V19BlockFilterRpc
with V20MultisigRpc
with V20AssortedRpc {
override def getFiltersBetweenHeights(
startHeight: Int,

View File

@ -0,0 +1,31 @@
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

@ -0,0 +1,87 @@
package org.bitcoins.rpc.client.v20
import org.bitcoins.commons.jsonmodels.bitcoind.MultiSigResultPostV20
import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts.AddressType
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.BitcoindVersion._
import org.bitcoins.rpc.client.common.{Client, MultisigRpc}
import play.api.libs.json.{JsArray, JsNumber, JsString, Json}
import scala.concurrent.Future
/**
* This trait defines RPC calls related to
* multisignature functionality in Bitcoin Core.
*
* @see [[https://en.bitcoin.it/wiki/Multisignature Bitcoin Wiki]]
* article on multisignature.
*/
trait V20MultisigRpc extends MultisigRpc { self: Client =>
private def addMultiSigAddress(
minSignatures: Int,
keys: Vector[Either[ECPublicKey, P2PKHAddress]],
account: String = "",
addressType: Option[AddressType]): Future[MultiSigResultPostV20] = {
def keyToString(key: Either[ECPublicKey, P2PKHAddress]): JsString =
key match {
case Right(k) => JsString(k.value)
case Left(k) => JsString(k.hex)
}
val params =
List(JsNumber(minSignatures),
JsArray(keys.map(keyToString)),
JsString(account)) ++ addressType.map(Json.toJson(_)).toList
self.version match {
case V20 | Unknown =>
bitcoindCall[MultiSigResultPostV20]("addmultisigaddress", params)
case version @ (V16 | V17 | V18 | V19 | Experimental) =>
throw new RuntimeException(
s"Cannot use v20MultisigRpc on an older version, got $version")
}
}
override def addMultiSigAddress(
minSignatures: Int,
keys: Vector[Either[ECPublicKey, P2PKHAddress]]): Future[
MultiSigResultPostV20] =
addMultiSigAddress(minSignatures, keys, addressType = None)
override def addMultiSigAddress(
minSignatures: Int,
keys: Vector[Either[ECPublicKey, P2PKHAddress]],
account: String): Future[MultiSigResultPostV20] =
addMultiSigAddress(minSignatures, keys, account, None)
override def addMultiSigAddress(
minSignatures: Int,
keys: Vector[Either[ECPublicKey, P2PKHAddress]],
addressType: AddressType): Future[MultiSigResultPostV20] =
addMultiSigAddress(minSignatures, keys, addressType = Some(addressType))
override def addMultiSigAddress(
minSignatures: Int,
keys: Vector[Either[ECPublicKey, P2PKHAddress]],
account: String,
addressType: AddressType): Future[MultiSigResultPostV20] =
addMultiSigAddress(minSignatures, keys, account, Some(addressType))
override def createMultiSig(
minSignatures: Int,
keys: Vector[ECPublicKey]): Future[MultiSigResultPostV20] = {
self.version match {
case V20 | Unknown =>
bitcoindCall[MultiSigResultPostV20](
"createmultisig",
List(JsNumber(minSignatures), Json.toJson(keys.map(_.hex))))
case version @ (V16 | V17 | V18 | V19 | Experimental) =>
throw new RuntimeException(
s"Cannot use v20MultisigRpc on an older version, got $version")
}
}
}

View File

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

View File

@ -36,6 +36,7 @@ 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.client.v20.BitcoindV20RpcClient
import org.bitcoins.rpc.config.{
BitcoindAuthCredentials,
BitcoindConfig,
@ -606,7 +607,7 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
BitcoindV19RpcClient.withActorSystem(
BitcoindRpcTestUtil.v19Instance())
case BitcoindVersion.V20 =>
BitcoindV19RpcClient.withActorSystem(
BitcoindV20RpcClient.withActorSystem(
BitcoindRpcTestUtil.v20Instance())
case BitcoindVersion.Experimental =>
BitcoindV19RpcClient.withActorSystem(
@ -693,6 +694,15 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
system: ActorSystem): Future[(BitcoindV19RpcClient, BitcoindV19RpcClient)] =
createNodePairInternal(BitcoindVersion.V19, clientAccum)
/**
* Returns a pair of [[org.bitcoins.rpc.client.v20.BitcoindV20RpcClient BitcoindV20RpcClient]]
* that are connected with some blocks in the chain
*/
def createNodePairV20(
clientAccum: RpcClientAccum = Vector.newBuilder)(implicit
system: ActorSystem): Future[(BitcoindV20RpcClient, BitcoindV20RpcClient)] =
createNodePairInternal(BitcoindVersion.V20, clientAccum)
/**
* Returns a triple of [[org.bitcoins.rpc.client.common.BitcoindRpcClient BitcoindRpcClient]]
* that are connected with some blocks in the chain