Use PSBT type in bitcoind calls (#2242)

This commit is contained in:
Ben Carman 2020-11-09 08:50:48 -06:00 committed by GitHub
parent e4194220c1
commit e830547773
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 158 additions and 153 deletions

View file

@ -4,18 +4,19 @@ import org.bitcoins.core.currency.Bitcoins
import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.script.ScriptPubKey
import org.bitcoins.core.protocol.transaction.Transaction
import org.bitcoins.core.psbt.PSBT
import org.bitcoins.core.script.ScriptType
import org.bitcoins.core.script.crypto.HashType
import org.bitcoins.crypto.{ECDigitalSignature, ECPublicKey}
sealed abstract class RpcPsbtResult
final case class WalletProcessPsbtResult(psbt: String, complete: Boolean)
final case class WalletProcessPsbtResult(psbt: PSBT, complete: Boolean)
extends RpcPsbtResult
sealed abstract class FinalizePsbtResult extends RpcPsbtResult
final case class FinalizedPsbt(hex: Transaction) extends FinalizePsbtResult
final case class NonFinalizedPsbt(psbt: String) extends FinalizePsbtResult
final case class NonFinalizedPsbt(psbt: PSBT) extends FinalizePsbtResult
final case class DecodePsbtResult(
tx: RpcTransaction,
@ -67,7 +68,7 @@ final case class RpcPsbtOutput(
) extends RpcPsbtResult
final case class WalletCreateFundedPsbtResult(
psbt: String, // todo change me
psbt: PSBT,
fee: Bitcoins,
changepos: Int
) extends RpcPsbtResult

View file

@ -34,6 +34,7 @@ import org.bitcoins.core.protocol.{
P2PKHAddress,
P2SHAddress
}
import org.bitcoins.core.psbt.PSBT
import org.bitcoins.core.script.ScriptType
import org.bitcoins.core.script.crypto.HashType
import org.bitcoins.core.wallet.fee.{BitcoinFeeUnit, SatoshisPerByte}
@ -394,6 +395,12 @@ object JsonReaders {
SerializerUtil.processJsString[Transaction](Transaction.fromHex)(json)
}
implicit object PSBTReads extends Reads[PSBT] {
override def reads(json: JsValue): JsResult[PSBT] =
SerializerUtil.processJsString[PSBT](PSBT.fromString)(json)
}
implicit object TransactionOutPointReads extends Reads[TransactionOutPoint] {
private case class OutPoint(txid: DoubleSha256DigestBE, vout: UInt32)
@ -479,7 +486,7 @@ object JsonReaders {
if ((json \ "hex").isDefined) {
JsError("PSBT was submitted as a serialized hex transaction!")
} else {
(json \ "psbt").validate[String].map(NonFinalizedPsbt)
(json \ "psbt").validate[PSBT].map(NonFinalizedPsbt)
}
}

View file

@ -28,6 +28,7 @@ import org.bitcoins.core.psbt.{
OutputPSBTRecord
}
import org.bitcoins.core.script.constant.ScriptToken
import org.bitcoins.core.psbt.PSBT
import org.bitcoins.crypto._
import play.api.libs.functional.syntax._
import play.api.libs.json._
@ -75,6 +76,7 @@ object JsonSerializers {
implicit val bitcoinAddressReads: Reads[BitcoinAddress] = BitcoinAddressReads
implicit val merkleBlockReads: Reads[MerkleBlock] = MerkleBlockReads
implicit val transactionReads: Reads[Transaction] = TransactionReads
implicit val psbtReads: Reads[PSBT] = PSBTReads
implicit val transactionOutPointReads: Reads[TransactionOutPoint] =
TransactionOutPointReads

View file

@ -10,11 +10,7 @@ import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.ln.currency.MilliSatoshis
import org.bitcoins.core.protocol.script.{ScriptPubKey, WitnessScriptPubKey}
import org.bitcoins.core.protocol.transaction.{Transaction, TransactionInput}
import org.bitcoins.core.psbt.{
GlobalPSBTRecord,
InputPSBTRecord,
OutputPSBTRecord
}
import org.bitcoins.core.psbt._
import org.bitcoins.core.script.crypto._
import org.bitcoins.core.util.BytesUtil
import org.bitcoins.crypto.{DoubleSha256Digest, DoubleSha256DigestBE}
@ -90,6 +86,10 @@ object JsonWriters {
override def writes(o: Transaction): JsValue = JsString(o.hex)
}
implicit object PSBTWrites extends Writes[PSBT] {
override def writes(o: PSBT): JsValue = JsString(o.base64)
}
implicit def mapWrites[K, V](keyString: K => String)(implicit
vWrites: Writes[V]): Writes[Map[K, V]] =
new Writes[Map[K, V]] {

View file

@ -12,6 +12,7 @@ import org.bitcoins.core.protocol.transaction.{
TransactionInput,
TransactionOutPoint
}
import org.bitcoins.core.psbt.PSBT
import org.bitcoins.rpc.client.v17.BitcoindV17RpcClient
import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil
import org.bitcoins.testkit.util.BitcoindRpcTest
@ -37,7 +38,7 @@ class PsbtRpcTest extends BitcoindRpcTest {
"cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAEBIJVe6gsAAAAAF6kUY0UgD2jRieGtwN8cTRbqjxTA2+uHIgIDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUZGMEMCIAQktY7/qqaU4VWepck7v9SokGQiQFXN8HC2dxRpRC0HAh9cjrD+plFtYLisszrWTt5g6Hhb+zqpS5m9+GFR25qaAQEEIgAgdx/RitRZZm3Unz1WTj28QvTIR3TjYK2haBao7UiNVoEBBUdSIQOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RiED3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg71SriIGA7E0HMunaDtq9PEjjNbpfnFn1Wn6xH8eSNR1QYRDVb1GELSmumcAAACAAAAAgAQAAIAiBgPeVdHh2sgF4/iljB+/m5TALz26r+En/vykmV8m+CCDvRC0prpnAAAAgAAAAIAFAACAAAA=",
"cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAACg8BAgMEBQYHCAkPAQIDBAUGBwgJCgsMDQ4PAAA=",
"cHNidP8BACoCAAAAAAFAQg8AAAAAABepFG6Rty1Vk+fUOR4v9E6R6YXDFkHwhwAAAAAAAA==" // this one is from Core
)
).map(PSBT.fromBase64)
for {
(client, _, _) <- clientsF
@ -53,7 +54,7 @@ class PsbtRpcTest extends BitcoindRpcTest {
client.createRawTransaction(Vector.empty, Map(address -> Bitcoins.one))
fundedRawTx <- client.fundRawTransaction(rawTx)
psbt <- client.convertToPsbt(fundedRawTx.hex)
processedPsbt <- client.walletProcessPsbt(psbt)
processedPsbt <- client.walletProcessPSBT(psbt)
decoded <- client.decodePsbt(processedPsbt.psbt)
} yield {
assert(decoded.inputs.exists(inputs =>
@ -75,7 +76,7 @@ class PsbtRpcTest extends BitcoindRpcTest {
psbt <-
client.createPsbt(Vector(TransactionInput.fromTxidAndVout(txid, vout)),
Map(newAddr -> Bitcoins(0.5)))
processed <- client.walletProcessPsbt(psbt)
processed <- client.walletProcessPSBT(psbt)
finalized <- client.finalizePsbt(processed.psbt)
} yield finalized match {
case _: FinalizedPsbt => succeed
@ -119,11 +120,11 @@ class PsbtRpcTest extends BitcoindRpcTest {
thirdClient.createPsbt(inputs, Map(newAddr -> Bitcoins(1.5)))
}
// Update psbts, should only have data for one input and not the other
clientProcessedPsbt <- client.walletProcessPsbt(psbt).map(_.psbt)
clientProcessedPsbt <- client.walletProcessPSBT(psbt).map(_.psbt)
otherClientProcessedPsbt <-
otherClient
.walletProcessPsbt(psbt)
.walletProcessPSBT(psbt)
.map(_.psbt)
// Combine and finalize the psbts

View file

@ -1,6 +1,7 @@
package org.bitcoins.rpc.v18
import org.bitcoins.core.currency.Bitcoins
import org.bitcoins.core.psbt.PSBT
import org.bitcoins.rpc.client.v18.BitcoindV18RpcClient
import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil
import org.bitcoins.testkit.util.BitcoindRpcTest
@ -23,10 +24,12 @@ class PsbtRpcTest extends BitcoindRpcTest {
behavior of "PsbtRpc"
it should "return something when analyzePsbt is called" 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="
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=")
val resultF = client.analyzePsbt(PSBT.fromBase64(psbt))
resultF.map { result =>
val inputs = result.inputs
assert(inputs.nonEmpty)
@ -38,7 +41,8 @@ class PsbtRpcTest extends BitcoindRpcTest {
val psbt =
"cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEA3wIAAAABJoFxNx7f8oXpN63upLN7eAAMBWbLs61kZBcTykIXG/YAAAAAakcwRAIgcLIkUSPmv0dNYMW1DAQ9TGkaXSQ18Jo0p2YqncJReQoCIAEynKnazygL3zB0DsA5BCJCLIHLRYOUV663b8Eu3ZWzASECZX0RjTNXuOD0ws1G23s59tnDjZpwq8ubLeXcjb/kzjH+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIACICAurVlmh8qAYEPtw94RbN8p1eklfBls0FXPaYyNAr8k6ZELSmumcAAACAAAAAgAIAAIAAIgIDlPYr6d8ZlSxVh3aK63aYBhrSxKJciU9H2MFitNchPQUQtKa6ZwAAAIABAACAAgAAgAA="
val analyzedF = clientF.flatMap(client => client.analyzePsbt(psbt))
val analyzedF =
clientF.flatMap(client => client.analyzePsbt(PSBT.fromBase64(psbt)))
analyzedF.map { result =>
assert(result.inputs.exists(_.next.isDefined))
@ -57,7 +61,8 @@ class PsbtRpcTest extends BitcoindRpcTest {
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 analyzedF =
clientF.flatMap(client => client.analyzePsbt(PSBT.fromBase64(psbt)))
val expectedfee = Bitcoins(0.00090341)
val expectedhasutxo = true
val expectedisfinal = false
@ -81,11 +86,12 @@ class PsbtRpcTest extends BitcoindRpcTest {
//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=="
PSBT.fromBase64(
"cHNidP8BACoCAAAAAAFAQg8AAAAAABepFG6Rty1Vk+fUOR4v9E6R6YXDFkHwhwAAAAAAAA==")
val updatedF = clientF.flatMap(client => client.utxoUpdatePsbt(psbt))
updatedF.map { result =>
assert(result.contains(psbt))
assert(result == psbt)
}
}
@ -97,18 +103,22 @@ class PsbtRpcTest extends BitcoindRpcTest {
*/
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.fromBase64(
"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="
PSBT.fromBase64(
"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(
result ==
//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"))
PSBT.fromBase64(
"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"))
}
}

View file

@ -7,6 +7,7 @@ import org.bitcoins.commons.jsonmodels.bitcoind.{
import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts.WalletFlag
import org.bitcoins.core.config.RegTest
import org.bitcoins.core.gcs.{BlockFilter, FilterType}
import org.bitcoins.core.psbt.PSBT
import org.bitcoins.rpc.client.common.BitcoindVersion
import org.bitcoins.rpc.client.v19.BitcoindV19RpcClient
import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil
@ -124,13 +125,14 @@ class BitcoindV19RpcClientTest extends BitcoindRpcTest {
"pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)"
val psbt =
"cHNidP8BACoCAAAAAAFAQg8AAAAAABepFG6Rty1Vk+fUOR4v9E6R6YXDFkHwhwAAAAAAAA=="
PSBT.fromBase64(
"cHNidP8BACoCAAAAAAFAQg8AAAAAABepFG6Rty1Vk+fUOR4v9E6R6YXDFkHwhwAAAAAAAA==")
for {
(client, _) <- clientPairF
result <- client.utxoUpdatePsbt(psbt, Seq(descriptor))
} yield {
assert(result.contains(psbt))
assert(result == psbt)
}
}
}

View file

@ -8,6 +8,7 @@ import org.bitcoins.commons.jsonmodels.bitcoind._
import org.bitcoins.core.config.RegTest
import org.bitcoins.core.gcs.{BlockFilter, FilterType}
import org.bitcoins.core.protocol.transaction.EmptyTransaction
import org.bitcoins.core.psbt.PSBT
import org.bitcoins.crypto.ECPublicKey
import org.bitcoins.rpc.client.common.BitcoindVersion
import org.bitcoins.rpc.client.v20.BitcoindV20RpcClient
@ -120,13 +121,14 @@ class BitcoindV20RpcClientTest extends BitcoindRpcTest {
"pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)"
val psbt =
"cHNidP8BACoCAAAAAAFAQg8AAAAAABepFG6Rty1Vk+fUOR4v9E6R6YXDFkHwhwAAAAAAAA=="
PSBT.fromBase64(
"cHNidP8BACoCAAAAAAFAQg8AAAAAABepFG6Rty1Vk+fUOR4v9E6R6YXDFkHwhwAAAAAAAA==")
for {
(client, _) <- clientPairF
result <- client.utxoUpdatePsbt(psbt, Seq(descriptor))
} yield {
assert(result.contains(psbt))
assert(result == psbt)
}
}

View file

@ -59,6 +59,7 @@ class BitcoindRpcClient(val instance: BitcoindInstance)(implicit
with TransactionRpc
with UTXORpc
with WalletRpc
with PsbtRpc
with UtilRpc {
override def version: BitcoindVersion = BitcoindVersion.Unknown

View file

@ -1,8 +1,17 @@
package org.bitcoins.rpc.client.common
import org.bitcoins.commons.jsonmodels.bitcoind.AnalyzePsbtResult
import org.bitcoins.commons.jsonmodels.bitcoind.{
AnalyzePsbtResult,
DecodePsbtResult,
FinalizePsbtResult
}
import org.bitcoins.commons.serializers.JsonSerializers._
import play.api.libs.json.{JsString, Json}
import org.bitcoins.commons.serializers.JsonWriters._
import org.bitcoins.core.currency.{Bitcoins, CurrencyUnit}
import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.transaction.{Transaction, TransactionInput}
import org.bitcoins.core.psbt.PSBT
import play.api.libs.json._
import scala.concurrent.Future
@ -16,21 +25,64 @@ import scala.concurrent.Future
trait PsbtRpc {
self: Client =>
def analyzePsbt(psbt: String): Future[AnalyzePsbtResult] = {
bitcoindCall[AnalyzePsbtResult]("analyzepsbt", List(JsString(psbt)))
def analyzePsbt(psbt: PSBT): Future[AnalyzePsbtResult] = {
bitcoindCall[AnalyzePsbtResult]("analyzepsbt", List(JsString(psbt.base64)))
}
def joinPsbts(txs: Seq[String]): Future[String] = {
bitcoindCall[String]("joinpsbts", List(Json.toJson(txs)))
def joinPsbts(psbts: Seq[PSBT]): Future[PSBT] = {
bitcoindCall[PSBT]("joinpsbts", List(Json.toJson(psbts)))
}
def utxoUpdatePsbt(psbt: String): Future[String] = {
bitcoindCall[String]("utxoupdatepsbt", List(JsString(psbt)))
def utxoUpdatePsbt(psbt: PSBT): Future[PSBT] = {
bitcoindCall[PSBT]("utxoupdatepsbt", List(JsString(psbt.base64)))
}
def utxoUpdatePsbt(psbt: String, descriptors: Seq[String]): Future[String] = {
bitcoindCall[String]("utxoupdatepsbt",
List(JsString(psbt), Json.toJson(descriptors)))
def utxoUpdatePsbt(psbt: PSBT, descriptors: Seq[String]): Future[PSBT] = {
bitcoindCall[PSBT]("utxoupdatepsbt",
List(JsString(psbt.base64), Json.toJson(descriptors)))
}
def convertToPsbt(
rawTx: Transaction,
permitSigData: Boolean = false,
isWitness: Option[Boolean] = None): Future[PSBT] = {
val firstArgs: List[JsValue] =
List(Json.toJson(rawTx), JsBoolean(permitSigData))
val args: List[JsValue] = firstArgs ++ isWitness.map(Json.toJson(_)).toList
bitcoindCall[PSBT]("converttopsbt", args)
}
def createPsbt(
inputs: Vector[TransactionInput],
outputs: Map[BitcoinAddress, CurrencyUnit],
locktime: Int = 0,
replacable: Boolean = false): Future[PSBT] = {
val outputsJson =
Json.toJson {
outputs.map {
case (addr, curr) => addr -> Bitcoins(curr.satoshis)
}
}
bitcoindCall[PSBT]("createpsbt",
List(Json.toJson(inputs),
outputsJson,
JsNumber(locktime),
JsBoolean(replacable)))
}
def combinePsbt(psbts: Vector[PSBT]): Future[PSBT] = {
bitcoindCall[PSBT]("combinepsbt", List(Json.toJson(psbts)))
}
def finalizePsbt(
psbt: PSBT,
extract: Boolean = true): Future[FinalizePsbtResult] = {
bitcoindCall[FinalizePsbtResult](
"finalizepsbt",
List(JsString(psbt.base64), JsBoolean(extract)))
}
def decodePsbt(psbt: PSBT): Future[DecodePsbtResult] =
bitcoindCall[DecodePsbtResult]("decodepsbt", List(Json.toJson(psbt)))
}

View file

@ -2,6 +2,7 @@ package org.bitcoins.rpc.client.common
import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts.{
AddressType,
WalletCreateFundedPsbtOptions,
WalletFlag
}
import org.bitcoins.commons.jsonmodels.bitcoind._
@ -11,7 +12,9 @@ import org.bitcoins.core.crypto.ECPrivateKeyUtil
import org.bitcoins.core.currency.{Bitcoins, CurrencyUnit}
import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.blockchain.MerkleBlock
import org.bitcoins.core.protocol.transaction.Transaction
import org.bitcoins.core.protocol.transaction.{Transaction, TransactionInput}
import org.bitcoins.core.psbt.PSBT
import org.bitcoins.core.script.crypto.HashType
import org.bitcoins.crypto.{
DoubleSha256Digest,
DoubleSha256DigestBE,
@ -475,4 +478,39 @@ trait WalletRpc { self: Client =>
uriExtensionOpt = walletNameOpt.map(walletExtension)
)
}
def walletProcessPSBT(
psbt: PSBT,
sign: Boolean = true,
sigHashType: HashType = HashType.sigHashAll,
walletNameOpt: Option[String] = None): Future[WalletProcessPsbtResult] = {
bitcoindCall[WalletProcessPsbtResult](
"walletprocesspsbt",
List(JsString(psbt.base64), JsBoolean(sign), Json.toJson(sigHashType)),
uriExtensionOpt = walletNameOpt.map(walletExtension)
)
}
def walletCreateFundedPsbt(
inputs: Vector[TransactionInput],
outputs: Map[BitcoinAddress, CurrencyUnit],
locktime: Int = 0,
options: WalletCreateFundedPsbtOptions = WalletCreateFundedPsbtOptions(),
bip32derivs: Boolean = false,
walletNameOpt: Option[String] = None
): Future[WalletCreateFundedPsbtResult] = {
val jsonOutputs =
Json.toJson {
outputs.map { case (addr, curr) => addr -> Bitcoins(curr.satoshis) }
}
bitcoindCall[WalletCreateFundedPsbtResult](
"walletcreatefundedpsbt",
List(Json.toJson(inputs),
jsonOutputs,
JsNumber(locktime),
Json.toJson(options),
Json.toJson(bip32derivs)),
uriExtensionOpt = walletNameOpt.map(walletExtension)
)
}
}

View file

@ -34,8 +34,7 @@ import scala.util.Try
class BitcoindV17RpcClient(override val instance: BitcoindInstance)(implicit
actorSystem: ActorSystem)
extends BitcoindRpcClient(instance)
with V17LabelRpc
with V17PsbtRpc {
with V17LabelRpc {
override def version: BitcoindVersion = BitcoindVersion.V17

View file

@ -1,110 +0,0 @@
package org.bitcoins.rpc.client.v17
import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts.WalletCreateFundedPsbtOptions
import org.bitcoins.commons.jsonmodels.bitcoind.{
DecodePsbtResult,
FinalizePsbtResult,
WalletCreateFundedPsbtResult,
WalletProcessPsbtResult
}
import org.bitcoins.commons.serializers.JsonSerializers._
import org.bitcoins.commons.serializers.JsonWriters._
import org.bitcoins.core.currency.{Bitcoins, CurrencyUnit}
import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.transaction.{Transaction, TransactionInput}
import org.bitcoins.core.script.crypto.HashType
import org.bitcoins.rpc.client.common.Client
import play.api.libs.json._
import scala.concurrent.Future
/**
* RPC calls related to PSBT (partially signed bitcoin transactions)
* handling in Bitcoin Core.
*
* @note The PSBT format is currently not supported by Bitcoin-S.
* Therefore raw strings are returned in several of these
* RPCs.
*
* @see [[https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki BIP174]] on PSBTs
* and [[https://github.com/bitcoin/bitcoin/blob/master/doc/psbt.md PSBT Howto for Bitcoin Core]]
*/
trait V17PsbtRpc { self: Client =>
def convertToPsbt(
rawTx: Transaction,
permitSigData: Boolean = false,
isWitness: Option[Boolean] = None): Future[String] = {
val firstArgs: List[JsValue] =
List(Json.toJson(rawTx), JsBoolean(permitSigData))
val args: List[JsValue] = firstArgs ++ isWitness.map(Json.toJson(_)).toList
bitcoindCall[String]("converttopsbt", args)
}
def createPsbt(
inputs: Vector[TransactionInput],
outputs: Map[BitcoinAddress, CurrencyUnit],
locktime: Int = 0,
replacable: Boolean = false): Future[String] = {
val outputsJson =
Json.toJson {
outputs.map {
case (addr, curr) => addr -> Bitcoins(curr.satoshis)
}
}
bitcoindCall[String]("createpsbt",
List(Json.toJson(inputs),
outputsJson,
JsNumber(locktime),
JsBoolean(replacable)))
}
def combinePsbt(psbts: Vector[String]): Future[String] = {
bitcoindCall[String]("combinepsbt", List(Json.toJson(psbts)))
}
def finalizePsbt(
psbt: String,
extract: Boolean = true): Future[FinalizePsbtResult] = {
bitcoindCall[FinalizePsbtResult]("finalizepsbt",
List(JsString(psbt), JsBoolean(extract)))
}
def walletCreateFundedPsbt(
inputs: Vector[TransactionInput],
outputs: Map[BitcoinAddress, CurrencyUnit],
locktime: Int = 0,
options: WalletCreateFundedPsbtOptions = WalletCreateFundedPsbtOptions(),
bip32derivs: Boolean = false
): Future[WalletCreateFundedPsbtResult] = {
val jsonOutputs =
Json.toJson {
outputs.map { case (addr, curr) => addr -> Bitcoins(curr.satoshis) }
}
bitcoindCall[WalletCreateFundedPsbtResult](
"walletcreatefundedpsbt",
List(Json.toJson(inputs),
jsonOutputs,
JsNumber(locktime),
Json.toJson(options),
Json.toJson(bip32derivs))
)
}
def walletProcessPsbt(
psbt: String,
sign: Boolean = true,
sighashType: HashType = HashType.sigHashAll,
bip32Derivs: Boolean = false
): Future[WalletProcessPsbtResult] = {
val args = List(JsString(psbt),
JsBoolean(sign),
Json.toJson(sighashType),
JsBoolean(bip32Derivs))
bitcoindCall[WalletProcessPsbtResult]("walletprocesspsbt", args)
}
def decodePsbt(psbt: String): Future[DecodePsbtResult] =
bitcoindCall[DecodePsbtResult]("decodepsbt", List(Json.toJson(psbt)))
}