diff --git a/app-commons/src/main/scala/org/bitcoins/commons/jsonmodels/bitcoind/NetworkResult.scala b/app-commons/src/main/scala/org/bitcoins/commons/jsonmodels/bitcoind/NetworkResult.scala index a618387eb29e..0f7064d309a3 100644 --- a/app-commons/src/main/scala/org/bitcoins/commons/jsonmodels/bitcoind/NetworkResult.scala +++ b/app-commons/src/main/scala/org/bitcoins/commons/jsonmodels/bitcoind/NetworkResult.scala @@ -99,12 +99,24 @@ case class PeerNetworkInfo( pingwait: Option[BigDecimal]) extends NetworkResult -case class NodeBan( +trait NodeBan extends NetworkResult { + def address: URI + def banned_until: UInt32 + def ban_created: UInt32 +} + +case class NodeBanPreV20( address: URI, banned_until: UInt32, ban_created: UInt32, ban_reason: String) - extends NetworkResult + extends NodeBan + +case class NodeBanPostV20( + address: URI, + banned_until: UInt32, + ban_created: UInt32) + extends NodeBan final case class GetNodeAddressesResult( time: FiniteDuration, diff --git a/app-commons/src/main/scala/org/bitcoins/commons/serializers/JsonSerializers.scala b/app-commons/src/main/scala/org/bitcoins/commons/serializers/JsonSerializers.scala index 941e10b37e73..177e0040f6e6 100644 --- a/app-commons/src/main/scala/org/bitcoins/commons/serializers/JsonSerializers.scala +++ b/app-commons/src/main/scala/org/bitcoins/commons/serializers/JsonSerializers.scala @@ -204,7 +204,11 @@ object JsonSerializers { (__ \ "bytesrecv_per_msg").read[Map[String, Int]] and (__ \ "minfeefilter").readNullable[SatoshisPerKiloByte])(Peer) - implicit val nodeBanReads: Reads[NodeBan] = Json.reads[NodeBan] + implicit val nodeBanPostV20Reads: Reads[NodeBanPostV20] = + Json.reads[NodeBanPostV20] + + implicit val nodeBanPreV20Reads: Reads[NodeBanPreV20] = + Json.reads[NodeBanPreV20] // Blockchain Models implicit val getBlockResultReads: Reads[GetBlockResult] = diff --git a/bitcoind-rpc/bitcoind-rpc.sbt b/bitcoind-rpc/bitcoind-rpc.sbt index 47340ac0391e..a7dc49684c22 100644 --- a/bitcoind-rpc/bitcoind-rpc.sbt +++ b/bitcoind-rpc/bitcoind-rpc.sbt @@ -1,6 +1,6 @@ import scala.util.Properties import scala.collection.JavaConverters._ -import scala.concurrent.{Future, Await} +import scala.concurrent.{Await, Future} import scala.concurrent.duration.DurationInt import java.nio.file.Files import java.nio.file.Paths @@ -19,16 +19,21 @@ TaskKeys.downloadBitcoind := { val binaryDir = CommonSettings.binariesPath.resolve("bitcoind") - if (Files.notExists(binaryDir)) { logger.info(s"Creating directory for bitcoind binaries: $binaryDir") Files.createDirectories(binaryDir) } - val experimentalVersion = "0.18.99" // TODO: change this when new version compiled on suredbits server + val experimentalVersion = + "0.18.99" // TODO: change this when new version compiled on suredbits server val versions = - List("0.19.0.1", "0.18.1", "0.17.0.1", "0.16.3", experimentalVersion) + List("0.20.1", + "0.19.0.1", + "0.18.1", + "0.17.0.1", + "0.16.3", + experimentalVersion) logger.debug( s"(Maybe) downloading Bitcoin Core binaries for versions: ${versions.mkString(",")}") @@ -51,12 +56,14 @@ TaskKeys.downloadBitcoind := { val expectedEndLocation = binaryDir resolve s"bitcoin-$version" - if (Files - .list(binaryDir) - .iterator - .asScala - .map(_.toString) - .exists(expectedEndLocation.toString.startsWith(_))) { + if ( + Files + .list(binaryDir) + .iterator + .asScala + .map(_.toString) + .exists(expectedEndLocation.toString.startsWith(_)) + ) { logger.debug( s"Directory $expectedEndLocation already exists, skipping download of version $version") Future.unit diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/BitcoindRpcClient.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/BitcoindRpcClient.scala index 7b7dbfbf046f..6dd79a06d47e 100644 --- a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/BitcoindRpcClient.scala +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/BitcoindRpcClient.scala @@ -3,9 +3,9 @@ package org.bitcoins.rpc.client.common import java.io.File import akka.actor.ActorSystem -import org.bitcoins.core.api.node.NodeApi import org.bitcoins.core.api.chain.ChainQueryApi import org.bitcoins.core.api.feeprovider.FeeRateApi +import org.bitcoins.core.api.node.NodeApi import org.bitcoins.core.protocol.BlockStamp import org.bitcoins.core.protocol.transaction.Transaction import org.bitcoins.core.util.FutureUtil @@ -15,6 +15,7 @@ import org.bitcoins.rpc.client.v16.BitcoindV16RpcClient import org.bitcoins.rpc.client.v17.BitcoindV17RpcClient import org.bitcoins.rpc.client.v18.BitcoindV18RpcClient import org.bitcoins.rpc.client.v19.BitcoindV19RpcClient +import org.bitcoins.rpc.client.v20.BitcoindV20RpcClient import org.bitcoins.rpc.config.{BitcoindConfig, BitcoindInstance} import scala.concurrent.Future @@ -164,6 +165,7 @@ object BitcoindRpcClient { case BitcoindVersion.V17 => BitcoindV17RpcClient.withActorSystem(instance) case BitcoindVersion.V18 => BitcoindV18RpcClient.withActorSystem(instance) case BitcoindVersion.V19 => BitcoindV19RpcClient.withActorSystem(instance) + case BitcoindVersion.V20 => BitcoindV20RpcClient.withActorSystem(instance) case BitcoindVersion.Experimental => BitcoindV18RpcClient.withActorSystem(instance) case BitcoindVersion.Unknown => @@ -180,7 +182,7 @@ sealed trait BitcoindVersion object BitcoindVersion { /** The newest version of `bitcoind` we support */ - val newest = V19 + val newest: BitcoindVersion = V20 case object V16 extends BitcoindVersion { override def toString: String = "v0.16" @@ -198,6 +200,10 @@ object BitcoindVersion { override def toString: String = "v0.19" } + case object V20 extends BitcoindVersion { + override def toString: String = "v0.20" + } + case object Experimental extends BitcoindVersion { override def toString: String = "v0.18.99" } diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/BlockchainRpc.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/BlockchainRpc.scala index ea8c24227199..00732c6bf574 100644 --- a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/BlockchainRpc.scala +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/BlockchainRpc.scala @@ -31,8 +31,8 @@ trait BlockchainRpc { self: Client => self.version match { case BitcoindVersion.V16 | BitcoindVersion.V17 | BitcoindVersion.V18 => bitcoindCall[GetBlockChainInfoResultPreV19]("getblockchaininfo") - case BitcoindVersion.V19 | BitcoindVersion.Experimental | - BitcoindVersion.Unknown => + case BitcoindVersion.V20 | BitcoindVersion.V19 | + BitcoindVersion.Experimental | BitcoindVersion.Unknown => bitcoindCall[GetBlockChainInfoResultPostV19]("getblockchaininfo") } } diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/MempoolRpc.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/MempoolRpc.scala index b61461801eea..e0bca5c5470f 100644 --- a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/MempoolRpc.scala +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/MempoolRpc.scala @@ -33,7 +33,7 @@ trait MempoolRpc { self: Client => Map[DoubleSha256DigestBE, GetMemPoolResult]] = { self.version match { - case V19 | Experimental | Unknown => + case 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 V19 | Experimental | Unknown => + case 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 V19 | Experimental | Unknown => + case 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 V19 | Experimental | Unknown => + case V20 | V19 | Experimental | Unknown => bitcoindCall[Map[DoubleSha256DigestBE, GetMemPoolResultPostV19]]( "getrawmempool", List(JsBoolean(true))) diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/P2PRpc.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/P2PRpc.scala index 19d032bf1b97..a795587631af 100644 --- a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/P2PRpc.scala +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/P2PRpc.scala @@ -9,6 +9,7 @@ import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts.{ import org.bitcoins.commons.jsonmodels.bitcoind._ import org.bitcoins.commons.serializers.JsonSerializers._ import org.bitcoins.core.protocol.blockchain.Block +import org.bitcoins.rpc.client.common.BitcoindVersion._ import play.api.libs.json.{JsBoolean, JsNumber, JsString} import scala.concurrent.Future @@ -65,7 +66,13 @@ trait P2PRpc { self: Client => } def listBanned: Future[Vector[NodeBan]] = { - bitcoindCall[Vector[NodeBan]]("listbanned") + self.version match { + case V20 | Unknown => + bitcoindCall[Vector[NodeBanPostV20]]("listbanned") + case V16 | V17 | V18 | V19 | Experimental => + bitcoindCall[Vector[NodeBanPreV20]]("listbanned") + + } } def setBan( diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/RawTransactionRpc.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/RawTransactionRpc.scala index cc093ed439c7..3be187b5e7d4 100644 --- a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/RawTransactionRpc.scala +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/RawTransactionRpc.scala @@ -98,7 +98,7 @@ trait RawTransactionRpc { self: Client => maxfeerate: Double = 0.10): Future[DoubleSha256DigestBE] = { val feeParameter = self.version match { - case V19 | Experimental | Unknown => + case V20 | V19 | Experimental | Unknown => JsNumber(maxfeerate) case V16 | V17 | V18 => JsBoolean(maxfeerate == 0) diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/WalletRpc.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/WalletRpc.scala index a6491e856c2e..b614d2a568de 100644 --- a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/WalletRpc.scala +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/WalletRpc.scala @@ -208,7 +208,7 @@ trait WalletRpc { self: Client => blank: Boolean = false, passphrase: String = ""): Future[CreateWalletResult] = self.version match { - case V19 | Experimental | Unknown => + case V20 | V19 | Experimental | Unknown => bitcoindCall[CreateWalletResult]("createwallet", List(JsString(walletName), JsBoolean(disablePrivateKeys), @@ -227,7 +227,7 @@ trait WalletRpc { self: Client => case V16 | V17 => bitcoindCall[AddressInfoResultPreV18]("getaddressinfo", List(JsString(address.value))) - case V18 | V19 | Experimental | Unknown => + case V18 | V19 | V20 | Experimental | Unknown => bitcoindCall[AddressInfoResultPostV18]("getaddressinfo", List(JsString(address.value))) } diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/v20/BitcoindV20RpcClient.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/v20/BitcoindV20RpcClient.scala new file mode 100644 index 000000000000..51c7310b69b9 --- /dev/null +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/v20/BitcoindV20RpcClient.scala @@ -0,0 +1,154 @@ +package org.bitcoins.rpc.client.v20 + +import akka.actor.ActorSystem +import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts.WalletFlag +import org.bitcoins.commons.jsonmodels.bitcoind.{ + GetBalancesResult, + RpcOpts, + SetWalletFlagResult, + SignRawTransactionResult +} +import org.bitcoins.commons.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.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.ECPrivateKey +import org.bitcoins.rpc.client.common.{ + BitcoindRpcClient, + BitcoindVersion, + DescriptorRpc, + PsbtRpc +} +import org.bitcoins.rpc.client.v19.V19BlockFilterRpc +import org.bitcoins.rpc.config.BitcoindInstance +import play.api.libs.json._ + +import scala.concurrent.Future +import scala.util.Try + +/** + * Class for creating a BitcoindV19 instance that can access RPCs + */ +class BitcoindV20RpcClient(override val instance: BitcoindInstance)(implicit + actorSystem: ActorSystem) + extends BitcoindRpcClient(instance) + with DescriptorRpc + with PsbtRpc + with V19BlockFilterRpc { + + 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 lazy val version: BitcoindVersion = BitcoindVersion.V20 + + /** + * $signRawTx + * + * This RPC call signs the raw transaction with keys found in + * the Bitcoin Core wallet. + */ + def signRawTransactionWithWallet( + transaction: Transaction, + utxoDeps: Vector[RpcOpts.SignRawTransactionOutputParameter] = + Vector.empty, + sigHash: HashType = HashType.sigHashAll + ): Future[SignRawTransactionResult] = + bitcoindCall[SignRawTransactionResult]("signrawtransactionwithwallet", + List(JsString(transaction.hex), + Json.toJson(utxoDeps), + Json.toJson(sigHash))) + + /** + * $signRawTx + * + * This RPC call signs the raw transaction with keys provided + * manually. + */ + def signRawTransactionWithKey( + transaction: Transaction, + keys: Vector[ECPrivateKey], + utxoDeps: Vector[RpcOpts.SignRawTransactionOutputParameter] = + Vector.empty, + sigHash: HashType = HashType.sigHashAll + ): Future[SignRawTransactionResult] = + bitcoindCall[SignRawTransactionResult]("signrawtransactionwithkey", + List(JsString(transaction.hex), + Json.toJson(keys), + Json.toJson(utxoDeps), + Json.toJson(sigHash))) + + /** + * Change the state of the given wallet flag for a wallet. + */ + def setWalletFlag( + flag: WalletFlag, + value: Boolean + ): Future[SetWalletFlagResult] = + bitcoindCall[SetWalletFlagResult]( + "setwalletflag", + List(JsString(flag.toString), Json.toJson(value))) + + def getBalances: Future[GetBalancesResult] = { + bitcoindCall[GetBalancesResult]("getbalances") + } + +} + +object BitcoindV20RpcClient { + + /** + * 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): BitcoindV20RpcClient = { + implicit val system = + ActorSystem.create(BitcoindRpcClient.ActorSystemName) + withActorSystem(instance) + } + + /** + * Creates an RPC client from the given instance, + * together with the given actor system. This is for + * advanced users, where you need fine grained control + * over the RPC client. + */ + def withActorSystem(instance: BitcoindInstance)(implicit + system: ActorSystem): BitcoindV20RpcClient = + new BitcoindV20RpcClient(instance)(system) + + def fromUnknownVersion( + rpcClient: BitcoindRpcClient): Try[BitcoindV20RpcClient] = + Try { + new BitcoindV20RpcClient(rpcClient.instance)(rpcClient.system) + } + +} diff --git a/testkit/src/main/scala/org/bitcoins/testkit/rpc/BitcoindRpcTestUtil.scala b/testkit/src/main/scala/org/bitcoins/testkit/rpc/BitcoindRpcTestUtil.scala index 1ea7d33c395c..7152b4bf345d 100644 --- a/testkit/src/main/scala/org/bitcoins/testkit/rpc/BitcoindRpcTestUtil.scala +++ b/testkit/src/main/scala/org/bitcoins/testkit/rpc/BitcoindRpcTestUtil.scala @@ -106,6 +106,7 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger { |debug=1 |walletbroadcast=1 |peerbloomfilters=1 + |fallbackfee=0.0002 |txindex=${if (pruneMode) 0 else 1 /* pruning and txindex are not compatible */} |zmqpubhashtx=tcp://127.0.0.1:$zmqPort @@ -157,7 +158,7 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger { version match { // default to newest version case Unknown => getBinary(BitcoindVersion.newest) - case known @ (Experimental | V16 | V17 | V18 | V19) => + case known @ (Experimental | V16 | V17 | V18 | V19 | V20) => val fileList = Files .list(binaryDirectory) .iterator() @@ -201,9 +202,12 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger { versionOpt: Option[BitcoindVersion] = None): BitcoindInstance = { val uri = new URI("http://localhost:" + port) val rpcUri = new URI("http://localhost:" + rpcPort) - val hasNeutrinoSupport = - versionOpt.contains(BitcoindVersion.V19) || versionOpt - .contains(BitcoindVersion.Experimental) + val hasNeutrinoSupport = versionOpt match { + case Some(V16) | Some(V17) | Some(V18) => + false + case Some(V19) | Some(V20) | Some(Experimental) | Some(Unknown) | None => + true + } val configFile = writtenConfig(uri, rpcUri, @@ -215,11 +219,7 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger { val binary: File = versionOpt match { case Some(version) => getBinary(version) case None => - if ( - Files.exists( - BitcoindRpcTestUtil.binaryDirectory - ) - ) { + if (Files.exists(BitcoindRpcTestUtil.binaryDirectory)) { newestBitcoindBinary } else { throw new RuntimeException( @@ -287,6 +287,18 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger { pruneMode = pruneMode, versionOpt = Some(BitcoindVersion.V19)) + def v20Instance( + port: Int = RpcUtil.randomPort, + rpcPort: Int = RpcUtil.randomPort, + zmqPort: Int = RpcUtil.randomPort, + pruneMode: Boolean = false + ): BitcoindInstance = + instance(port = port, + rpcPort = rpcPort, + zmqPort = zmqPort, + pruneMode = pruneMode, + versionOpt = Some(BitcoindVersion.V20)) + def vExperimentalInstance( port: Int = RpcUtil.randomPort, rpcPort: Int = RpcUtil.randomPort, @@ -593,6 +605,9 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger { case BitcoindVersion.V19 => BitcoindV19RpcClient.withActorSystem( BitcoindRpcTestUtil.v19Instance()) + case BitcoindVersion.V20 => + BitcoindV19RpcClient.withActorSystem( + BitcoindRpcTestUtil.v20Instance()) case BitcoindVersion.Experimental => BitcoindV19RpcClient.withActorSystem( BitcoindRpcTestUtil.vExperimentalInstance())