Bitcoind v0.21.0 support (#2414)

* Bitcoind v0.21.0rc3 support

* Use v0.21.0 instead of Experimental in node tests

* Bump to rc5

* Use official release

* Fix rebase issues

* Remove DoNotDiscover tag

* Fix count for NeutrinoNodeTest
This commit is contained in:
benthecarman 2021-01-14 14:01:44 -06:00 committed by GitHub
parent 6426158a1c
commit d69c60b8b2
30 changed files with 929 additions and 252 deletions

View File

@ -34,7 +34,24 @@ case class NetTarget(
time_left_in_cycle: UInt32)
extends NetworkResult
case class GetNetworkInfoResult(
trait GetNetworkInfoResult extends NetworkResult {
def version: Int
def subversion: String
def protocolversion: Int
def localservices: String
def localservicesnames: Option[Vector[ServiceIdentifier]]
def localrelay: Boolean
def timeoffset: Int
def networkactive: Boolean
def connections: Int
def networks: Vector[Network]
def relayfee: Bitcoins
def incrementalfee: Bitcoins
def localadresses: Option[Vector[NetworkAddress]]
def warnings: String
}
case class GetNetworkInfoResultPreV21(
version: Int,
subversion: String,
protocolversion: Int,
@ -49,7 +66,26 @@ case class GetNetworkInfoResult(
incrementalfee: Bitcoins,
localadresses: Option[Vector[NetworkAddress]],
warnings: String)
extends NetworkResult
extends GetNetworkInfoResult
case class GetNetworkInfoResultPostV21(
version: Int,
subversion: String,
protocolversion: Int,
localservices: String,
localservicesnames: Option[Vector[ServiceIdentifier]],
localrelay: Boolean,
timeoffset: Int,
networkactive: Boolean,
connections: Int,
connections_in: Int,
connections_out: Int,
networks: Vector[Network],
relayfee: Bitcoins,
incrementalfee: Bitcoins,
localadresses: Option[Vector[NetworkAddress]],
warnings: String)
extends GetNetworkInfoResult
case class Network(
name: String,
@ -73,7 +109,6 @@ sealed trait Peer extends NetworkResult {
def synced_headers: Int
def synced_blocks: Int
def inflight: Vector[Int]
def whitelisted: Boolean
def bytessent_per_msg: Map[String, Int]
def bytesrecv_per_msg: Map[String, Int]
def minfeefilter: Option[SatoshisPerKiloByte]
@ -81,7 +116,7 @@ sealed trait Peer extends NetworkResult {
case class PeerPreV20(
id: Int,
networkInfo: PeerNetworkInfo,
networkInfo: PeerNetworkInfoPreV21,
version: Int,
subver: String,
inbound: Boolean,
@ -97,9 +132,9 @@ case class PeerPreV20(
minfeefilter: Option[SatoshisPerKiloByte])
extends Peer
case class PeerPostV20(
case class PeerV20(
id: Int,
networkInfo: PeerNetworkInfo,
networkInfo: PeerNetworkInfoPreV21,
version: Int,
subver: String,
inbound: Boolean,
@ -114,7 +149,43 @@ case class PeerPostV20(
minfeefilter: Option[SatoshisPerKiloByte])
extends Peer
case class PeerNetworkInfo(
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"
}
trait PeerNetworkInfo extends NetworkResult {
def addr: URI
def addrbind: URI
def addrlocal: Option[URI]
def services: String
def servicesnames: Option[Vector[ServiceIdentifier]]
def relaytxes: Boolean
def lastsend: UInt32
def lastrecv: UInt32
def bytessent: Int
def bytesrecv: Int
def conntime: UInt32
def timeoffset: Int
def pingtime: Option[BigDecimal]
def minping: Option[BigDecimal]
def pingwait: Option[BigDecimal]
}
case class PeerNetworkInfoPreV21(
addr: URI,
addrbind: URI,
addrlocal: Option[URI],
@ -130,7 +201,29 @@ case class PeerNetworkInfo(
pingtime: Option[BigDecimal],
minping: Option[BigDecimal],
pingwait: Option[BigDecimal])
extends NetworkResult
extends PeerNetworkInfo
case class PeerNetworkInfoPostV21(
addr: URI,
addrbind: URI,
addrlocal: Option[URI],
network: String,
mapped_as: Option[Int],
services: String,
servicesnames: Option[Vector[ServiceIdentifier]],
relaytxes: Boolean,
lastsend: UInt32,
lastrecv: UInt32,
last_transaction: UInt32,
last_block: UInt32,
bytessent: Int,
bytesrecv: Int,
conntime: UInt32,
timeoffset: Int,
pingtime: Option[BigDecimal],
minping: Option[BigDecimal],
pingwait: Option[BigDecimal])
extends PeerNetworkInfo
trait NodeBan extends NetworkResult {
def address: URI

View File

@ -175,3 +175,6 @@ final case class GetDescriptorInfoResult(
) extends OtherResult
final case class SubmitHeaderResult(header: BlockHeader) extends OtherResult
case class IndexInfoResult(synced: Boolean, best_block_height: Int)
extends OtherResult

View File

@ -1,8 +1,5 @@
package org.bitcoins.commons.jsonmodels.bitcoind
import java.io.File
import java.time.ZonedDateTime
import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts.LabelPurpose
import org.bitcoins.core.currency.Bitcoins
import org.bitcoins.core.hd.BIP32Path
@ -19,6 +16,9 @@ import org.bitcoins.crypto.{
Sha256Hash160Digest
}
import java.io.File
import java.time.ZonedDateTime
sealed abstract class WalletResult
trait MultiSigResult extends WalletResult {
@ -239,7 +239,6 @@ sealed trait AddressInfoResult extends WalletResult {
def timestamp: Option[ZonedDateTime]
def hdkeypath: Option[BIP32Path]
def hdseedid: Option[RipeMd160Digest]
def labels: Vector[LabelResult]
}
case class AddressInfoResultPreV18(
@ -353,6 +352,90 @@ object AddressInfoResultPostV18 {
}
}
case class AddressInfoResultPostV21(
address: BitcoinAddress,
scriptPubKey: ScriptPubKey,
isProps: AddressInfoResultPostV21.AddressInfoIsProps,
desc: String,
witness_version: Option[WitnessVersion],
witness_program: Option[String],
script: Option[ScriptType],
hex: Option[ScriptPubKey],
pubkeys: Option[Vector[ECPublicKey]],
sigsrequired: Option[Int],
pubkey: Option[ECPublicKey],
embedded: Option[EmbeddedResult],
ischange: Boolean,
timestamp: Option[ZonedDateTime],
hdkeypath: Option[BIP32Path],
hdseedid: Option[RipeMd160Digest],
hdmasterfingerprint: Option[String],
labels: Vector[String]
) extends AddressInfoResult {
override val label: String = labels.mkString(", ")
override val ismine: Boolean = isProps.ismine
val solvable: Boolean = isProps.solvable
override val iswatchonly: Boolean = isProps.iswatchonly
override val isscript: Boolean = isProps.isscript
override val iswitness: Boolean = isProps.iswitness
override val iscompressed: Option[Boolean] = isProps.iscompressed
}
object AddressInfoResultPostV21 {
case class AddressInfoIsProps(
ismine: Boolean,
solvable: Boolean,
iswatchonly: Boolean,
isscript: Boolean,
iswitness: Boolean,
iscompressed: Option[Boolean])
case class AddressInfoResultPostV21WithoutIsProps(
address: BitcoinAddress,
scriptPubKey: ScriptPubKey,
desc: String,
witness_version: Option[WitnessVersion],
witness_program: Option[String],
script: Option[ScriptType],
hex: Option[ScriptPubKey],
pubkeys: Option[Vector[ECPublicKey]],
sigsrequired: Option[Int],
pubkey: Option[ECPublicKey],
embedded: Option[EmbeddedResult],
ischange: Boolean,
timestamp: Option[ZonedDateTime],
hdkeypath: Option[BIP32Path],
hdseedid: Option[RipeMd160Digest],
hdmasterfingerprint: Option[String],
labels: Vector[String])
def apply(
info: AddressInfoResultPostV21WithoutIsProps,
isProps: AddressInfoIsProps): AddressInfoResultPostV21 = {
AddressInfoResultPostV21(
address = info.address,
scriptPubKey = info.scriptPubKey,
isProps = isProps,
desc = info.desc,
witness_version = info.witness_version,
witness_program = info.witness_program,
script = info.script,
hex = info.hex,
pubkeys = info.pubkeys,
sigsrequired = info.sigsrequired,
pubkey = info.pubkey,
embedded = info.embedded,
ischange = info.ischange,
timestamp = info.timestamp,
hdkeypath = info.hdkeypath,
hdseedid = info.hdseedid,
hdmasterfingerprint = info.hdmasterfingerprint,
labels = info.labels
)
}
}
case class EmbeddedResult(
isscript: Boolean,
iswitness: Boolean,

View File

@ -177,8 +177,11 @@ object JsonSerializers {
implicit val networkAddressReads: Reads[NetworkAddress] =
Json.reads[NetworkAddress]
implicit val networkInfoReads: Reads[GetNetworkInfoResult] =
Json.reads[GetNetworkInfoResult]
implicit val geNetworkInfoPreV21Reads: Reads[GetNetworkInfoResultPreV21] =
Json.reads[GetNetworkInfoResultPreV21]
implicit val geNetworkInfoPostV21Reads: Reads[GetNetworkInfoResultPostV21] =
Json.reads[GetNetworkInfoResultPostV21]
implicit val satsPerKbReads: Reads[SatoshisPerKiloByte] =
new Reads[SatoshisPerKiloByte] {
@ -196,11 +199,14 @@ object JsonSerializers {
SatoshisPerVirtualByte(Satoshis(num.toBigInt)))(json)
}
implicit val peerNetworkInfoReads: Reads[PeerNetworkInfo] =
Json.reads[PeerNetworkInfo]
implicit val peerNetworkInfoPreV21Reads: Reads[PeerNetworkInfoPreV21] =
Json.reads[PeerNetworkInfoPreV21]
implicit val peerNetworkInfoPostV21Reads: Reads[PeerNetworkInfoPostV21] =
Json.reads[PeerNetworkInfoPostV21]
implicit val peerPreV20Reads: Reads[PeerPreV20] = ((__ \ "id").read[Int] and
__.read[PeerNetworkInfo] and
__.read[PeerNetworkInfoPreV21] and
(__ \ "version").read[Int] and
(__ \ "subver").read[String] and
(__ \ "inbound").read[Boolean] and
@ -215,8 +221,8 @@ object JsonSerializers {
(__ \ "bytesrecv_per_msg").read[Map[String, Int]] and
(__ \ "minfeefilter").readNullable[SatoshisPerKiloByte])(PeerPreV20)
implicit val peerPostV20Reads: Reads[PeerPostV20] = ((__ \ "id").read[Int] and
__.read[PeerNetworkInfo] and
implicit val peerV20Reads: Reads[PeerV20] = ((__ \ "id").read[Int] and
__.read[PeerNetworkInfoPreV21] and
(__ \ "version").read[Int] and
(__ \ "subver").read[String] and
(__ \ "inbound").read[Boolean] and
@ -228,7 +234,21 @@ object JsonSerializers {
(__ \ "whitelisted").read[Boolean] and
(__ \ "bytessent_per_msg").read[Map[String, Int]] and
(__ \ "bytesrecv_per_msg").read[Map[String, Int]] and
(__ \ "minfeefilter").readNullable[SatoshisPerKiloByte])(PeerPostV20)
(__ \ "minfeefilter").readNullable[SatoshisPerKiloByte])(PeerV20)
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 nodeBanPostV20Reads: Reads[NodeBanPostV20] =
Json.reads[NodeBanPostV20]
@ -478,6 +498,23 @@ object JsonSerializers {
}
}
implicit val addressInfoResultPostV21Reads: Reads[
AddressInfoResultPostV21] = {
Reads[AddressInfoResultPostV21] { json =>
for {
isProps <-
Json.reads[AddressInfoResultPostV21.AddressInfoIsProps].reads(json)
infoWithoutProps <-
Json
.reads[
AddressInfoResultPostV21.AddressInfoResultPostV21WithoutIsProps]
.reads(json)
} yield {
AddressInfoResultPostV21(infoWithoutProps, isProps)
}
}
}
implicit val receivedLabelReads: Reads[ReceivedLabel] =
Json.reads[ReceivedLabel]
@ -567,6 +604,9 @@ object JsonSerializers {
implicit val testMempoolAcceptResultReads: Reads[TestMempoolAcceptResult] =
TestMempoolAcceptResultReads
implicit val indexInfoResultReads: Reads[IndexInfoResult] =
Json.reads[IndexInfoResult]
implicit val createWalletResultReads: Reads[CreateWalletResult] =
Json.reads[CreateWalletResult]

View File

@ -52,8 +52,8 @@ class ServerRunTest extends BitcoinSAsyncTest {
val confFile = datadir.resolve("bitcoin-s.conf")
for {
bitcoind <- BitcoinSFixture.createBitcoindWithFunds(
Some(BitcoindVersion.Experimental))
bitcoind <-
BitcoinSFixture.createBitcoindWithFunds(Some(BitcoindVersion.V21))
// Make it so we connect to the correct bitcoind
port = bitcoind.instance.uri.getPort

View File

@ -45,7 +45,7 @@ class BitcoindInstanceTest extends BitcoindRpcTest {
clientAccum += client
for {
firstStarted <- client.isStartedF
_ <- client.start()
_ <- startClient(client)
secondStarted <- client.isStartedF
_ <- client.getBalance
@ -135,7 +135,7 @@ class BitcoindInstanceTest extends BitcoindRpcTest {
val client = BitcoindRpcClient.withActorSystem(instance)
for {
_ <- client.start()
_ <- startClient(client)
_ <- client.getNewAddress.flatMap(client.generateToAddress(101, _))
balance <- client.getBalance
_ <- BitcoindRpcTestUtil.stopServers(Vector(client))

View File

@ -38,7 +38,7 @@ class MultiWalletRpcTest extends BitcoindRpcTest {
clientAccum += walletClient
for {
_ <- walletClient.start()
_ <- startClient(walletClient)
_ <- walletClient.createWallet(walletName)
_ <- walletClient.encryptWallet(password, Some(walletName))
_ <-

View File

@ -179,7 +179,6 @@ class P2PRpcTest extends BitcoindRpcTest {
it should "be able to submit a new block" in {
for {
(client1, client2) <-
BitcoindRpcTestUtil.createUnconnectedNodePair(clientAccum = clientAccum)
hash <- client2.getNewAddress.flatMap(client2.generateToAddress(1, _))

View File

@ -45,7 +45,7 @@ class WalletRpcTest extends BitcoindRpcTest {
clientAccum += walletClient
for {
_ <- walletClient.start()
_ <- startClient(walletClient)
_ <- walletClient.getNewAddress.flatMap(
walletClient.generateToAddress(101, _))
_ <- walletClient.encryptWallet(password)

View File

@ -3,6 +3,7 @@ package org.bitcoins.rpc.v18
import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts.AddNodeArgument
import org.bitcoins.commons.jsonmodels.bitcoind.{
AddressInfoResultPostV18,
AddressInfoResultPostV21,
AddressInfoResultPreV18
}
import org.bitcoins.core.api.chain.db.BlockHeaderDbHelper
@ -131,7 +132,7 @@ class BitcoindV18RpcClientTest extends BitcoindRpcTest {
info <- client.getAddressInfo(address)
} yield {
info match {
case _: AddressInfoResultPreV18 =>
case _: AddressInfoResultPreV18 | _: AddressInfoResultPostV21 =>
fail("Was expecting AddressInfoResultPostV18")
case postV18Info: AddressInfoResultPostV18 =>
assert(postV18Info.address == address)

View File

@ -0,0 +1,222 @@
package org.bitcoins.rpc.v21
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.psbt.PSBT
import org.bitcoins.crypto.ECPublicKey
import org.bitcoins.rpc.client.common.BitcoindVersion
import org.bitcoins.rpc.client.v21.BitcoindV21RpcClient
import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil
import org.bitcoins.testkit.util.BitcoindRpcTest
import java.io.File
import java.nio.file.Files
import scala.concurrent.Future
class BitcoindV21RpcClientTest extends BitcoindRpcTest {
lazy val clientPairF: Future[(BitcoindV21RpcClient, BitcoindV21RpcClient)] =
BitcoindRpcTestUtil.createNodePairV21(clientAccum)
lazy val clientF: Future[BitcoindV21RpcClient] = clientPairF.map(_._1)
clientF.foreach(c => clientAccum.+=(c))
behavior of "BitcoindV21RpcClient"
it should "be able to start a V21 bitcoind instance" in {
clientF.map { client =>
assert(client.version == BitcoindVersion.V21)
}
}
it should "be able to get peer info" in {
for {
(freshClient, otherFreshClient) <- clientPairF
infoList <- freshClient.getPeerInfo
} yield {
assert(infoList.length >= 0)
val info = infoList.head
assert(info.addnode)
assert(info.networkInfo.addr == otherFreshClient.getDaemon.uri)
}
}
it should "be able to get network info" in {
for {
(freshClient, _) <- clientPairF
info <- freshClient.getNetworkInfo
} yield {
assert(info.networkactive)
assert(info.localrelay)
assert(info.connections == 1)
}
}
it should "get index info" in {
for {
(client, _) <- clientPairF
indexes <- client.getIndexInfo
} yield {
val txIndexInfo = indexes("txindex")
assert(txIndexInfo.synced)
assert(txIndexInfo.best_block_height == 400)
val blockFilterIndexInfo = indexes("basic block filter index")
assert(blockFilterIndexInfo.synced)
assert(blockFilterIndexInfo.best_block_height == 400)
}
}
it should "be able to get the address info for a given address" in {
for {
(client, _) <- clientPairF
addr <- client.getNewAddress
info <- client.getAddressInfo(addr)
} yield assert(info.address == addr)
}
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 =
PSBT.fromBase64(
"cHNidP8BACoCAAAAAAFAQg8AAAAAABepFG6Rty1Vk+fUOR4v9E6R6YXDFkHwhwAAAAAAAA==")
for {
(client, _) <- clientPairF
result <- client.utxoUpdatePsbt(psbt, Seq(descriptor))
} yield {
assert(result == 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,9 +1,8 @@
import scala.util.Properties
import scala.collection.JavaConverters._
import scala.concurrent.{Await, Future}
import scala.concurrent.duration.DurationInt
import java.nio.file.Files
import java.nio.file.Paths
import scala.collection.JavaConverters._
import scala.concurrent.duration.DurationInt
import scala.concurrent.{Await, Future}
import scala.util.Properties
name := "bitcoin-s-bitcoind-rpc"
@ -28,7 +27,8 @@ TaskKeys.downloadBitcoind := {
"0.18.99" // TODO: change this when new version compiled on suredbits server
val versions =
List("0.20.1",
List("0.21.0",
"0.20.1",
"0.19.0.1",
"0.18.1",
"0.17.0.1",
@ -51,7 +51,10 @@ TaskKeys.downloadBitcoind := {
val location =
if (version == experimentalVersion)
s"https://s3-us-west-1.amazonaws.com/suredbits.com/bitcoin-core-$version/bitcoin-$version-$platform.$suffix"
else
else if (version.init.endsWith("rc")) { // if it is a release candidate
val (base, rc) = version.splitAt(version.length - 3)
s"https://bitcoincore.org/bin/bitcoin-core-$base/test.$rc/bitcoin-$version-$platform.$suffix"
} else
s"https://bitcoincore.org/bin/bitcoin-core-$version/bitcoin-$version-$platform.$suffix"
val expectedEndLocation = binaryDir resolve s"bitcoin-$version"

View File

@ -26,6 +26,7 @@ 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.client.v21.BitcoindV21RpcClient
import org.bitcoins.rpc.config.{BitcoindConfig, BitcoindInstance}
import java.io.File
@ -65,7 +66,7 @@ class BitcoindRpcClient(val instance: BitcoindInstance)(implicit
with PsbtRpc
with UtilRpc {
override def version: BitcoindVersion = BitcoindVersion.Unknown
override def version: BitcoindVersion = instance.getVersion
require(version == BitcoindVersion.Unknown || version == instance.getVersion,
s"bitcoind version must be $version, got ${instance.getVersion}")
@ -264,6 +265,7 @@ object BitcoindRpcClient {
case BitcoindVersion.V18 => BitcoindV18RpcClient.withActorSystem(instance)
case BitcoindVersion.V19 => BitcoindV19RpcClient.withActorSystem(instance)
case BitcoindVersion.V20 => BitcoindV20RpcClient.withActorSystem(instance)
case BitcoindVersion.V21 => BitcoindV21RpcClient.withActorSystem(instance)
case BitcoindVersion.Experimental =>
BitcoindV18RpcClient.withActorSystem(instance)
case BitcoindVersion.Unknown =>
@ -280,9 +282,9 @@ sealed trait BitcoindVersion
object BitcoindVersion extends StringFactory[BitcoindVersion] {
/** The newest version of `bitcoind` we support */
val newest: BitcoindVersion = V20
val newest: BitcoindVersion = V21
val known = Vector(V16, V17, V18, V19, V20, Experimental)
val known = Vector(V16, V17, V18, V19, V20, V21, Experimental)
case object V16 extends BitcoindVersion {
override def toString: String = "v0.16"
@ -304,6 +306,10 @@ object BitcoindVersion extends StringFactory[BitcoindVersion] {
override def toString: String = "v0.20"
}
case object V21 extends BitcoindVersion {
override def toString: String = "v0.21"
}
case object Experimental extends BitcoindVersion {
override def toString: String = "v0.18.99"
}

View File

@ -4,6 +4,7 @@ import org.bitcoins.commons.jsonmodels.bitcoind._
import org.bitcoins.commons.serializers.JsonSerializers._
import org.bitcoins.core.protocol.blockchain.{Block, BlockHeader}
import org.bitcoins.crypto.{DoubleSha256Digest, DoubleSha256DigestBE}
import org.bitcoins.rpc.client.common.BitcoindVersion._
import play.api.libs.json.{JsBoolean, JsNumber, JsString}
import scala.concurrent.Future
@ -29,10 +30,9 @@ trait BlockchainRpc { self: Client =>
def getBlockChainInfo: Future[GetBlockChainInfoResult] = {
self.version match {
case BitcoindVersion.V16 | BitcoindVersion.V17 | BitcoindVersion.V18 =>
case V16 | V17 | V18 =>
bitcoindCall[GetBlockChainInfoResultPreV19]("getblockchaininfo")
case BitcoindVersion.V20 | BitcoindVersion.V19 |
BitcoindVersion.Experimental | BitcoindVersion.Unknown =>
case V21 | V20 | V19 | Experimental | Unknown =>
bitcoindCall[GetBlockChainInfoResultPostV19]("getblockchaininfo")
}
}

View File

@ -33,7 +33,7 @@ trait MempoolRpc { self: Client =>
Map[DoubleSha256DigestBE, GetMemPoolResult]] = {
self.version match {
case V20 | V19 | Experimental | Unknown =>
case V21 | V20 | V19 | Experimental | Unknown =>
bitcoindCall[Map[DoubleSha256DigestBE, GetMemPoolResultPostV19]](
"getmempoolancestors",
List(JsString(txid.hex), JsBoolean(true)))
@ -64,7 +64,7 @@ trait MempoolRpc { self: Client =>
def getMemPoolDescendantsVerbose(txid: DoubleSha256DigestBE): Future[
Map[DoubleSha256DigestBE, GetMemPoolResult]] = {
self.version match {
case V20 | V19 | Experimental | Unknown =>
case V21 | V20 | V19 | Experimental | Unknown =>
bitcoindCall[Map[DoubleSha256DigestBE, GetMemPoolResultPostV19]](
"getmempooldescendants",
List(JsString(txid.hex), JsBoolean(true)))
@ -84,7 +84,7 @@ trait MempoolRpc { self: Client =>
txid: DoubleSha256DigestBE): Future[GetMemPoolEntryResult] = {
self.version match {
case V20 | V19 | Experimental | Unknown =>
case V21 | V20 | V19 | Experimental | Unknown =>
bitcoindCall[GetMemPoolEntryResultPostV19]("getmempoolentry",
List(JsString(txid.hex)))
case V16 | V17 | V18 =>
@ -127,7 +127,7 @@ trait MempoolRpc { self: Client =>
Map[DoubleSha256DigestBE, GetMemPoolResult]] = {
self.version match {
case V20 | V19 | Experimental | Unknown =>
case V21 | V20 | V19 | Experimental | Unknown =>
bitcoindCall[Map[DoubleSha256DigestBE, GetMemPoolResultPostV19]](
"getrawmempool",
List(JsBoolean(true)))

View File

@ -42,7 +42,7 @@ trait MultisigRpc { self: Client =>
JsString(account)) ++ addressType.map(Json.toJson(_)).toList
self.version match {
case V20 | Unknown =>
case V21 | V20 | Unknown =>
bitcoindCall[MultiSigResultPostV20](
"addmultisigaddress",
params,
@ -84,7 +84,7 @@ trait MultisigRpc { self: Client =>
keys: Vector[ECPublicKey],
walletNameOpt: Option[String] = None): Future[MultiSigResult] = {
self.version match {
case V20 | Unknown =>
case V21 | V20 | Unknown =>
bitcoindCall[MultiSigResultPostV20](
"createmultisig",
List(JsNumber(minSignatures), Json.toJson(keys.map(_.hex))),

View File

@ -57,13 +57,20 @@ trait P2PRpc { self: Client =>
}
def getNetworkInfo: Future[GetNetworkInfoResult] = {
bitcoindCall[GetNetworkInfoResult]("getnetworkinfo")
version match {
case V21 | Unknown =>
bitcoindCall[GetNetworkInfoResultPostV21]("getnetworkinfo")
case V16 | V17 | V18 | V19 | V20 | Experimental =>
bitcoindCall[GetNetworkInfoResultPreV21]("getnetworkinfo")
}
}
def getPeerInfo: Future[Vector[Peer]] = {
self.version match {
case V20 | Unknown =>
bitcoindCall[Vector[PeerPostV20]]("getpeerinfo")
case V21 | Unknown =>
bitcoindCall[Vector[PeerPostV21]]("getpeerinfo")
case V20 =>
bitcoindCall[Vector[PeerV20]]("getpeerinfo")
case V16 | V17 | V18 | V19 | Experimental =>
bitcoindCall[Vector[PeerPreV20]]("getpeerinfo")
}
@ -71,7 +78,7 @@ trait P2PRpc { self: Client =>
def listBanned: Future[Vector[NodeBan]] = {
self.version match {
case V20 | Unknown =>
case V21 | V20 | Unknown =>
bitcoindCall[Vector[NodeBanPostV20]]("listbanned")
case V16 | V17 | V18 | V19 | Experimental =>
bitcoindCall[Vector[NodeBanPreV20]]("listbanned")

View File

@ -113,7 +113,7 @@ trait RawTransactionRpc { self: Client =>
maxfeerate: Double = 0.10): Future[DoubleSha256DigestBE] = {
val feeParameter = self.version match {
case V20 | V19 | Experimental | Unknown =>
case V21 | V20 | V19 | Experimental | Unknown =>
JsNumber(maxfeerate)
case V16 | V17 | V18 =>
JsBoolean(maxfeerate == 0)

View File

@ -1,13 +1,10 @@
package org.bitcoins.rpc.client.common
import org.bitcoins.commons.jsonmodels.bitcoind.{
DecodeScriptResult,
ValidateAddressResult,
ValidateAddressResultImpl
}
import org.bitcoins.commons.jsonmodels.bitcoind._
import org.bitcoins.commons.serializers.JsonSerializers._
import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.script.ScriptPubKey
import org.bitcoins.rpc.client.common.BitcoindVersion._
import play.api.libs.json.{JsString, Json}
import scala.concurrent.Future
@ -26,4 +23,29 @@ trait UtilRpc { self: Client =>
def decodeScript(script: ScriptPubKey): Future[DecodeScriptResult] = {
bitcoindCall[DecodeScriptResult]("decodescript", List(Json.toJson(script)))
}
def getIndexInfo: Future[Map[String, IndexInfoResult]] = {
version match {
case V21 | Unknown =>
bitcoindCall[Map[String, IndexInfoResult]]("getindexinfo")
case V16 | V17 | V18 | V19 | V20 | Experimental =>
Future.failed(
new RuntimeException(
s"getIndexInfo is only for version V21+, got $version"))
}
}
def getIndexInfo(indexName: String): Future[IndexInfoResult] = {
version match {
case V21 | Unknown =>
bitcoindCall[Map[String, IndexInfoResult]](
"getindexinfo",
List(JsString(indexName))).map(_.head._2)
case V16 | V17 | V18 | V19 | V20 | Experimental =>
Future.failed(
new RuntimeException(
s"getIndexInfo is only for version V21+, got $version"))
}
}
}

View File

@ -309,7 +309,7 @@ trait WalletRpc { self: Client =>
): Future[SetWalletFlagResult] = {
self.version match {
case V20 | V19 | Experimental | Unknown =>
case V21 | V20 | V19 | Experimental | Unknown =>
bitcoindCall[SetWalletFlagResult](
"setwalletflag",
List(JsString(flag.toString), Json.toJson(value)),
@ -323,7 +323,7 @@ trait WalletRpc { self: Client =>
def getBalances: Future[GetBalancesResult] = {
self.version match {
case V20 | V19 | Experimental | Unknown =>
case V21 | V20 | V19 | Experimental | Unknown =>
bitcoindCall[GetBalancesResult]("getbalances")
case V16 | V17 | V18 =>
Future.failed(
@ -334,7 +334,7 @@ trait WalletRpc { self: Client =>
def getBalances(walletName: String): Future[GetBalancesResult] = {
self.version match {
case V20 | V19 | Experimental | Unknown =>
case V21 | V20 | V19 | Experimental | Unknown =>
bitcoindCall[GetBalancesResult]("getbalances",
uriExtensionOpt =
Some(walletExtension(walletName)))
@ -408,7 +408,7 @@ trait WalletRpc { self: Client =>
blank: Boolean = false,
passphrase: String = ""): Future[CreateWalletResult] =
self.version match {
case V20 | V19 | Experimental | Unknown =>
case V21 | V20 | V19 | Experimental | Unknown =>
bitcoindCall[CreateWalletResult]("createwallet",
List(JsString(walletName),
JsBoolean(disablePrivateKeys),
@ -436,6 +436,11 @@ trait WalletRpc { self: Client =>
"getaddressinfo",
List(JsString(address.value)),
uriExtensionOpt = walletNameOpt.map(walletExtension))
case V21 | Unknown =>
bitcoindCall[AddressInfoResultPostV21](
"getaddressinfo",
List(JsString(address.value)),
uriExtensionOpt = walletNameOpt.map(walletExtension))
}
}

View File

@ -26,7 +26,7 @@ import scala.concurrent.Future
import scala.util.Try
/**
* Class for creating a BitcoindV19 instance that can access RPCs
* Class for creating a BitcoindV20 instance that can access RPCs
*/
class BitcoindV20RpcClient(override val instance: BitcoindInstance)(implicit
actorSystem: ActorSystem)

View File

@ -38,7 +38,7 @@ trait V20MultisigRpc extends MultisigRpc { self: Client =>
JsString(account)) ++ addressType.map(Json.toJson(_)).toList
self.version match {
case V20 | Unknown =>
case V20 | V21 | Unknown =>
bitcoindCall[MultiSigResultPostV20]("addmultisigaddress", params)
case version @ (V16 | V17 | V18 | V19 | Experimental) =>
throw new RuntimeException(
@ -76,7 +76,7 @@ trait V20MultisigRpc extends MultisigRpc { self: Client =>
keys: Vector[ECPublicKey],
walletNameOpt: Option[String] = None): Future[MultiSigResultPostV20] = {
self.version match {
case V20 | Unknown =>
case V20 | V21 | Unknown =>
bitcoindCall[MultiSigResultPostV20](
"createmultisig",
List(JsNumber(minSignatures), Json.toJson(keys.map(_.hex))),

View File

@ -0,0 +1,154 @@
package org.bitcoins.rpc.client.v21
import akka.actor.ActorSystem
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
import org.bitcoins.core.api.chain.ChainQueryApi.FilterResponse
import org.bitcoins.core.api.chain.db.CompactFilterDb
import org.bitcoins.core.gcs.FilterType
import org.bitcoins.core.protocol.transaction.Transaction
import org.bitcoins.core.script.crypto.HashType
import org.bitcoins.core.util.FutureUtil
import org.bitcoins.crypto.{DoubleSha256DigestBE, ECPrivateKey}
import org.bitcoins.rpc.client.common.{
BitcoindRpcClient,
BitcoindVersion,
DescriptorRpc,
PsbtRpc
}
import org.bitcoins.rpc.client.v19.V19BlockFilterRpc
import org.bitcoins.rpc.client.v20.{V20AssortedRpc, V20MultisigRpc}
import org.bitcoins.rpc.config.BitcoindInstance
import play.api.libs.json._
import scala.concurrent.Future
import scala.util.Try
/**
* Class for creating a BitcoindV21 instance that can access RPCs
*/
class BitcoindV21RpcClient(override val instance: BitcoindInstance)(implicit
actorSystem: ActorSystem)
extends BitcoindRpcClient(instance)
with DescriptorRpc
with PsbtRpc
with V19BlockFilterRpc
with V20MultisigRpc
with V20AssortedRpc {
override def getFiltersBetweenHeights(
startHeight: Int,
endHeight: Int): Future[Vector[ChainQueryApi.FilterResponse]] = {
val allHeights = startHeight.to(endHeight)
def f(range: Vector[Int]): Future[Vector[FilterResponse]] = {
val filterFs = range.map { height =>
for {
hash <- getBlockHash(height)
filter <- getBlockFilter(hash, FilterType.Basic)
} yield {
FilterResponse(filter.filter, hash, height)
}
}
Future.sequence(filterFs)
}
FutureUtil.batchExecute(elements = allHeights.toVector,
f = f,
init = Vector.empty,
batchSize = 25)
}
override def getFilterCount(): Future[Int] = getBlockCount
override def getFilterHeaderCount(): Future[Int] = getBlockCount
override def getFilter(
hash: DoubleSha256DigestBE): Future[Option[CompactFilterDb]] = {
for {
header <- getBlockHeader(hash)
filter <- getBlockFilter(hash, FilterType.Basic)
} yield Some(filter.filterDb(header.height))
}
override def getFiltersAtHeight(
height: Int): Future[Vector[CompactFilterDb]] = {
for {
hash <- getBlockHash(height)
filter <- getBlockFilter(hash, FilterType.Basic)
} yield Vector(filter.filterDb(height))
}
override lazy val version: BitcoindVersion = BitcoindVersion.V21
/**
* $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)))
}
object BitcoindV21RpcClient {
/**
* 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): BitcoindV21RpcClient = {
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): BitcoindV21RpcClient =
new BitcoindV21RpcClient(instance)(system)
def fromUnknownVersion(
rpcClient: BitcoindRpcClient): Try[BitcoindV21RpcClient] =
Try {
new BitcoindV21RpcClient(rpcClient.instance)(rpcClient.system)
}
}

View File

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

View File

@ -14,12 +14,11 @@ import org.bitcoins.testkit.node.{
NodeTestUtil,
NodeUnitTest
}
import org.scalatest.{DoNotDiscover, FutureOutcome}
import org.scalatest.FutureOutcome
import scala.concurrent.duration.DurationInt
import scala.concurrent.{Future, Promise}
@DoNotDiscover
class NeutrinoNodeTest extends NodeUnitTest {
/** Wallet config with data directory set to user temp directory */
@ -31,7 +30,7 @@ class NeutrinoNodeTest extends NodeUnitTest {
override def withFixture(test: OneArgAsyncTest): FutureOutcome =
withNeutrinoNodeFundedWalletBitcoind(test,
getBIP39PasswordOpt(),
Some(BitcoindVersion.Experimental))
Some(BitcoindVersion.V21))
private var assertionP: Promise[Boolean] = Promise()
after {
@ -124,7 +123,7 @@ class NeutrinoNodeTest extends NodeUnitTest {
startGenF.flatMap { cancellable =>
//we should expect 5 headers have been announced to us via
//the send headers message.
val ExpectedCount = 113
val ExpectedCount = 119
def hasBlocksF =
RpcUtil.retryUntilSatisfiedF(conditionF = () => {

View File

@ -3,14 +3,12 @@ package org.bitcoins.node
import org.bitcoins.core.currency._
import org.bitcoins.core.protocol.script.MultiSignatureScriptPubKey
import org.bitcoins.core.protocol.transaction.TransactionOutput
import org.bitcoins.core.util.EnvUtil
import org.bitcoins.core.wallet.fee.SatoshisPerByte
import org.bitcoins.crypto.ECPublicKey
import org.bitcoins.rpc.client.common.BitcoindVersion
import org.bitcoins.rpc.util.AsyncUtil
import org.bitcoins.server.BitcoinSAppConfig
import org.bitcoins.testkit.BitcoinSTestAppConfig
import org.bitcoins.testkit.fixtures.UsesExperimentalBitcoind
import org.bitcoins.testkit.node.{
NeutrinoNodeFundedWalletBitcoind,
NodeTestUtil,
@ -30,195 +28,185 @@ class NeutrinoNodeWithWalletTest extends NodeUnitTest {
override type FixtureParam = NeutrinoNodeFundedWalletBitcoind
def withFixture(test: OneArgAsyncTest): FutureOutcome = {
// We need to disable the test on non-linux CI runs
// because we do not have a mac binary of the BIP 157
// compatible version of bitcoin core
if (EnvUtil.isCI && !EnvUtil.isLinux) {
FutureOutcome.succeeded
} else {
withNeutrinoNodeFundedWalletBitcoind(
test = test,
bip39PasswordOpt = getBIP39PasswordOpt(),
versionOpt = Some(BitcoindVersion.Experimental)
)(system, getFreshConfig)
}
withNeutrinoNodeFundedWalletBitcoind(
test = test,
bip39PasswordOpt = getBIP39PasswordOpt(),
versionOpt = Some(BitcoindVersion.V21)
)(system, getFreshConfig)
}
val TestAmount = 1.bitcoin
val FeeRate = SatoshisPerByte(10.sats)
val TestFees: Satoshis = 2230.sats
it must "receive information about received payments" taggedAs UsesExperimentalBitcoind in {
param =>
val NeutrinoNodeFundedWalletBitcoind(node, wallet, bitcoind, _) = param
def condition(
expectedConfirmedAmount: CurrencyUnit,
expectedUnconfirmedAmount: CurrencyUnit,
expectedUtxos: Int,
expectedAddresses: Int): Future[Boolean] = {
for {
confirmedBalance <- wallet.getConfirmedBalance()
unconfirmedBalance <- wallet.getUnconfirmedBalance()
addresses <- wallet.listAddresses()
utxos <- wallet.listDefaultAccountUtxos()
} yield {
// +- fee rate because signatures could vary in size
(expectedConfirmedAmount === confirmedBalance +- FeeRate.currencyUnit) &&
(expectedUnconfirmedAmount === unconfirmedBalance +- FeeRate.currencyUnit) &&
(expectedAddresses == addresses.size) &&
(expectedUtxos == utxos.size)
}
}
//default wallet utxos are 3BTC, 2BTC, 1BTC
//our coin selection algorithm seems to be selecting
//the 3BTC utxo to spend, so we should have
//confirmed = 2BTC + 1BTC
//unconfirmed = 3 BTC - TestAmount - TestFees
val condition1 = () => {
condition(
expectedConfirmedAmount = 3.bitcoin,
expectedUnconfirmedAmount =
3.bitcoin - TestAmount - TestFees,
expectedUtxos = 3,
expectedAddresses = 7
)
}
//this is just sending TestAmount back to us
//so everything should stay the same as above
//expected we should have received TestAmount back
//and have 1 more address/utxo
val condition2 = { () =>
condition(
expectedConfirmedAmount = 3.bitcoin,
expectedUnconfirmedAmount =
(3.bitcoin - TestAmount - TestFees) + TestAmount,
expectedUtxos = 4,
expectedAddresses = 8
)
}
it must "receive information about received payments" in { param =>
val NeutrinoNodeFundedWalletBitcoind(node, wallet, bitcoind, _) = param
def condition(
expectedConfirmedAmount: CurrencyUnit,
expectedUnconfirmedAmount: CurrencyUnit,
expectedUtxos: Int,
expectedAddresses: Int): Future[Boolean] = {
for {
// send
addr <- bitcoind.getNewAddress
_ <- wallet.sendToAddress(addr, TestAmount, Some(FeeRate))
confirmedBalance <- wallet.getConfirmedBalance()
unconfirmedBalance <- wallet.getUnconfirmedBalance()
addresses <- wallet.listAddresses()
utxos <- wallet.listDefaultAccountUtxos()
} yield {
// +- fee rate because signatures could vary in size
(expectedConfirmedAmount === confirmedBalance +- FeeRate.currencyUnit) &&
(expectedUnconfirmedAmount === unconfirmedBalance +- FeeRate.currencyUnit) &&
(expectedAddresses == addresses.size) &&
(expectedUtxos == utxos.size)
}
}
_ <- wallet.getConfirmedBalance()
_ <- wallet.getUnconfirmedBalance()
_ <- wallet.getBalance()
_ <-
bitcoind.getNewAddress
.flatMap(bitcoind.generateToAddress(1, _))
_ <- wallet.getConfirmedBalance()
_ <- NodeTestUtil.awaitSync(node, bitcoind)
_ <- NodeTestUtil.awaitCompactFiltersSync(node, bitcoind)
_ <- AsyncUtil.awaitConditionF(condition1)
// receive
address <- wallet.getNewAddress()
txId <- bitcoind.sendToAddress(address, TestAmount)
expectedTx <- bitcoind.getRawTransactionRaw(txId)
//default wallet utxos are 3BTC, 2BTC, 1BTC
//our coin selection algorithm seems to be selecting
//the 3BTC utxo to spend, so we should have
//confirmed = 2BTC + 1BTC
//unconfirmed = 3 BTC - TestAmount - TestFees
val condition1 = () => {
condition(
expectedConfirmedAmount = 3.bitcoin,
expectedUnconfirmedAmount =
3.bitcoin - TestAmount - TestFees,
expectedUtxos = 3,
expectedAddresses = 7
)
}
_ <-
bitcoind.getNewAddress
.flatMap(bitcoind.generateToAddress(1, _))
_ <- NodeTestUtil.awaitSync(node, bitcoind)
_ <- NodeTestUtil.awaitCompactFilterHeadersSync(node, bitcoind)
_ <- NodeTestUtil.awaitCompactFiltersSync(node, bitcoind)
_ <- AsyncUtil.awaitConditionF(condition2)
// assert we got the full tx with witness data
txs <- wallet.listTransactions()
} yield assert(txs.exists(_.transaction == expectedTx))
//this is just sending TestAmount back to us
//so everything should stay the same as above
//expected we should have received TestAmount back
//and have 1 more address/utxo
val condition2 = { () =>
condition(
expectedConfirmedAmount = 3.bitcoin,
expectedUnconfirmedAmount =
(3.bitcoin - TestAmount - TestFees) + TestAmount,
expectedUtxos = 4,
expectedAddresses = 8
)
}
for {
// send
addr <- bitcoind.getNewAddress
_ <- wallet.sendToAddress(addr, TestAmount, Some(FeeRate))
_ <- wallet.getConfirmedBalance()
_ <- wallet.getUnconfirmedBalance()
_ <- wallet.getBalance()
_ <-
bitcoind.getNewAddress
.flatMap(bitcoind.generateToAddress(1, _))
_ <- wallet.getConfirmedBalance()
_ <- NodeTestUtil.awaitSync(node, bitcoind)
_ <- NodeTestUtil.awaitCompactFiltersSync(node, bitcoind)
_ <- AsyncUtil.awaitConditionF(condition1)
// receive
address <- wallet.getNewAddress()
txId <- bitcoind.sendToAddress(address, TestAmount)
expectedTx <- bitcoind.getRawTransactionRaw(txId)
_ <-
bitcoind.getNewAddress
.flatMap(bitcoind.generateToAddress(1, _))
_ <- NodeTestUtil.awaitSync(node, bitcoind)
_ <- NodeTestUtil.awaitCompactFilterHeadersSync(node, bitcoind)
_ <- NodeTestUtil.awaitCompactFiltersSync(node, bitcoind)
_ <- AsyncUtil.awaitConditionF(condition2)
// assert we got the full tx with witness data
txs <- wallet.listTransactions()
} yield assert(txs.exists(_.transaction == expectedTx))
}
it must "watch an arbitrary SPK" taggedAs UsesExperimentalBitcoind in {
param =>
val NeutrinoNodeFundedWalletBitcoind(node, wallet, bitcoind, _) = param
def generateBlock() =
for {
_ <-
bitcoind.getNewAddress
.flatMap(bitcoind.generateToAddress(1, _))
_ <- NodeTestUtil.awaitSync(node, bitcoind)
_ <- NodeTestUtil.awaitCompactFiltersSync(node, bitcoind)
} yield ()
val pk1 = ECPublicKey.freshPublicKey
val pk2 = ECPublicKey.freshPublicKey
val spk = MultiSignatureScriptPubKey(2, Vector(pk1, pk2))
val sats = TestAmount
val output = TransactionOutput(sats, spk)
it must "watch an arbitrary SPK" in { param =>
val NeutrinoNodeFundedWalletBitcoind(node, wallet, bitcoind, _) = param
def generateBlock() =
for {
// start watching
_ <- wallet.watchScriptPubKey(spk)
// send
txSent <- wallet.sendToOutputs(Vector(output), FeeRate)
_ <- node.broadcastTransaction(txSent)
// confirm
_ <- generateBlock()
_ <- generateBlock()
_ <- generateBlock()
// verify
txs <- wallet.listTransactions()
} yield assert(txs.exists(_.txIdBE == txSent.txIdBE))
}
it must "rescan information about received payments" taggedAs UsesExperimentalBitcoind in {
param =>
val NeutrinoNodeFundedWalletBitcoind(node, wallet, bitcoind, _) = param
def condition(): Future[Boolean] = {
for {
balance <- wallet.getBalance()
addresses <- wallet.listAddresses()
utxos <- wallet.listUtxos()
} yield {
balance == BitcoinSWalletTest.expectedDefaultAmt + TestAmount &&
utxos.size == 4 &&
addresses.map(_.scriptPubKey.hex).sorted == utxos
.map(_.output.scriptPubKey.hex)
.sorted
}
}
for {
addresses <- wallet.listAddresses()
utxos <- wallet.listDefaultAccountUtxos()
_ = assert(addresses.size == 6)
_ = assert(utxos.size == 3)
address <- wallet.getNewAddress()
_ <-
bitcoind
.sendToAddress(address, TestAmount)
addresses <- wallet.listAddresses()
utxos <- wallet.listDefaultAccountUtxos()
_ = assert(addresses.size == 7)
_ = assert(utxos.size == 3)
_ <- wallet.clearAllUtxosAndAddresses()
addresses <- wallet.listAddresses()
utxos <- wallet.listDefaultAccountUtxos()
_ = assert(addresses.isEmpty)
_ = assert(utxos.isEmpty)
_ <-
bitcoind.getNewAddress
.flatMap(bitcoind.generateToAddress(1, _))
_ <- NodeTestUtil.awaitSync(node, bitcoind)
_ <- NodeTestUtil.awaitCompactFiltersSync(node, bitcoind)
} yield ()
_ <- wallet.fullRescanNeutrinoWallet(addressBatchSize = 7)
val pk1 = ECPublicKey.freshPublicKey
val pk2 = ECPublicKey.freshPublicKey
val spk = MultiSignatureScriptPubKey(2, Vector(pk1, pk2))
val sats = TestAmount
val output = TransactionOutput(sats, spk)
_ <- AsyncUtil.awaitConditionF(condition)
} yield succeed
for {
// start watching
_ <- wallet.watchScriptPubKey(spk)
// send
txSent <- wallet.sendToOutputs(Vector(output), FeeRate)
_ <- node.broadcastTransaction(txSent)
// confirm
_ <- generateBlock()
_ <- generateBlock()
_ <- generateBlock()
// verify
txs <- wallet.listTransactions()
} yield assert(txs.exists(_.txIdBE == txSent.txIdBE))
}
it must "rescan information about received payments" in { param =>
val NeutrinoNodeFundedWalletBitcoind(node, wallet, bitcoind, _) = param
def condition(): Future[Boolean] = {
for {
balance <- wallet.getBalance()
addresses <- wallet.listAddresses()
utxos <- wallet.listUtxos()
} yield {
balance == BitcoinSWalletTest.expectedDefaultAmt + TestAmount &&
utxos.size == 4 &&
addresses.map(_.scriptPubKey.hex).sorted == utxos
.map(_.output.scriptPubKey.hex)
.sorted
}
}
for {
addresses <- wallet.listAddresses()
utxos <- wallet.listDefaultAccountUtxos()
_ = assert(addresses.size == 6)
_ = assert(utxos.size == 3)
address <- wallet.getNewAddress()
_ <-
bitcoind
.sendToAddress(address, TestAmount)
addresses <- wallet.listAddresses()
utxos <- wallet.listDefaultAccountUtxos()
_ = assert(addresses.size == 7)
_ = assert(utxos.size == 3)
_ <- wallet.clearAllUtxosAndAddresses()
addresses <- wallet.listAddresses()
utxos <- wallet.listDefaultAccountUtxos()
_ = assert(addresses.isEmpty)
_ = assert(utxos.isEmpty)
_ <-
bitcoind.getNewAddress
.flatMap(bitcoind.generateToAddress(1, _))
_ <- NodeTestUtil.awaitSync(node, bitcoind)
_ <- NodeTestUtil.awaitCompactFiltersSync(node, bitcoind)
_ <- wallet.fullRescanNeutrinoWallet(addressBatchSize = 7)
_ <- AsyncUtil.awaitConditionF(condition)
} yield succeed
}
}

View File

@ -70,6 +70,10 @@ trait EclairRpcTestUtil extends BitcoinSLogger {
bitcoindV: BitcoindVersion =
EclairRpcClient.bitcoindV): BitcoindInstance = {
bitcoindV match {
case BitcoindVersion.V21 =>
BitcoindRpcTestUtil.v21Instance(port = port,
rpcPort = rpcPort,
zmqPort = zmqPort)
case BitcoindVersion.V20 =>
BitcoindRpcTestUtil.v20Instance(port = port,
rpcPort = rpcPort,

View File

@ -3,7 +3,6 @@ package org.bitcoins.testkit.fixtures
import akka.actor.ActorSystem
import org.bitcoins.rpc.client.common.{BitcoindRpcClient, BitcoindVersion}
import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil
import org.bitcoins.testkit.util.BitcoinSAsyncFixtureTest
import org.scalatest._
@ -160,11 +159,12 @@ object BitcoinSFixture {
/** Creates a new bitcoind instance */
def createBitcoind(versionOpt: Option[BitcoindVersion] = None)(implicit
system: ActorSystem): Future[BitcoindRpcClient] = {
import system.dispatcher
val instance = BitcoindRpcTestUtil.instance(versionOpt = versionOpt)
val bitcoind = versionOpt match {
case Some(v) => BitcoindRpcClient.fromVersion(v, instance)
case None => new BitcoindRpcClient(instance)
}
bitcoind.start()
BitcoindRpcTestUtil.startServers(Vector(bitcoind)).map(_ => bitcoind)
}
}

View File

@ -36,6 +36,7 @@ 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.client.v21.BitcoindV21RpcClient
import org.bitcoins.rpc.config.{
BitcoindAuthCredentials,
BitcoindConfig,
@ -51,6 +52,7 @@ import scala.collection.mutable
import scala.concurrent._
import scala.concurrent.duration.{DurationInt, FiniteDuration}
import scala.util._
import scala.util.control.NonFatal
//noinspection AccessorLikeMethodIsEmptyParen
trait BitcoindRpcTestUtil extends BitcoinSLogger {
@ -103,6 +105,7 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
if (blockFilterIndex)
conf + """
|blockfilterindex=1
|peerblockfilters=1
|""".stripMargin
else
conf
@ -137,7 +140,7 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
version match {
// default to newest version
case Unknown => getBinary(BitcoindVersion.newest, binaryDirectory)
case known @ (Experimental | V16 | V17 | V18 | V19 | V20) =>
case known @ (Experimental | V16 | V17 | V18 | V19 | V20 | V21) =>
val fileList = Files
.list(binaryDirectory)
.iterator()
@ -186,7 +189,8 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
val hasNeutrinoSupport = versionOpt match {
case Some(V16) | Some(V17) | Some(V18) =>
false
case Some(V19) | Some(V20) | Some(Experimental) | Some(Unknown) | None =>
case Some(V19) | Some(V20) | Some(V21) | Some(Experimental) | Some(
Unknown) | None =>
true
}
val configFile =
@ -290,6 +294,20 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
versionOpt = Some(BitcoindVersion.V20),
binaryDirectory = binaryDirectory)
def v21Instance(
port: Int = RpcUtil.randomPort,
rpcPort: Int = RpcUtil.randomPort,
zmqPort: Int = RpcUtil.randomPort,
pruneMode: Boolean = false,
binaryDirectory: Path = BitcoindRpcTestClient.sbtBinaryDirectory
): BitcoindInstance =
instance(port = port,
rpcPort = rpcPort,
zmqPort = zmqPort,
pruneMode = pruneMode,
versionOpt = Some(BitcoindVersion.V21),
binaryDirectory = binaryDirectory)
def vExperimentalInstance(
port: Int = RpcUtil.randomPort,
rpcPort: Int = RpcUtil.randomPort,
@ -344,6 +362,12 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
zmqPort,
pruneMode,
binaryDirectory = binaryDirectory)
case BitcoindVersion.V21 =>
BitcoindRpcTestUtil.v21Instance(port,
rpcPort,
zmqPort,
pruneMode,
binaryDirectory = binaryDirectory)
case BitcoindVersion.Experimental =>
BitcoindRpcTestUtil.vExperimentalInstance(port,
rpcPort,
@ -359,7 +383,18 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
def startServers(servers: Vector[BitcoindRpcClient])(implicit
ec: ExecutionContext): Future[Unit] = {
val startedServers = servers.map(_.start())
val startedServers = servers.map { server =>
server.start().flatMap { res =>
val createWalletF = for {
_ <- res.createWallet("")
_ <- res.loadWallet("")
} yield res
createWalletF.recoverWith {
case NonFatal(_) => Future.successful(res)
}
}
}
Future.sequence(startedServers).map(_ => ())
}
@ -576,13 +611,7 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
val client2: BitcoindRpcClient =
BitcoindRpcClient.withActorSystem(instance())
val start1F = client1.start()
val start2F = client2.start()
for {
_ <- start1F
_ <- start2F
} yield {
startServers(Vector(client1, client2)).map { _ =>
clientAccum ++= List(client1, client2)
(client1, client2)
}
@ -654,6 +683,9 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
case BitcoindVersion.V20 =>
BitcoindV20RpcClient.withActorSystem(
BitcoindRpcTestUtil.v20Instance())
case BitcoindVersion.V21 =>
BitcoindV21RpcClient.withActorSystem(
BitcoindRpcTestUtil.v21Instance())
case BitcoindVersion.Experimental =>
BitcoindV19RpcClient.withActorSystem(
BitcoindRpcTestUtil.vExperimentalInstance())
@ -748,6 +780,15 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
system: ActorSystem): Future[(BitcoindV20RpcClient, BitcoindV20RpcClient)] =
createNodePairInternal(BitcoindVersion.V20, clientAccum)
/**
* Returns a pair of [[org.bitcoins.rpc.client.v21.BitcoindV21RpcClient BitcoindV21RpcClient]]
* that are connected with some blocks in the chain
*/
def createNodePairV21(
clientAccum: RpcClientAccum = Vector.newBuilder)(implicit
system: ActorSystem): Future[(BitcoindV21RpcClient, BitcoindV21RpcClient)] =
createNodePairInternal(BitcoindVersion.V21, clientAccum)
/**
* Returns a triple of [[org.bitcoins.rpc.client.common.BitcoindRpcClient BitcoindRpcClient]]
* that are connected with some blocks in the chain
@ -1035,7 +1076,7 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
//start the bitcoind instance so eclair can properly use it
val rpc = BitcoindRpcClient.withActorSystem(instance)
val startedF = rpc.start()
val startedF = startServers(Vector(rpc))
val blocksToGenerate = 102
//fund the wallet by generating 102 blocks, need this to get over coinbase maturity

View File

@ -6,6 +6,7 @@ import org.bitcoins.rpc.client.common.BitcoindRpcClient
import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil
import scala.collection.mutable
import scala.concurrent.Future
abstract class BitcoindRpcTest extends BitcoinSAsyncTest {
@ -45,4 +46,8 @@ abstract class BitcoindRpcTest extends BitcoinSAsyncTest {
BitcoindRpcTestUtil.stopServers(clientAccum.result())
super.afterAll()
}
def startClient(client: BitcoindRpcClient): Future[Unit] = {
BitcoindRpcTestUtil.startServers(Vector(client))
}
}