mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-01-18 21:34:39 +01:00
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:
parent
6426158a1c
commit
d69c60b8b2
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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]
|
||||
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
|
@ -38,7 +38,7 @@ class MultiWalletRpcTest extends BitcoindRpcTest {
|
||||
clientAccum += walletClient
|
||||
|
||||
for {
|
||||
_ <- walletClient.start()
|
||||
_ <- startClient(walletClient)
|
||||
_ <- walletClient.createWallet(walletName)
|
||||
_ <- walletClient.encryptWallet(password, Some(walletName))
|
||||
_ <-
|
||||
|
@ -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, _))
|
||||
|
@ -45,7 +45,7 @@ class WalletRpcTest extends BitcoindRpcTest {
|
||||
clientAccum += walletClient
|
||||
|
||||
for {
|
||||
_ <- walletClient.start()
|
||||
_ <- startClient(walletClient)
|
||||
_ <- walletClient.getNewAddress.flatMap(
|
||||
walletClient.generateToAddress(101, _))
|
||||
_ <- walletClient.encryptWallet(password)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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"
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -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)))
|
||||
|
@ -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))),
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
|
@ -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"))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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))),
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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 = () => {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user