diff --git a/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/TestRpcUtilTest.scala b/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/TestRpcUtilTest.scala index 0ce78b708860..8cb7d8e10b1a 100644 --- a/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/TestRpcUtilTest.scala +++ b/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/TestRpcUtilTest.scala @@ -183,24 +183,6 @@ class TestRpcUtilTest extends BitcoindRpcTest { } } - it should "be able to wait for disconnected nodes" in { - for { - (first, second) <- BitcoindRpcTestUtil.createUnconnectedNodePair( - clientAccum) - _ <- first.addNode(second.instance.uri, AddNodeArgument.Add) - _ <- BitcoindRpcTestUtil.awaitConnection(first, second) - - peerInfo <- first.getPeerInfo - _ = assert(peerInfo.length == 1) - _ = assert(peerInfo.head.addnode) - _ = assert(peerInfo.head.networkInfo.addr == second.instance.uri) - - _ <- first.disconnectNode(peerInfo.head.networkInfo.addr) - _ <- BitcoindRpcTestUtil.awaitDisconnected(first, second) - newPeerInfo <- first.getPeerInfo - } yield assert(newPeerInfo.isEmpty) - } - it should "be able to find outputs of previous transactions" in { for { (first, second, _) <- clientsF diff --git a/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/common/P2PRpcTest.scala b/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/common/P2PRpcTest.scala index 8c7182d9d251..e8841aabef68 100644 --- a/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/common/P2PRpcTest.scala +++ b/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/common/P2PRpcTest.scala @@ -19,18 +19,6 @@ class P2PRpcTest extends BitcoindRpcTest { behavior of "P2PRpcTest" - 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 the added node info" in { for { diff --git a/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/common/WalletRpcTest.scala b/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/common/WalletRpcTest.scala index 51bb3cc1c96d..143886aa4775 100644 --- a/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/common/WalletRpcTest.scala +++ b/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/common/WalletRpcTest.scala @@ -4,22 +4,14 @@ import java.io.File import java.util.Scanner import org.bitcoins.core.crypto.{ECPrivateKey, ECPublicKey} -import org.bitcoins.core.currency.{Bitcoins, Satoshis} +import org.bitcoins.core.currency.{Bitcoins, CurrencyUnit, Satoshis} import org.bitcoins.core.number.{Int64, UInt32} -import org.bitcoins.core.protocol.P2PKHAddress import org.bitcoins.core.protocol.script.ScriptSignature -import org.bitcoins.core.protocol.transaction.{ - TransactionInput, - TransactionOutPoint -} +import org.bitcoins.core.protocol.transaction.{TransactionInput, TransactionOutPoint} +import org.bitcoins.core.protocol.{BitcoinAddress, P2PKHAddress} import org.bitcoins.core.wallet.fee.SatoshisPerByte import org.bitcoins.rpc.client.common.RpcOpts.AddressType -import org.bitcoins.rpc.client.common.{ - BitcoindRpcClient, - BitcoindVersion, - RpcOpts -} -import org.bitcoins.rpc.jsonmodels.RpcAddress +import org.bitcoins.rpc.client.common.{BitcoindRpcClient, BitcoindVersion, RpcOpts} import org.bitcoins.rpc.util.RpcUtil import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil import org.bitcoins.testkit.util.BitcoindRpcTest @@ -78,8 +70,8 @@ class WalletRpcTest extends BitcoindRpcTest { } yield { val expectedFileName = - if (client.instance.getVersion == BitcoindVersion.V17) "" - else "wallet.dat" + if (client.instance.getVersion == BitcoindVersion.V16) "wallet.dat" + else "" assert(wallets == Vector(expectedFileName)) } @@ -247,30 +239,51 @@ class WalletRpcTest extends BitcoindRpcTest { } it should "be able to list address groupings" in { + + val amount = Bitcoins(1.25) + + def getChangeAddressAndAmount(client: BitcoindRpcClient, address: BitcoinAddress): Future[(BitcoinAddress, CurrencyUnit)] = { + for { + listTx <- client.listTransactions().map(_.filter(tx => tx.address.contains(address) && tx.category == "send")) + _ = assert(listTx.nonEmpty) + tx = listTx.head + _ = assert(tx.txid.nonEmpty) + rawTx <- client.getRawTransactionRaw(tx.txid.get) + } yield { + val outs = rawTx.outputs.filterNot(_.value == amount) + val changeAddresses = outs + .map(out => (BitcoinAddress.fromScriptPubKey(out.scriptPubKey, networkParam), out.value)) + assert(changeAddresses.size == 1) + (changeAddresses.head._1.get, changeAddresses.head._2) + } + } + for { (client, _, _) <- clientsF + groupingsBefore <- client.listAddressGroupings + address <- client.getNewAddress _ <- BitcoindRpcTestUtil - .fundBlockChainTransaction(client, address, Bitcoins(1.25)) - groupings <- client.listAddressGroupings - block <- BitcoindRpcTestUtil.getFirstBlock(client) - } yield { - val rpcAddress = - groupings.find(vec => vec.head.address == address).get.head - assert(rpcAddress.address == address) - assert(rpcAddress.balance == Bitcoins(1.25)) + .fundBlockChainTransaction(client, address, amount) - val firstAddress = - block.tx.head.vout.head.scriptPubKey.addresses.get.head + (changeAddress, changeAmount) <- getChangeAddressAndAmount(client, address) - val maxGroup = - groupings - .max(Ordering.by[Vector[RpcAddress], BigDecimal](addr => - addr.head.balance.toBigDecimal)) - .head + groupingsAfter <- client.listAddressGroupings + } yield { - assert(maxGroup.address == firstAddress) + // the address should appear in a new address grouping + val rpcAddress = + groupingsAfter.find(vec => vec.head.address == address).get.head + assert(rpcAddress.address == address) + assert(rpcAddress.balance == amount) + + // the change address should be added to an exiting address grouping + val changeGrouping = groupingsAfter.find(after => + groupingsBefore.exists(before => before.head == after.head && before.size + 1 == after.size)).get + val rpcChangeAddress = changeGrouping.find(addr => addr.address == changeAddress).get + assert(rpcChangeAddress.address == changeAddress) + assert(rpcChangeAddress.balance == changeAmount) } } diff --git a/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/v16/BitcoindV16RpcClientTest.scala b/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/v16/BitcoindV16RpcClientTest.scala index 83acf4309db5..fced7750a12c 100644 --- a/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/v16/BitcoindV16RpcClientTest.scala +++ b/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/v16/BitcoindV16RpcClientTest.scala @@ -26,7 +26,19 @@ class BitcoindV16RpcClientTest extends BitcoindRpcTest { lazy val clientsF: Future[(BitcoindV16RpcClient, BitcoindV16RpcClient)] = BitcoindRpcTestUtil.createNodePairV16(clientAccum) - behavior of "BitoindV16RpcClient" + behavior of "BitcoindV16RpcClient" + + it should "be able to get peer info" in { + for { + (freshClient, otherFreshClient) <- clientsF + 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 start a V16 bitcoind" in { for { diff --git a/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/v17/BitcoindV17RpcClientTest.scala b/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/v17/BitcoindV17RpcClientTest.scala index 9d947d593585..f99fc0eb7a26 100644 --- a/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/v17/BitcoindV17RpcClientTest.scala +++ b/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/v17/BitcoindV17RpcClientTest.scala @@ -29,6 +29,18 @@ class BitcoindV17RpcClientTest extends BitcoindRpcTest { behavior of "BitcoindV17RpcClient" + it should "be able to get peer info" in { + for { + (freshClient, otherFreshClient) <- clientsF + infoList <- freshClient.getPeerInfo + } yield { + assert(infoList.length >= 0) + val info = infoList.head + assert(info.addnode) + assert(info.networkInfo.addr == otherFreshClient.getDaemon.uri) + } + } + it should "test mempool acceptance" in { for { (client, otherClient) <- clientsF diff --git a/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/v17/PsbtRpcTest.scala b/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/v17/PsbtRpcTest.scala index 0be82cb6c8a9..a3b2dc03654a 100644 --- a/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/v17/PsbtRpcTest.scala +++ b/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/v17/PsbtRpcTest.scala @@ -52,7 +52,8 @@ class PsbtRpcTest extends BitcoindRpcTest { processedPsbt <- client.walletProcessPsbt(psbt) decoded <- client.decodePsbt(processedPsbt.psbt) } yield { - assert(decoded.inputs.exists(_.nonWitnessUtxo.isDefined)) + assert(decoded.inputs.exists(inputs => + inputs.nonWitnessUtxo.isDefined || inputs.witnessUtxo.isDefined)) } } diff --git a/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/v18/BitcoindV18RpcClientTest.scala b/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/v18/BitcoindV18RpcClientTest.scala new file mode 100644 index 000000000000..8652dc2065f3 --- /dev/null +++ b/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/v18/BitcoindV18RpcClientTest.scala @@ -0,0 +1,101 @@ +package org.bitcoins.rpc.v18 +import org.bitcoins.chain.models.BlockHeaderDbHelper +import org.bitcoins.core.protocol.blockchain.RegTestNetChainParams +import org.bitcoins.rpc.client.common.BitcoindVersion +import org.bitcoins.rpc.client.common.RpcOpts.AddNodeArgument +import org.bitcoins.rpc.client.v18.BitcoindV18RpcClient +import org.bitcoins.testkit.chain.BlockHeaderHelper +import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil +import org.bitcoins.testkit.util.BitcoindRpcTest + +import scala.concurrent.Future + +class BitcoindV18RpcClientTest extends BitcoindRpcTest { + lazy val clientF: Future[BitcoindV18RpcClient] = { + val client = new BitcoindV18RpcClient(BitcoindRpcTestUtil.v18Instance()) + val clientIsStartedF = BitcoindRpcTestUtil.startServers(Vector(client)) + clientIsStartedF.map(_ => client) + } + lazy val clientPairF: Future[(BitcoindV18RpcClient, BitcoindV18RpcClient)] = + BitcoindRpcTestUtil.createNodePairV18(clientAccum) + + clientF.foreach(c => clientAccum.+=(c)) + + behavior of "BitcoindV18RpcClient" + + it should "be able to start a V18 bitcoind instance" in { + + clientF.map { client => + assert(client.version == BitcoindVersion.V18) + } + + } + + it should "return active rpc commands" in { + val generatedF = clientF.flatMap(client => + client.getNewAddress.flatMap(addr => client.generateToAddress(100, addr))) + val rpcinfoF = + generatedF.flatMap(_ => clientF.flatMap(client => client.getRpcInfo())) + + rpcinfoF.map { result => + assert(result.active_commands.length == 1) + } + } + + it should "return a list of wallets" in { + for { + client <- clientF + _ <- client.createWallet("Suredbits") + list <- client.listWalletDir() + } yield { + assert(list.wallets.exists(_.name.contains("Suredbits"))) + } + } + + it should "analyze a descriptor" in { + + val descriptor = + "pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)" + + val descriptorF = + clientF.flatMap(client => client.getDescriptorInfo(descriptor)) + + descriptorF.map { result => + assert(result.isrange.==(false)) + assert(result.issolvable.==(true)) + assert(result.hasprivatekeys.==(false)) + } + } + + it should "get node address given a null parameter" in { + val nodeF = clientF.flatMap(client => client.getNodeAddresses()) + + nodeF.map { result => + assert(result.isEmpty) + } + } + + //TODO: currently the test doesn't work because of how known nodes work (remove ignore and implement test) + it should "get node addresses given a count" ignore { + for { + (freshClient, otherFreshClient) <- clientPairF + freshclientnode <- freshClient.addNode(freshClient.getDaemon.uri, + AddNodeArgument.Add) + nodeaddress <- freshClient.getNodeAddresses(1) + } yield { + assert(nodeaddress.head.address == otherFreshClient.instance.uri) + assert(nodeaddress.head.services == 1) + } + + } + + it should "successfully submit a header" in { + val genesisHeader = RegTestNetChainParams.genesisBlock.blockHeader + val genesisHeaderDb = + BlockHeaderDbHelper.fromBlockHeader(height = 1, genesisHeader) + val nextHeader = BlockHeaderHelper.buildNextHeader(genesisHeaderDb) + clientF.flatMap(client => + client.submitHeader(nextHeader.blockHeader).map(_ => succeed)) + } + +} diff --git a/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/v18/PsbtRpcTest.scala b/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/v18/PsbtRpcTest.scala new file mode 100644 index 000000000000..babf248051d0 --- /dev/null +++ b/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/v18/PsbtRpcTest.scala @@ -0,0 +1,114 @@ +package org.bitcoins.rpc.v18 +import org.bitcoins.core.currency.Bitcoins +import org.bitcoins.rpc.client.v18.BitcoindV18RpcClient +import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil +import org.bitcoins.testkit.util.BitcoindRpcTest + +import scala.concurrent.Future + +/**Tests for PSBT for RPC calls specific to V18 new PSBT calls + *@see https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#test-vectors + */ +class PsbtRpcTest extends BitcoindRpcTest { + + lazy val clientF: Future[BitcoindV18RpcClient] = { + val client = new BitcoindV18RpcClient(BitcoindRpcTestUtil.v18Instance()) + val clientIsStartedF = BitcoindRpcTestUtil.startServers(Vector(client)) + clientIsStartedF.map(_ => client) + } + + clientF.foreach(c => clientAccum.+=(c)) + + behavior of "PsbtRpc" + + it should "return something when analyzePsbt is called" in { + clientF.flatMap { client => + val resultF = client.analyzePsbt( + //PSBT with one P2PKH input and one P2SH-P2WPKH input both with non-final scriptSigs. P2SH-P2WPKH input's redeemScript is available. Outputs filled. + "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEA3wIAAAABJoFxNx7f8oXpN63upLN7eAAMBWbLs61kZBcTykIXG/YAAAAAakcwRAIgcLIkUSPmv0dNYMW1DAQ9TGkaXSQ18Jo0p2YqncJReQoCIAEynKnazygL3zB0DsA5BCJCLIHLRYOUV663b8Eu3ZWzASECZX0RjTNXuOD0ws1G23s59tnDjZpwq8ubLeXcjb/kzjH+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIACICAurVlmh8qAYEPtw94RbN8p1eklfBls0FXPaYyNAr8k6ZELSmumcAAACAAAAAgAIAAIAAIgIDlPYr6d8ZlSxVh3aK63aYBhrSxKJciU9H2MFitNchPQUQtKa6ZwAAAIABAACAAgAAgAA=") + resultF.map { result => + val inputs = result.inputs + assert(inputs.nonEmpty) + } + } + } + it should "analyze a PSBT and return a non-empty result" in { + //PSBT with one P2PKH input and one P2SH-P2WPKH input both with non-final scriptSigs. P2SH-P2WPKH input's redeemScript is available. Outputs filled. + + val psbt = + "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEA3wIAAAABJoFxNx7f8oXpN63upLN7eAAMBWbLs61kZBcTykIXG/YAAAAAakcwRAIgcLIkUSPmv0dNYMW1DAQ9TGkaXSQ18Jo0p2YqncJReQoCIAEynKnazygL3zB0DsA5BCJCLIHLRYOUV663b8Eu3ZWzASECZX0RjTNXuOD0ws1G23s59tnDjZpwq8ubLeXcjb/kzjH+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIACICAurVlmh8qAYEPtw94RbN8p1eklfBls0FXPaYyNAr8k6ZELSmumcAAACAAAAAgAIAAIAAIgIDlPYr6d8ZlSxVh3aK63aYBhrSxKJciU9H2MFitNchPQUQtKa6ZwAAAIABAACAAgAAgAA=" + val analyzedF = clientF.flatMap(client => client.analyzePsbt(psbt)) + + analyzedF.map { result => + assert(result.inputs.exists(_.next.isDefined)) + assert(result.inputs.exists(_.missing.head.pubkeys.head.nonEmpty)) + assert(result.inputs.exists(_.missing.head.signatures.isEmpty)) + assert(result.inputs.exists(_.missing.head.redeemscript.isEmpty)) + assert(result.inputs.exists(_.missing.head.witnessscript.isEmpty)) + assert(result.inputs.exists(_.is_final) == false) + assert(result.estimated_feerate.isDefined == false) + assert(result.estimated_vsize.isDefined == false) + assert(result.fee.isDefined) + assert(result.next.nonEmpty) + } + } + it should "correctly analyze a psbt " in { + val psbt = + //PSBT with one P2PKH input and one P2SH-P2WPKH input both with non-final scriptSigs. P2SH-P2WPKH input's redeemScript is available. Outputs filled. + "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEA3wIAAAABJoFxNx7f8oXpN63upLN7eAAMBWbLs61kZBcTykIXG/YAAAAAakcwRAIgcLIkUSPmv0dNYMW1DAQ9TGkaXSQ18Jo0p2YqncJReQoCIAEynKnazygL3zB0DsA5BCJCLIHLRYOUV663b8Eu3ZWzASECZX0RjTNXuOD0ws1G23s59tnDjZpwq8ubLeXcjb/kzjH+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIACICAurVlmh8qAYEPtw94RbN8p1eklfBls0FXPaYyNAr8k6ZELSmumcAAACAAAAAgAIAAIAAIgIDlPYr6d8ZlSxVh3aK63aYBhrSxKJciU9H2MFitNchPQUQtKa6ZwAAAIABAACAAgAAgAA=" + val analyzedF = clientF.flatMap(client => client.analyzePsbt(psbt)) + val expectedfee = Bitcoins(0.00090341) + val expectedhasutxo = true + val expectedisfinal = false + val expectedrole = "updater" + analyzedF.map { result => + assert(result.fee.get == expectedfee) + assert(result.next == expectedrole) + assert(result.inputs.head.has_utxo == expectedhasutxo) + + assert(result.inputs.head.is_final == expectedisfinal) + + assert(result.estimated_vsize.isEmpty) + + assert(result.estimated_feerate.isEmpty) + + assert(result.inputs.head.next.get == expectedrole) + + } + } + + //Todo: figure out how to implement a test here + it should "check to see if the utxoUpdate input has been updated" in { + val psbt = + "cHNidP8BACoCAAAAAAFAQg8AAAAAABepFG6Rty1Vk+fUOR4v9E6R6YXDFkHwhwAAAAAAAA==" + val updatedF = clientF.flatMap(client => client.utxoUpdatePsbt(psbt)) + + updatedF.map { result => + assert(result.contains(psbt)) + } + } + + /** + * Join psbt looks at the characteristics of a vector of PSBTs and converts them into a singular PSBT. + * This test takes test vectors from BIP 157 each missing some characteristic covered by the other. When joined + * together the resulting PSBT represented as a string is very different so we can't just search for parts of either + * PSBT. + */ + it should "joinpsbts " in { + val seqofpsbts = Vector( + "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAAAA", + //PSBT with one P2PKH input and one P2SH-P2WPKH input both with non-final scriptSigs. P2SH-P2WPKH input's redeemScript is available. Outputs filled. + "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEA3wIAAAABJoFxNx7f8oXpN63upLN7eAAMBWbLs61kZBcTykIXG/YAAAAAakcwRAIgcLIkUSPmv0dNYMW1DAQ9TGkaXSQ18Jo0p2YqncJReQoCIAEynKnazygL3zB0DsA5BCJCLIHLRYOUV663b8Eu3ZWzASECZX0RjTNXuOD0ws1G23s59tnDjZpwq8ubLeXcjb/kzjH+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIACICAurVlmh8qAYEPtw94RbN8p1eklfBls0FXPaYyNAr8k6ZELSmumcAAACAAAAAgAIAAIAAIgIDlPYr6d8ZlSxVh3aK63aYBhrSxKJciU9H2MFitNchPQUQtKa6ZwAAAIABAACAAgAAgAA=" + ) + + val joinedF = clientF.flatMap(client => client.joinPsbts(seqofpsbts)) + + joinedF.map { result => + assert( + result.contains( + //the expected joined version of these 2 psbts + "cHNidP8BAP0LAQIAAAADJoFxNx7f8oXpN63upLN7eAAMBWbLs61kZBcTykIXG/YAAAAAAP7///+rCUmgjFr3xJuCEvQX4vFas/XDPc8VOCGoE5+Helt75AAAAAAA/v///6sJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAQAAAAD+////BNPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh2A76gsAAAAAGXapFHaKQLvXQMvoHZiOcd4qTVxxOWsdiKyOJAAAAAAAABl2qRRvRiC1U/oJXnIbnuDv6foDnMpFl4isAAAAAAABAMwBAAAAAomjxx6rTSDgNxu7pMxpj6KVyUY6+i45f4UzzLYvlWflAQAAABcWABS+GNFSqbASA52vPafeT1M0nuy5hf////+G+KpDpx3/FEiJOlMKcjfva0YIu7LdLQFx5jrsakiQtAEAAAAXFgAU/j6e8adF6XTZAsQ1WUOryzS9U1P/////AgDC6wsAAAAAGXapFIXP8Ql/2eAIuzSvcJxiGXs4l4pIiKxy/vhOLAAAABepFDOXJboh79Yqx1OpvNBn1semo50FhwAAAAAAAQDfAgAAAAEmgXE3Ht/yhek3re6ks3t4AAwFZsuzrWRkFxPKQhcb9gAAAABqRzBEAiBwsiRRI+a/R01gxbUMBD1MaRpdJDXwmjSnZiqdwlF5CgIgATKcqdrPKAvfMHQOwDkEIkIsgctFg5RXrrdvwS7dlbMBIQJlfRGNM1e44PTCzUbbezn22cONmnCry5st5dyNv+TOMf7///8C09/1BQAAAAAZdqkU0MWZA8W6woaHYOkP1SGkZlqnZSCIrADh9QUAAAAAF6kUNUXm4zuDLEcFDyTT7rk8nAOUi8eHsy4TAAABASAA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHhwEEFgAUhdE1N/LiZUBaNNuvqePdoB+4IwgAAAAiAgLq1ZZofKgGBD7cPeEWzfKdXpJXwZbNBVz2mMjQK/JOmRC0prpnAAAAgAAAAIACAACAACICA5T2K+nfGZUsVYd2iut2mAYa0sSiXIlPR9jBYrTXIT0FELSmumcAAACAAQAAgAIAAIAA")) + } + } + +} diff --git a/bitcoind-rpc/bitcoind-rpc.sbt b/bitcoind-rpc/bitcoind-rpc.sbt index 2efe5395df0e..12621dac79b9 100644 --- a/bitcoind-rpc/bitcoind-rpc.sbt +++ b/bitcoind-rpc/bitcoind-rpc.sbt @@ -22,21 +22,22 @@ TaskKeys.downloadBitcoind := { Files.createDirectories(binaryDir) } - val versions = List("0.17.0.1", "0.16.3") + val versions = List("0.18.1", "0.17.0.1", "0.16.3") logger.debug( s"(Maybe) downloading Bitcoin Core binaries for versions: ${versions.mkString(",")}") - val platform = - if (Properties.isLinux) "x86_64-linux-gnu" - else if (Properties.isMac) "osx64" + val (platform, suffix) = + if (Properties.isLinux) ("x86_64-linux-gnu", "tar.gz") + else if (Properties.isMac) ("osx64", "tar.gz") + else if (Properties.isWin) ("win64", "zip") else sys.error(s"Unsupported OS: ${Properties.osName}") versions.foreach { version => val versionDir = binaryDir resolve version - val archiveLocation = binaryDir resolve s"$version.tar.gz" + val archiveLocation = binaryDir resolve s"$version.$suffix" val location = - s"https://bitcoincore.org/bin/bitcoin-core-$version/bitcoin-$version-$platform.tar.gz" + s"https://bitcoincore.org/bin/bitcoin-core-$version/bitcoin-$version-$platform.$suffix" val expectedEndLocation = binaryDir resolve s"bitcoin-$version" if (Files 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 e3c130a65f78..d03c42d39f0b 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 @@ -86,8 +86,10 @@ sealed trait BitcoindVersion object BitcoindVersion { - /** The newest `bitcoind` version supported by Bitcoin-S */ - val newest = V17 + + + /** The newest version of `bitcoind` we support */ + val newest = V18 case object V16 extends BitcoindVersion { override def toString: String = "v0.16" @@ -97,6 +99,10 @@ object BitcoindVersion { override def toString: String = "v0.17" } + case object V18 extends BitcoindVersion { + override def toString: String = "v0.18" + } + case object Unknown extends BitcoindVersion { override def toString: String = "Unknown" } 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 b114bd91bbda..9645231ad66e 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 @@ -49,14 +49,14 @@ trait P2PRpc { self: Client => bitcoindCall[Int]("getconnectioncount") } - def getNetworkInfo: Future[GetNetworkInfoResult] = { - bitcoindCall[GetNetworkInfoResult]("getnetworkinfo") - } - def getNetTotals: Future[GetNetTotalsResult] = { bitcoindCall[GetNetTotalsResult]("getnettotals") } + def getNetworkInfo: Future[GetNetworkInfoResult] = { + bitcoindCall[GetNetworkInfoResult]("getnetworkinfo") + } + def getPeerInfo: Future[Vector[Peer]] = { bitcoindCall[Vector[Peer]]("getpeerinfo") } 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 b13b930bc69d..c3352ee6f3e7 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 @@ -7,9 +7,11 @@ import org.bitcoins.core.protocol.transaction.{Transaction, TransactionInput} import org.bitcoins.rpc.jsonmodels.{ FundRawTransactionResult, GetRawTransactionResult, - RpcTransaction + RpcTransaction, + SignRawTransactionResult } import org.bitcoins.rpc.serializers.JsonSerializers._ +import org.bitcoins.rpc.serializers.JsonWriters._ import play.api.libs.json._ import scala.concurrent.Future @@ -94,4 +96,5 @@ trait RawTransactionRpc { self: Client => "sendrawtransaction", List(JsString(transaction.hex), JsBoolean(allowHighFees))) } + } 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 939ea80d9cb0..5761c37739f5 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 @@ -184,4 +184,17 @@ trait WalletRpc { self: Client => "walletpassphrasechange", List(JsString(currentPassphrase), JsString(newPassphrase))) } + + def createWallet( + walletName: String, + disablePrivateKeys: Boolean = false): Future[CreateWalletResult] = { + bitcoindCall[CreateWalletResult]( + "createwallet", + List(JsString(walletName), Json.toJson(disablePrivateKeys))) + } + + def getAddressInfo(address: BitcoinAddress): Future[AddressInfoResult] = { + bitcoindCall[AddressInfoResult]("getaddressinfo", + List(JsString(address.value))) + } } diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/v17/BitcoindV17RpcClient.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/v17/BitcoindV17RpcClient.scala index 2bb8709061a7..eda6d6746a81 100644 --- a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/v17/BitcoindV17RpcClient.scala +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/v17/BitcoindV17RpcClient.scala @@ -2,7 +2,6 @@ package org.bitcoins.rpc.client.v17 import akka.actor.ActorSystem import org.bitcoins.core.crypto.ECPrivateKey -import org.bitcoins.core.protocol.BitcoinAddress import org.bitcoins.core.protocol.transaction.Transaction import org.bitcoins.core.script.crypto.HashType import org.bitcoins.rpc.client.common.{ @@ -12,8 +11,6 @@ import org.bitcoins.rpc.client.common.{ } import org.bitcoins.rpc.config.BitcoindInstance import org.bitcoins.rpc.jsonmodels.{ - AddressInfoResult, - CreateWalletResult, SignRawTransactionResult, TestMempoolAcceptResult } @@ -44,11 +41,6 @@ class BitcoindV17RpcClient(override val instance: BitcoindInstance)( override def version: BitcoindVersion = BitcoindVersion.V17 - def getAddressInfo(address: BitcoinAddress): Future[AddressInfoResult] = { - bitcoindCall[AddressInfoResult]("getaddressinfo", - List(JsString(address.value))) - } - /** * $signRawTx * @@ -93,14 +85,6 @@ class BitcoindV17RpcClient(override val instance: BitcoindInstance)( List(JsArray(Vector(Json.toJson(transaction))), JsBoolean(allowHighFees))) .map(_.head) } - - def createWallet( - walletName: String, - disablePrivateKeys: Boolean = false): Future[CreateWalletResult] = { - bitcoindCall[CreateWalletResult]( - "createwallet", - List(JsString(walletName), Json.toJson(disablePrivateKeys))) - } } object BitcoindV17RpcClient { diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/v18/BitcoindV18RpcClient.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/v18/BitcoindV18RpcClient.scala new file mode 100644 index 000000000000..d6b1cf50c92c --- /dev/null +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/v18/BitcoindV18RpcClient.scala @@ -0,0 +1,99 @@ +package org.bitcoins.rpc.client.v18 +import akka.actor.ActorSystem +import org.bitcoins.rpc.client.common.{BitcoindRpcClient, BitcoindVersion} +import org.bitcoins.rpc.config.BitcoindInstance + +import scala.util.Try +import org.bitcoins.core.protocol.transaction.Transaction +import org.bitcoins.rpc.client.common.RpcOpts +import org.bitcoins.core.crypto.ECPrivateKey +import org.bitcoins.core.script.crypto.HashType +import org.bitcoins.rpc.jsonmodels.SignRawTransactionResult +import play.api.libs.json.Json +import play.api.libs.json.JsString +import scala.concurrent.Future +import org.bitcoins.rpc.serializers.JsonSerializers._ +import org.bitcoins.rpc.serializers.JsonWriters._ + +/** + * Class for creating a BitcoindV18 instance that can access RPCs + * @param instance + * @param actorSystem + */ +class BitcoindV18RpcClient(override val instance: BitcoindInstance)( + implicit + actorSystem: ActorSystem) + extends BitcoindRpcClient(instance) + with V18PsbtRpc + with V18DescriptorRpc + with V18AssortedRpc { + + override lazy val version: BitcoindVersion = BitcoindVersion.V18 + + /** + * $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 BitcoindV18RpcClient { + + /** + * 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): BitcoindV18RpcClient = { + 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, wher you need fine grained control + * over the RPC client. + */ + def withActorSystem(instance: BitcoindInstance)( + implicit system: ActorSystem): BitcoindV18RpcClient = + new BitcoindV18RpcClient(instance)(system) + + def fromUnknownVersion( + rpcClient: BitcoindRpcClient): Try[BitcoindV18RpcClient] = + Try { + new BitcoindV18RpcClient(rpcClient.instance)(rpcClient.system) + } + +} diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/v18/V18AssortedRpc.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/v18/V18AssortedRpc.scala new file mode 100644 index 000000000000..67e6895fafa2 --- /dev/null +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/v18/V18AssortedRpc.scala @@ -0,0 +1,48 @@ +package org.bitcoins.rpc.client.v18 +import org.bitcoins.core.protocol.blockchain.BlockHeader +import org.bitcoins.rpc.client.common.Client +import org.bitcoins.rpc.jsonmodels.{ + GetNodeAddressesResult, + GetRpcInfoResult, + ListWalletDirResult +} +import org.bitcoins.rpc.serializers.JsonSerializers._ +import play.api.libs.json.{JsString, Json} + +import scala.concurrent.Future + +/** + * Assorted Rpc calls for Bitcoin V18 + * @see [[https://bitcoincore.org/en/doc/0.18.0/rpc/network/getnodeaddresses/]] + * @see [[https://bitcoincore.org/en/doc/0.18.0/rpc/wallet/listwalletdir/]] + * @see [[https://github.com/bitcoin/bitcoin/commit/e82f6ad6f270f1f101d8853be32fd11eff4ddfb8]] + * @see [[https://bitcoincore.org/en/doc/0.18.0/rpc/mining/submitheader/]] + */ +trait V18AssortedRpc { + self: Client => + + private def getNodeAddresses( + count: Option[Int]): Future[Vector[GetNodeAddressesResult]] = { + bitcoindCall[Vector[GetNodeAddressesResult]]("getnodeaddresses", + List(Json.toJson(count))) + } + + def getNodeAddresses(count: Int): Future[Vector[GetNodeAddressesResult]] = + getNodeAddresses(Some(count)) + + def getNodeAddresses(): Future[Vector[GetNodeAddressesResult]] = + getNodeAddresses(None) + + def listWalletDir(): Future[ListWalletDirResult] = { + bitcoindCall[ListWalletDirResult]("listwalletdir") + } + + def getRpcInfo(): Future[GetRpcInfoResult] = { + bitcoindCall[GetRpcInfoResult]("getrpcinfo") + } + + def submitHeader(header: BlockHeader): Future[Unit] = { + bitcoindCall[Unit]("submitheader", List(JsString(header.hex))) + } + +} diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/v18/V18DescriptorRpc.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/v18/V18DescriptorRpc.scala new file mode 100644 index 000000000000..f3ceadb7da07 --- /dev/null +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/v18/V18DescriptorRpc.scala @@ -0,0 +1,32 @@ +package org.bitcoins.rpc.client.v18 +import org.bitcoins.rpc.client.common.Client +import org.bitcoins.rpc.jsonmodels.{ + DeriveAddressesResult, + GetDescriptorInfoResult +} +import play.api.libs.json.{JsString, Json} +import org.bitcoins.rpc.serializers.JsonSerializers._ + +import scala.concurrent.Future + +/** + * RPC calls in V18 that use descriptor to give us output information + * @see [[https://bitcoincore.org/en/doc/0.18.0/rpc/util/deriveaddresses/]] + * @see [[https://bitcoincore.org/en/doc/0.18.0/rpc/util/getdescriptorinfo/]] + */ +trait V18DescriptorRpc { + self: Client => + + def deriveAddresses( + descriptor: String, + range: Option[Vector[Double]]): Future[DeriveAddressesResult] = { + bitcoindCall[DeriveAddressesResult]( + "deriveaddresses", + List(JsString(descriptor), Json.toJson(range))) + } + + def getDescriptorInfo(descriptor: String): Future[GetDescriptorInfoResult] = { + bitcoindCall[GetDescriptorInfoResult]("getdescriptorinfo", + List(JsString(descriptor))) + } +} diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/v18/V18PsbtRpc.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/v18/V18PsbtRpc.scala new file mode 100644 index 000000000000..63870a5c8d9c --- /dev/null +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/v18/V18PsbtRpc.scala @@ -0,0 +1,31 @@ +package org.bitcoins.rpc.client.v18 + +import org.bitcoins.rpc.client.common.Client +import org.bitcoins.rpc.jsonmodels.AnalyzePsbtResult +import org.bitcoins.rpc.serializers.JsonSerializers._ +import play.api.libs.json._ + +import scala.concurrent.Future + +/** + * Set of utilities to analyze, join, and update existing PSBTs + * @see [[https://bitcoincore.org/en/doc/0.18.0/rpc/rawtransactions/analyzepsbt/]] + * @see [[https://bitcoincore.org/en/doc/0.18.0/rpc/rawtransactions/joinpsbts/]] + * @see [[https://bitcoincore.org/en/doc/0.18.0/rpc/rawtransactions/utxoupdatepsbt/]] + */ +trait V18PsbtRpc { + self: Client => + + def analyzePsbt(psbt: String): Future[AnalyzePsbtResult] = { + bitcoindCall[AnalyzePsbtResult]("analyzepsbt", List(JsString(psbt))) + } + + def joinPsbts(txs: Seq[String]): Future[String] = { + bitcoindCall[String]("joinpsbts", List(Json.toJson(txs))) + } + + def utxoUpdatePsbt(psbt: String): Future[String] = { + bitcoindCall[String]("utxoupdatepsbt", List(JsString(psbt))) + } + +} diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/config/BitcoindInstance.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/config/BitcoindInstance.scala index 1914908d1cc1..0755ce77cf49 100644 --- a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/config/BitcoindInstance.scala +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/config/BitcoindInstance.scala @@ -9,7 +9,15 @@ import org.bitcoins.core.util.BitcoinSLogger import org.bitcoins.rpc.client.common.BitcoindVersion import scala.sys.process._ + +import org.bitcoins.core.util.BitcoinSLogger +import org.bitcoins.core.config.NetworkParameters + import scala.util.Properties +import java.nio.file.Files + +import scala.util.Properties + /** * Created by chris on 4/29/17. @@ -48,6 +56,8 @@ sealed trait BitcoindInstance extends BitcoinSLogger { BitcoindVersion.V16 case _: String if foundVersion.startsWith(BitcoindVersion.V17.toString) => BitcoindVersion.V17 + case _: String if foundVersion.startsWith(BitcoindVersion.V18.toString) => + BitcoindVersion.V18 case _: String => BitcoindVersion.Unknown } } @@ -95,6 +105,7 @@ object BitcoindInstance { val path = cmd new File(path.trim) + } /** Constructs a `bitcoind` instance from the given datadir, using the diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/jsonmodels/NetworkResult.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/jsonmodels/NetworkResult.scala index 0d212460dd7d..ba638ec31047 100644 --- a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/jsonmodels/NetworkResult.scala +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/jsonmodels/NetworkResult.scala @@ -4,6 +4,9 @@ import java.net.URI import org.bitcoins.core.currency.Bitcoins import org.bitcoins.core.number.{UInt32, UInt64} +import org.bitcoins.core.wallet.fee.SatoshisPerKiloByte + +import scala.concurrent.duration.FiniteDuration sealed abstract class NetworkResult @@ -72,7 +75,8 @@ case class Peer( inflight: Vector[Int], whitelisted: Boolean, bytessent_per_msg: Map[String, Int], - bytesrecv_per_msg: Map[String, Int]) + bytesrecv_per_msg: Map[String, Int], + minfeefilter: Option[SatoshisPerKiloByte]) extends NetworkResult case class PeerNetworkInfo( @@ -98,3 +102,10 @@ case class NodeBan( ban_created: UInt32, ban_reason: String) extends NetworkResult + +final case class GetNodeAddressesResult( + time: FiniteDuration, + services: Int, + address: java.net.URI, + port: Int +) extends NetworkResult diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/jsonmodels/OtherResult.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/jsonmodels/OtherResult.scala index 82d64bc5409a..cd9f004b677f 100644 --- a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/jsonmodels/OtherResult.scala +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/jsonmodels/OtherResult.scala @@ -9,6 +9,7 @@ import org.bitcoins.core.crypto.{ import org.bitcoins.core.currency.Satoshis import org.bitcoins.core.number.UInt32 import org.bitcoins.core.protocol.BitcoinAddress +import org.bitcoins.core.protocol.blockchain.BlockHeader import org.bitcoins.core.protocol.script.ScriptPubKey import org.bitcoins.core.protocol.transaction.Transaction import org.bitcoins.core.wallet.fee.BitcoinFeeUnit @@ -53,8 +54,8 @@ case class BlockTransaction( case class GetMiningInfoResult( blocks: Int, - currentblockweight: Int, - currentblocktx: Int, + currentblockweight: Option[Int], + currentblocktx: Option[Int], difficulty: BigDecimal, networkhashps: BigDecimal, pooledtx: Int, @@ -99,8 +100,6 @@ trait ValidateAddressResult { @deprecated("Use 'getaddressinfo' instead", since = "0.16") def hex: Option[String] - @deprecated("Use 'getaddressinfo' instead", since = "0.16") - def addresses: Option[Vector[BitcoinAddress]] def sigsrequired: Option[Int] @deprecated("Use 'getaddressinfo' instead", since = "0.16") @@ -117,6 +116,15 @@ trait ValidateAddressResult { @deprecated("Use 'getaddressinfo' instead", since = "0.16") def hdmasterkeyid: Option[Sha256Hash160Digest] + + @deprecated("Use 'getaddressinfo' instead", since = "0.16") + def ischange: Option[Boolean] + + @deprecated("Use 'getaddressinfo' instead", since = "0.16") + def solvable: Option[Boolean] + + @deprecated("Use 'getaddressinfo' instead", since = "0.16") + def desc: Option[String] } case class ValidateAddressResultImpl( @@ -128,13 +136,15 @@ case class ValidateAddressResultImpl( isscript: Option[Boolean], script: Option[String], hex: Option[String], - addresses: Option[Vector[BitcoinAddress]], sigsrequired: Option[Int], pubkey: Option[ECPublicKey], iscompressed: Option[Boolean], account: Option[String], hdkeypath: Option[String], - hdmasterkeyid: Option[Sha256Hash160Digest]) + hdmasterkeyid: Option[Sha256Hash160Digest], + ischange: Option[Boolean], + solvable: Option[Boolean], + desc: Option[String]) extends ValidateAddressResult case class EstimateSmartFeeResult( @@ -148,3 +158,15 @@ case class TestMempoolAcceptResult( allowed: Boolean, rejectReason: Option[String] ) + +final case class DeriveAddressesResult(addresses: Vector[BitcoinAddress]) + extends OtherResult + +final case class GetDescriptorInfoResult( + descriptor: String, + isrange: Boolean, + issolvable: Boolean, + hasprivatekeys: Boolean +) extends OtherResult + +final case class SubmitHeaderResult(header: BlockHeader) extends OtherResult diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/jsonmodels/RawTransactionResult.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/jsonmodels/RawTransactionResult.scala index 48b9fa687f55..10f46a002c9f 100644 --- a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/jsonmodels/RawTransactionResult.scala +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/jsonmodels/RawTransactionResult.scala @@ -8,6 +8,8 @@ import org.bitcoins.core.protocol.transaction.{Transaction, TransactionInput} import org.bitcoins.core.protocol.{BitcoinAddress, P2PKHAddress, P2SHAddress} import org.bitcoins.core.script.ScriptType +import scala.concurrent.duration.FiniteDuration + sealed abstract class RawTransactionResult case class RpcTransaction( @@ -91,3 +93,12 @@ case class SignRawTransactionError( sequence: UInt32, error: String) extends RawTransactionResult + +final case class GetRpcInfoResult( + active_commands: Vector[RpcCommands] +) extends RawTransactionResult + +final case class RpcCommands( + method: String, + duration: FiniteDuration //this time is in microseconds +) extends RawTransactionResult diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/jsonmodels/RpcPsbtResult.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/jsonmodels/RpcPsbtResult.scala index d82d76c07ad9..a0986e8d71e3 100644 --- a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/jsonmodels/RpcPsbtResult.scala +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/jsonmodels/RpcPsbtResult.scala @@ -69,3 +69,23 @@ final case class WalletCreateFundedPsbtResult( fee: Bitcoins, changepos: Int ) extends RpcPsbtResult + +final case class AnalyzePsbtResult( + inputs: Vector[AnalyzePsbtInput], + estimated_vsize: Option[Double], + estimated_feerate: Option[Double], + fee: Option[Bitcoins], + next: String +) extends RpcPsbtResult +final case class AnalyzePsbtInput( + has_utxo: Boolean, + is_final: Boolean, + missing: Option[PsbtMissingData], + next: Option[String] +) extends RpcPsbtResult +final case class PsbtMissingData( + pubkeys: Option[Vector[ECPublicKey]], + signatures: Option[Vector[ECDigitalSignature]], + redeemscript: Option[RpcPsbtScript], + witnessscript: Option[RpcPsbtScript] +) extends RpcPsbtResult diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/jsonmodels/WalletResult.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/jsonmodels/WalletResult.scala index 74f3caa4a1bd..65d3b197021c 100644 --- a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/jsonmodels/WalletResult.scala +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/jsonmodels/WalletResult.scala @@ -72,7 +72,7 @@ case class GetWalletInfoResult( keypoolsize: Int, keypoolsize_hd_internal: Int, paytxfee: BitcoinFeeUnit, - hdmasterkeyid: Sha256Hash160Digest, + hdmasterkeyid: Option[Sha256Hash160Digest], unlocked_until: Option[Int]) extends WalletResult @@ -225,6 +225,14 @@ case class EmbeddedResult( case class LabelResult(name: String, purpose: LabelPurpose) extends WalletResult +final case class ListWalletDirResult( + wallets: Vector[ArrayOfWalletsInput] +) extends WalletResult + +final case class ArrayOfWalletsInput( + name: String +) extends WalletResult + final case class CreateWalletResult( name: String, warning: String diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/serializers/JsonSerializers.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/serializers/JsonSerializers.scala index 13210e1d5f12..6d3e072663b9 100644 --- a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/serializers/JsonSerializers.scala +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/serializers/JsonSerializers.scala @@ -6,7 +6,7 @@ import java.net.{InetAddress, URI} import org.bitcoins.core.crypto._ import org.bitcoins.core.currency.{Bitcoins, Satoshis} import org.bitcoins.core.hd.BIP32Path -import org.bitcoins.core.number.{Int32, UInt32, UInt64} +import org.bitcoins.core.number.{Int32, Int64, UInt32, UInt64} import org.bitcoins.core.protocol.blockchain.{Block, BlockHeader, MerkleBlock} import org.bitcoins.core.protocol.script.{ScriptPubKey, ScriptSignature} import org.bitcoins.core.protocol.transaction.{ @@ -21,7 +21,7 @@ import org.bitcoins.core.protocol.{ P2SHAddress } import org.bitcoins.core.script.ScriptType -import org.bitcoins.core.wallet.fee.BitcoinFeeUnit +import org.bitcoins.core.wallet.fee.{BitcoinFeeUnit, SatoshisPerKiloByte} import org.bitcoins.rpc.client.common.RpcOpts.AddressType import org.bitcoins.rpc.jsonmodels._ import org.bitcoins.rpc.serializers.JsonReaders._ @@ -30,7 +30,10 @@ import java.time.LocalDateTime import play.api.libs.functional.syntax._ import play.api.libs.json._ +import scala.concurrent.duration.DurationLong + object JsonSerializers { + implicit val bigIntReads: Reads[BigInt] = BigIntReads implicit val localDateTimeReads: Reads[LocalDateTime] = LocalDateTimeReads @@ -145,8 +148,17 @@ object JsonSerializers { implicit val networkInfoReads: Reads[GetNetworkInfoResult] = Json.reads[GetNetworkInfoResult] + implicit val satsPerKbReads: Reads[SatoshisPerKiloByte] = + new Reads[SatoshisPerKiloByte] { + + def reads(json: JsValue): JsResult[SatoshisPerKiloByte] = + SerializerUtil.processJsNumber(num => + SatoshisPerKiloByte(Satoshis(Int64(num.toBigInt))))(json) + } + implicit val peerNetworkInfoReads: Reads[PeerNetworkInfo] = Json.reads[PeerNetworkInfo] + implicit val peerReads: Reads[Peer] = ((__ \ "id").read[Int] and __.read[PeerNetworkInfo] and (__ \ "version").read[Int] and @@ -160,7 +172,8 @@ object JsonSerializers { (__ \ "inflight").read[Vector[Int]] and (__ \ "whitelisted").read[Boolean] and (__ \ "bytessent_per_msg").read[Map[String, Int]] and - (__ \ "bytesrecv_per_msg").read[Map[String, Int]])(Peer) + (__ \ "bytesrecv_per_msg").read[Map[String, Int]] and + (__ \ "minfeefilter").readNullable[SatoshisPerKiloByte])(Peer) implicit val nodeBanReads: Reads[NodeBan] = Json.reads[NodeBan] @@ -368,9 +381,54 @@ object JsonSerializers { implicit val rpcPsbtInputReads: Reads[RpcPsbtInput] = RpcPsbtInputReads - implicit val decodePsbtResult: Reads[DecodePsbtResult] = + implicit val decodePsbtResultReads: Reads[DecodePsbtResult] = Json.reads[DecodePsbtResult] + implicit val psbtMissingDataReads: Reads[PsbtMissingData] = + Json.reads[PsbtMissingData] + + implicit val analyzePsbtInputReads: Reads[AnalyzePsbtInput] = + Json.reads[AnalyzePsbtInput] + + implicit val analyzePsbtResultReads: Reads[AnalyzePsbtResult] = + Json.reads[AnalyzePsbtResult] + + implicit val getNodeAddressesReads: Reads[GetNodeAddressesResult] = + Reads[GetNodeAddressesResult] { js => + for { + time <- (js \ "time").validate[Long].map(_.seconds) + services <- (js \ "services").validate[Int] + address <- (js \ "address").validate[URI] + port <- (js \ "port").validate[Int] + } yield GetNodeAddressesResult(time, services, address, port) + } + + implicit val rgetpcCommandsReads: Reads[RpcCommands] = Reads[RpcCommands] { + js => + for { + method <- (js \ "method").validate[String] + duration <- (js \ "duration").validate[Long].map(_.microseconds) + } yield RpcCommands(method, duration) + } + + implicit val getRpcInfoResultReads: Reads[GetRpcInfoResult] = + Json.reads[GetRpcInfoResult] + + implicit val arrayOfWalletsInputReads: Reads[ArrayOfWalletsInput] = + Json.reads[ArrayOfWalletsInput] + + implicit val listWalletsDirResultReads: Reads[ListWalletDirResult] = + Json.reads[ListWalletDirResult] + + implicit val deriveAddressesResultReads: Reads[DeriveAddressesResult] = + Json.reads[DeriveAddressesResult] + + implicit val submitHeaderResultReads: Reads[SubmitHeaderResult] = + Json.reads[SubmitHeaderResult] + + implicit val getDescriptorInfoResultReads: Reads[GetDescriptorInfoResult] = + Json.reads[GetDescriptorInfoResult] + implicit val walletCreateFundedPsbtResultReads: Reads[ WalletCreateFundedPsbtResult] = Json.reads[WalletCreateFundedPsbtResult] @@ -400,4 +458,5 @@ object JsonSerializers { implicit val outputMapWrites: Writes[Map[BitcoinAddress, Bitcoins]] = mapWrites[BitcoinAddress, Bitcoins](_.value) + } diff --git a/eclair-rpc/src/main/scala/org/bitcoins/eclair/rpc/json/JsonReaders.scala b/eclair-rpc/src/main/scala/org/bitcoins/eclair/rpc/json/JsonReaders.scala index 493625228e9b..f656180f83cc 100644 --- a/eclair-rpc/src/main/scala/org/bitcoins/eclair/rpc/json/JsonReaders.scala +++ b/eclair-rpc/src/main/scala/org/bitcoins/eclair/rpc/json/JsonReaders.scala @@ -16,8 +16,10 @@ import scala.util.{Failure, Success} object JsonReaders { import org.bitcoins.rpc.serializers.JsonReaders._ - implicit val feeProportionalMillionthsReads: Reads[FeeProportionalMillionths] = Reads { js => - SerializerUtil.processJsNumberBigInt(FeeProportionalMillionths.fromBigInt)(js) + implicit val feeProportionalMillionthsReads: Reads[ + FeeProportionalMillionths] = Reads { js => + SerializerUtil.processJsNumberBigInt(FeeProportionalMillionths.fromBigInt)( + js) } implicit val channelStateReads: Reads[ChannelState] = { @@ -85,7 +87,8 @@ object JsonReaders { implicit val shortChannelIdReads: Reads[ShortChannelId] = { Reads { jsValue => - SerializerUtil.processJsString(ShortChannelId.fromHumanReadableString)(jsValue) + SerializerUtil.processJsString(ShortChannelId.fromHumanReadableString)( + jsValue) } } @@ -120,14 +123,13 @@ object JsonReaders { paymentHash <- (jsValue \ "paymentHash").validate[Sha256Digest] expiry <- (jsValue \ "expiry").validate[Long] } yield - InvoiceResult( - prefix, - timestamp.seconds, - nodeId, - serialized, - description, - paymentHash, - expiry.seconds) + InvoiceResult(prefix, + timestamp.seconds, + nodeId, + serialized, + description, + paymentHash, + expiry.seconds) } } 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 b6e838d16e7f..e32a5f838533 100644 --- a/testkit/src/main/scala/org/bitcoins/testkit/rpc/BitcoindRpcTestUtil.scala +++ b/testkit/src/main/scala/org/bitcoins/testkit/rpc/BitcoindRpcTestUtil.scala @@ -29,6 +29,7 @@ import org.bitcoins.rpc.client.common.{ } import org.bitcoins.rpc.client.v16.BitcoindV16RpcClient import org.bitcoins.rpc.client.v17.BitcoindV17RpcClient +import org.bitcoins.rpc.client.v18.BitcoindV18RpcClient import org.bitcoins.rpc.config.{ BitcoindAuthCredentials, BitcoindInstance, @@ -45,6 +46,7 @@ import org.bitcoins.util.ListUtil import scala.annotation.tailrec import scala.collection.immutable.Map import scala.collection.mutable +import org.bitcoins.core.compat.JavaConverters._ import scala.concurrent._ import scala.concurrent.duration.{DurationInt, FiniteDuration} import scala.util._ @@ -57,9 +59,11 @@ import java.nio.file.Path import org.bitcoins.rpc.client.common.BitcoindVersion.Unknown import org.bitcoins.rpc.client.common.BitcoindVersion.V16 import org.bitcoins.rpc.client.common.BitcoindVersion.V17 +import org.bitcoins.rpc.client.common.BitcoindVersion.V18 import java.nio.file.Files import org.bitcoins.testkit.util.FileUtil +import org.bitcoins.rpc.BitcoindException //noinspection AccessorLikeMethodIsEmptyParen trait BitcoindRpcTestUtil extends BitcoinSLogger { @@ -171,8 +175,7 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger { private def getBinary(version: BitcoindVersion): File = version match { // default to newest version case Unknown => getBinary(BitcoindVersion.newest) - case known @ (V16 | V17) => - import org.bitcoins.core.compat.JavaConverters._ + case known @ (V16 | V17 | V18) => val versionFolder = Files .list(binaryDirectory) .iterator() @@ -194,7 +197,7 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger { versionFolder .resolve("bin") - .resolve("bitcoind") + .resolve(if (Properties.isWin) "bitcoind.exe" else "bitcoind") .toFile() } @@ -266,6 +269,18 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger { pruneMode = pruneMode, versionOpt = Some(BitcoindVersion.V17)) + def v18Instance( + 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.V18)) + def startServers(servers: Vector[BitcoindRpcClient])( implicit ec: ExecutionContext): Future[Unit] = { val startedServers = servers.map(_.start()) @@ -391,7 +406,8 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger { import system.dispatcher for { - hashes <- clients.head.generate(blocks) + address <- clients.head.getNewAddress + hashes <- clients.head.generateToAddress(blocks, address) _ <- { val pairs = ListUtil.uniquePairs(clients) val syncFuts = pairs.map { @@ -455,11 +471,10 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger { .getAddedNodeInfo(to.getDaemon.uri) .map(info => info.isEmpty || info.head.connected.contains(false)) .recoverWith { - case exception: Exception + case exception: BitcoindException if exception.getMessage.contains("Node has not been added") => - from.getPeerInfo.map { peerInfo => - peerInfo.forall(_.networkInfo.addr != to.instance.uri) - } + from.getPeerInfo.map( + _.forall(_.networkInfo.addr != to.instance.uri)) } } @@ -554,6 +569,9 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger { case BitcoindVersion.V17 => BitcoindV17RpcClient.withActorSystem( BitcoindRpcTestUtil.v17Instance()) + case BitcoindVersion.V18 => + BitcoindV18RpcClient.withActorSystem( + BitcoindRpcTestUtil.v18Instance()) } // this is safe as long as this method is never @@ -619,6 +637,15 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger { (BitcoindV17RpcClient, BitcoindV17RpcClient)] = createNodePairInternal(BitcoindVersion.V17, clientAccum) + /** + * Returns a pair of [[org.bitcoins.rpc.client.v18.BitcoindV18RpcClient BitcoindV18RpcClient]] + * that are connected with some blocks in the chain + */ + def createNodePairV18(clientAccum: RpcClientAccum = Vector.newBuilder)( + implicit system: ActorSystem): Future[ + (BitcoindV18RpcClient, BitcoindV18RpcClient)] = + createNodePairInternal(BitcoindVersion.V18, clientAccum) + /** * Returns a triple of [[org.bitcoins.rpc.client.common.BitcoindRpcClient BitcoindRpcClient]] * that are connected with some blocks in the chain @@ -664,7 +691,8 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger { amount: Bitcoins = Bitcoins(1))( implicit executionContext: ExecutionContext): Future[Transaction] = { for { - blocks <- sender.generate(2) + address <- sender.getNewAddress + blocks <- sender.generateToAddress(2, address) block0 <- sender.getBlock(blocks(0)) block1 <- sender.getBlock(blocks(1)) transaction0 <- sender.getTransaction(block0.tx(0)) @@ -707,17 +735,18 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger { case unknown: BitcoindRpcClient => val v16T = BitcoindV16RpcClient.fromUnknownVersion(unknown) val v17T = BitcoindV17RpcClient.fromUnknownVersion(unknown) - (v16T, v17T) match { - case (Failure(_), Failure(_)) => - throw new RuntimeException( - "Could not figure out version of provided bitcoind RPC client!") - case (Success(_), Success(_)) => + val v18T = BitcoindV18RpcClient.fromUnknownVersion(unknown) + (v16T, v17T, v18T) match { + case (Failure(_), Failure(_), Failure(_)) => throw new RuntimeException( - "This should not happen, managed to construct different versioned RPC clients from one single client") - case (Success(v16), Failure(_)) => + "Could not figure out version of provided bitcoind RPC client!" + + "This should not happen, managed to construct different versioned RPC clients from one single client") + case (Success(v16), _, _) => v16.signRawTransaction(transaction, utxoDeps) - case (Failure(_), Success(v17)) => + case (_, Success(v17), _) => v17.signRawTransactionWithWallet(transaction, utxoDeps) + case (_, _, Success(v18)) => + v18.signRawTransactionWithWallet(transaction, utxoDeps) } } @@ -752,19 +781,16 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger { implicit val materializer: ActorMaterializer = ActorMaterializer.create(actorSystem) implicit val ec: ExecutionContextExecutor = materializer.executionContext - createRawCoinbaseTransaction(sender, receiver, amount) - .flatMap(signRawTransaction(sender, _)) - .flatMap { signedTransaction => - sender - .generate(100) - .flatMap { _ => // Can't spend coinbase until depth 100 - sender - .sendRawTransaction(signedTransaction.hex, allowHighFees = true) - .flatMap { transactionHash => - sender.getTransaction(transactionHash) - } - } - } + for { + rawcoinbasetx <- createRawCoinbaseTransaction(sender, receiver, amount) + signedtx <- signRawTransaction(sender, rawcoinbasetx) + addr <- sender.getNewAddress + _ <- sender.generateToAddress(100, addr) + // Can't spend coinbase until depth 100 + transactionHash <- sender.sendRawTransaction(signedtx.hex, + allowHighFees = true) + transaction <- sender.getTransaction(transactionHash) + } yield transaction } /** @@ -792,7 +818,7 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger { implicit val mat: ActorMaterializer = ActorMaterializer.create(system) implicit val ec: ExecutionContextExecutor = mat.executionContext fundMemPoolTransaction(sender, address, amount).flatMap { txid => - sender.generate(1).map { _ => + sender.getNewAddress.flatMap(sender.generateToAddress(1, _)).map { _ => txid } } @@ -872,7 +898,7 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger { //fund the wallet by generating 102 blocks, need this to get over coinbase maturity val generatedF = startedF.flatMap { _ => clientAccum += rpc - rpc.generate(blocksToGenerate) + rpc.getNewAddress.flatMap(rpc.generateToAddress(blocksToGenerate, _)) } def areBlocksGenerated(): Future[Boolean] = {