2018-06-28 02:05:54 +02:00
#!/usr/bin/env python3
2019-02-21 02:03:13 +01:00
# Copyright (c) 2018-2019 The Bitcoin Core developers
2018-06-28 02:05:54 +02:00
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
""" Test the Partially Signed Transaction RPCs.
"""
2018-08-01 02:58:01 +02:00
from decimal import Decimal
2018-06-28 02:05:54 +02:00
from test_framework . test_framework import BitcoinTestFramework
2019-04-07 00:38:51 +02:00
from test_framework . util import (
assert_equal ,
2019-06-29 04:44:38 +02:00
assert_greater_than ,
2019-04-07 00:38:51 +02:00
assert_raises_rpc_error ,
2019-08-27 21:13:56 +02:00
connect_nodes ,
2019-04-07 00:38:51 +02:00
disconnect_nodes ,
find_output ,
)
2018-07-07 00:10:35 +02:00
import json
import os
2018-06-28 02:05:54 +02:00
2018-08-15 03:52:16 +02:00
MAX_BIP125_RBF_SEQUENCE = 0xfffffffd
2018-06-28 02:05:54 +02:00
# Create one-input, one-output, no-fee transaction:
class PSBTTest ( BitcoinTestFramework ) :
def set_test_params ( self ) :
self . setup_clean_chain = False
self . num_nodes = 3
2019-04-27 19:44:38 +02:00
self . extra_args = [
[ " -walletrbf=1 " ] ,
2019-07-27 19:35:07 +02:00
[ " -walletrbf=0 " ] ,
2019-04-27 19:44:38 +02:00
[ ]
]
2018-06-28 02:05:54 +02:00
2018-09-09 19:32:37 +02:00
def skip_test_if_missing_module ( self ) :
self . skip_if_no_wallet ( )
2018-09-20 20:43:06 +02:00
def test_utxo_conversion ( self ) :
mining_node = self . nodes [ 2 ]
offline_node = self . nodes [ 0 ]
online_node = self . nodes [ 1 ]
# Disconnect offline node from others
disconnect_nodes ( offline_node , 1 )
disconnect_nodes ( online_node , 0 )
disconnect_nodes ( offline_node , 2 )
disconnect_nodes ( mining_node , 0 )
# Mine a transaction that credits the offline address
offline_addr = offline_node . getnewaddress ( address_type = " p2sh-segwit " )
online_addr = online_node . getnewaddress ( address_type = " p2sh-segwit " )
online_node . importaddress ( offline_addr , " " , False )
2019-09-20 17:33:17 +02:00
mining_node . sendtoaddress ( address = offline_addr , amount = 0.02 )
2018-09-20 20:43:06 +02:00
mining_node . generate ( nblocks = 1 )
2019-04-07 00:19:45 +02:00
self . sync_blocks ( [ mining_node , online_node ] )
2018-09-20 20:43:06 +02:00
# Construct an unsigned PSBT on the online node (who doesn't know the output is Segwit, so will include a non-witness UTXO)
utxos = online_node . listunspent ( addresses = [ offline_addr ] )
2019-09-20 17:33:17 +02:00
raw = online_node . createrawtransaction ( [ { " txid " : utxos [ 0 ] [ " txid " ] , " vout " : utxos [ 0 ] [ " vout " ] } ] , [ { online_addr : 0.019998 } ] )
2018-09-20 20:43:06 +02:00
psbt = online_node . walletprocesspsbt ( online_node . converttopsbt ( raw ) ) [ " psbt " ]
2019-02-19 23:43:44 +01:00
assert " non_witness_utxo " in mining_node . decodepsbt ( psbt ) [ " inputs " ] [ 0 ]
2018-09-20 20:43:06 +02:00
# Have the offline node sign the PSBT (which will update the UTXO to segwit)
signed_psbt = offline_node . walletprocesspsbt ( psbt ) [ " psbt " ]
2019-02-19 23:43:44 +01:00
assert " witness_utxo " in mining_node . decodepsbt ( signed_psbt ) [ " inputs " ] [ 0 ]
2018-09-20 20:43:06 +02:00
# Make sure we can mine the resulting transaction
txid = mining_node . sendrawtransaction ( mining_node . finalizepsbt ( signed_psbt ) [ " hex " ] )
mining_node . generate ( 1 )
2019-04-07 00:19:45 +02:00
self . sync_blocks ( [ mining_node , online_node ] )
2018-09-20 20:43:06 +02:00
assert_equal ( online_node . gettxout ( txid , 0 ) [ " confirmations " ] , 1 )
# Reconnect
2019-08-27 21:13:56 +02:00
connect_nodes ( self . nodes [ 0 ] , 1 )
connect_nodes ( self . nodes [ 0 ] , 2 )
2018-09-20 20:43:06 +02:00
2018-09-09 19:32:37 +02:00
def skip_test_if_missing_module ( self ) :
self . skip_if_no_wallet ( )
2018-06-28 02:05:54 +02:00
def run_test ( self ) :
# Create and fund a raw tx for sending 10 BTC
psbtx1 = self . nodes [ 0 ] . walletcreatefundedpsbt ( [ ] , { self . nodes [ 2 ] . getnewaddress ( ) : 10 } ) [ ' psbt ' ]
# Node 1 should not be able to add anything to it but still return the psbtx same as before
psbtx = self . nodes [ 1 ] . walletprocesspsbt ( psbtx1 ) [ ' psbt ' ]
assert_equal ( psbtx1 , psbtx )
# Sign the transaction and send
signed_tx = self . nodes [ 0 ] . walletprocesspsbt ( psbtx ) [ ' psbt ' ]
final_tx = self . nodes [ 0 ] . finalizepsbt ( signed_tx ) [ ' hex ' ]
self . nodes [ 0 ] . sendrawtransaction ( final_tx )
# Create p2sh, p2wpkh, and p2wsh addresses
pubkey0 = self . nodes [ 0 ] . getaddressinfo ( self . nodes [ 0 ] . getnewaddress ( ) ) [ ' pubkey ' ]
pubkey1 = self . nodes [ 1 ] . getaddressinfo ( self . nodes [ 1 ] . getnewaddress ( ) ) [ ' pubkey ' ]
pubkey2 = self . nodes [ 2 ] . getaddressinfo ( self . nodes [ 2 ] . getnewaddress ( ) ) [ ' pubkey ' ]
p2sh = self . nodes [ 1 ] . addmultisigaddress ( 2 , [ pubkey0 , pubkey1 , pubkey2 ] , " " , " legacy " ) [ ' address ' ]
p2wsh = self . nodes [ 1 ] . addmultisigaddress ( 2 , [ pubkey0 , pubkey1 , pubkey2 ] , " " , " bech32 " ) [ ' address ' ]
p2sh_p2wsh = self . nodes [ 1 ] . addmultisigaddress ( 2 , [ pubkey0 , pubkey1 , pubkey2 ] , " " , " p2sh-segwit " ) [ ' address ' ]
p2wpkh = self . nodes [ 1 ] . getnewaddress ( " " , " bech32 " )
p2pkh = self . nodes [ 1 ] . getnewaddress ( " " , " legacy " )
p2sh_p2wpkh = self . nodes [ 1 ] . getnewaddress ( " " , " p2sh-segwit " )
# fund those addresses
2019-09-20 17:33:17 +02:00
rawtx = self . nodes [ 0 ] . createrawtransaction ( [ ] , { p2sh : 0.2 , p2wsh : 0.2 , p2wpkh : 0.2 , p2sh_p2wsh : 0.2 , p2sh_p2wpkh : 0.2 , p2pkh : 0.2 } )
2018-06-28 02:05:54 +02:00
rawtx = self . nodes [ 0 ] . fundrawtransaction ( rawtx , { " changePosition " : 3 } )
signed_tx = self . nodes [ 0 ] . signrawtransactionwithwallet ( rawtx [ ' hex ' ] ) [ ' hex ' ]
txid = self . nodes [ 0 ] . sendrawtransaction ( signed_tx )
self . nodes [ 0 ] . generate ( 6 )
self . sync_all ( )
# Find the output pos
p2sh_pos = - 1
p2wsh_pos = - 1
p2wpkh_pos = - 1
p2pkh_pos = - 1
p2sh_p2wsh_pos = - 1
p2sh_p2wpkh_pos = - 1
decoded = self . nodes [ 0 ] . decoderawtransaction ( signed_tx )
for out in decoded [ ' vout ' ] :
if out [ ' scriptPubKey ' ] [ ' addresses ' ] [ 0 ] == p2sh :
p2sh_pos = out [ ' n ' ]
elif out [ ' scriptPubKey ' ] [ ' addresses ' ] [ 0 ] == p2wsh :
p2wsh_pos = out [ ' n ' ]
elif out [ ' scriptPubKey ' ] [ ' addresses ' ] [ 0 ] == p2wpkh :
p2wpkh_pos = out [ ' n ' ]
elif out [ ' scriptPubKey ' ] [ ' addresses ' ] [ 0 ] == p2sh_p2wsh :
p2sh_p2wsh_pos = out [ ' n ' ]
elif out [ ' scriptPubKey ' ] [ ' addresses ' ] [ 0 ] == p2sh_p2wpkh :
p2sh_p2wpkh_pos = out [ ' n ' ]
elif out [ ' scriptPubKey ' ] [ ' addresses ' ] [ 0 ] == p2pkh :
p2pkh_pos = out [ ' n ' ]
# spend single key from node 1
2019-09-20 17:33:17 +02:00
rawtx = self . nodes [ 1 ] . walletcreatefundedpsbt ( [ { " txid " : txid , " vout " : p2wpkh_pos } , { " txid " : txid , " vout " : p2sh_p2wpkh_pos } , { " txid " : txid , " vout " : p2pkh_pos } ] , { self . nodes [ 1 ] . getnewaddress ( ) : 0.5998 } ) [ ' psbt ' ]
2018-06-28 02:05:54 +02:00
walletprocesspsbt_out = self . nodes [ 1 ] . walletprocesspsbt ( rawtx )
assert_equal ( walletprocesspsbt_out [ ' complete ' ] , True )
self . nodes [ 1 ] . sendrawtransaction ( self . nodes [ 1 ] . finalizepsbt ( walletprocesspsbt_out [ ' psbt ' ] ) [ ' hex ' ] )
2019-06-29 04:44:38 +02:00
# feeRate of 0.1 BTC / KB produces a total fee slightly below -maxtxfee (~0.05280000):
res = self . nodes [ 1 ] . walletcreatefundedpsbt ( [ { " txid " : txid , " vout " : p2wpkh_pos } , { " txid " : txid , " vout " : p2sh_p2wpkh_pos } , { " txid " : txid , " vout " : p2pkh_pos } ] , { self . nodes [ 1 ] . getnewaddress ( ) : 29.99 } , 0 , { " feeRate " : 0.1 } )
assert_greater_than ( res [ " fee " ] , 0.05 )
assert_greater_than ( 0.06 , res [ " fee " ] )
# feeRate of 10 BTC / KB produces a total fee well above -maxtxfee
2019-07-02 16:16:36 +02:00
# previously this was silently capped at -maxtxfee
2019-06-29 04:44:38 +02:00
assert_raises_rpc_error ( - 4 , " Fee exceeds maximum configured by -maxtxfee " , self . nodes [ 1 ] . walletcreatefundedpsbt , [ { " txid " : txid , " vout " : p2wpkh_pos } , { " txid " : txid , " vout " : p2sh_p2wpkh_pos } , { " txid " : txid , " vout " : p2pkh_pos } ] , { self . nodes [ 1 ] . getnewaddress ( ) : 29.99 } , 0 , { " feeRate " : 10 } )
2018-06-28 02:05:54 +02:00
# partially sign multisig things with node 1
2019-09-20 17:33:17 +02:00
psbtx = self . nodes [ 1 ] . walletcreatefundedpsbt ( [ { " txid " : txid , " vout " : p2wsh_pos } , { " txid " : txid , " vout " : p2sh_pos } , { " txid " : txid , " vout " : p2sh_p2wsh_pos } ] , { self . nodes [ 1 ] . getnewaddress ( ) : 0.5998 } ) [ ' psbt ' ]
2018-06-28 02:05:54 +02:00
walletprocesspsbt_out = self . nodes [ 1 ] . walletprocesspsbt ( psbtx )
psbtx = walletprocesspsbt_out [ ' psbt ' ]
assert_equal ( walletprocesspsbt_out [ ' complete ' ] , False )
# partially sign with node 2. This should be complete and sendable
walletprocesspsbt_out = self . nodes [ 2 ] . walletprocesspsbt ( psbtx )
assert_equal ( walletprocesspsbt_out [ ' complete ' ] , True )
self . nodes [ 2 ] . sendrawtransaction ( self . nodes [ 2 ] . finalizepsbt ( walletprocesspsbt_out [ ' psbt ' ] ) [ ' hex ' ] )
# check that walletprocesspsbt fails to decode a non-psbt
2019-09-20 17:33:17 +02:00
rawtx = self . nodes [ 1 ] . createrawtransaction ( [ { " txid " : txid , " vout " : p2wpkh_pos } ] , { self . nodes [ 1 ] . getnewaddress ( ) : 0.1998 } )
2018-06-28 02:05:54 +02:00
assert_raises_rpc_error ( - 22 , " TX decode failed " , self . nodes [ 1 ] . walletprocesspsbt , rawtx )
# Convert a non-psbt to psbt and make sure we can decode it
2019-09-20 17:33:17 +02:00
rawtx = self . nodes [ 0 ] . createrawtransaction ( [ ] , { self . nodes [ 1 ] . getnewaddress ( ) : 0.2 } )
2018-06-28 02:05:54 +02:00
rawtx = self . nodes [ 0 ] . fundrawtransaction ( rawtx )
new_psbt = self . nodes [ 0 ] . converttopsbt ( rawtx [ ' hex ' ] )
self . nodes [ 0 ] . decodepsbt ( new_psbt )
2018-11-27 18:46:20 +01:00
# Make sure that a non-psbt with signatures cannot be converted
2018-11-27 18:43:48 +01:00
# Error could be either "TX decode failed" (segwit inputs causes parsing to fail) or "Inputs must not have scriptSigs and scriptWitnesses"
2019-04-26 15:04:08 +02:00
# We must set iswitness=True because the serialized transaction has inputs and is therefore a witness transaction
2018-06-28 02:05:54 +02:00
signedtx = self . nodes [ 0 ] . signrawtransactionwithwallet ( rawtx [ ' hex ' ] )
2019-09-20 17:33:17 +02:00
# assert_raises_rpc_error(-22, "", self.nodes[0].converttopsbt, hexstring=signedtx['hex'], iswitness=True)
# assert_raises_rpc_error(-22, "", self.nodes[0].converttopsbt, hexstring=signedtx['hex'], permitsigdata=False, iswitness=True)
2018-09-30 04:09:15 +02:00
# Unless we allow it to convert and strip signatures
self . nodes [ 0 ] . converttopsbt ( signedtx [ ' hex ' ] , True )
2018-06-28 02:05:54 +02:00
2018-08-14 09:27:30 +02:00
# Explicitly allow converting non-empty txs
2018-06-28 02:05:54 +02:00
new_psbt = self . nodes [ 0 ] . converttopsbt ( rawtx [ ' hex ' ] )
self . nodes [ 0 ] . decodepsbt ( new_psbt )
# Create outputs to nodes 1 and 2
node1_addr = self . nodes [ 1 ] . getnewaddress ( )
node2_addr = self . nodes [ 2 ] . getnewaddress ( )
2019-09-20 17:33:17 +02:00
txid1 = self . nodes [ 0 ] . sendtoaddress ( node1_addr , 0.26 )
txid2 = self . nodes [ 0 ] . sendtoaddress ( node2_addr , 0.26 )
2019-02-14 04:22:40 +01:00
blockhash = self . nodes [ 0 ] . generate ( 6 ) [ 0 ]
2018-06-28 02:05:54 +02:00
self . sync_all ( )
2019-09-20 17:33:17 +02:00
vout1 = find_output ( self . nodes [ 1 ] , txid1 , Decimal ( " 0.26 " ) , blockhash = blockhash )
vout2 = find_output ( self . nodes [ 2 ] , txid2 , Decimal ( " 0.26 " ) , blockhash = blockhash )
2018-06-28 02:05:54 +02:00
# Create a psbt spending outputs from nodes 1 and 2
2019-09-20 17:33:17 +02:00
psbt_orig = self . nodes [ 0 ] . createpsbt ( [ { " txid " : txid1 , " vout " : vout1 } , { " txid " : txid2 , " vout " : vout2 } ] , { self . nodes [ 0 ] . getnewaddress ( ) : 0.51998 } )
2018-06-28 02:05:54 +02:00
# Update psbts, should only have data for one input and not the other
psbt1 = self . nodes [ 1 ] . walletprocesspsbt ( psbt_orig ) [ ' psbt ' ]
psbt1_decoded = self . nodes [ 0 ] . decodepsbt ( psbt1 )
assert psbt1_decoded [ ' inputs ' ] [ 0 ] and not psbt1_decoded [ ' inputs ' ] [ 1 ]
psbt2 = self . nodes [ 2 ] . walletprocesspsbt ( psbt_orig ) [ ' psbt ' ]
psbt2_decoded = self . nodes [ 0 ] . decodepsbt ( psbt2 )
assert not psbt2_decoded [ ' inputs ' ] [ 0 ] and psbt2_decoded [ ' inputs ' ] [ 1 ]
# Combine, finalize, and send the psbts
combined = self . nodes [ 0 ] . combinepsbt ( [ psbt1 , psbt2 ] )
finalized = self . nodes [ 0 ] . finalizepsbt ( combined ) [ ' hex ' ]
self . nodes [ 0 ] . sendrawtransaction ( finalized )
self . nodes [ 0 ] . generate ( 6 )
self . sync_all ( )
2018-08-15 03:52:16 +02:00
# Test additional args in walletcreatepsbt
# Make sure both pre-included and funded inputs
# have the correct sequence numbers based on
# replaceable arg
block_height = self . nodes [ 0 ] . getblockcount ( )
unspent = self . nodes [ 0 ] . listunspent ( ) [ 0 ]
2019-09-20 17:33:17 +02:00
psbtx_info = self . nodes [ 0 ] . walletcreatefundedpsbt ( [ { " txid " : unspent [ " txid " ] , " vout " : unspent [ " vout " ] } ] , [ { self . nodes [ 2 ] . getnewaddress ( ) : unspent [ " amount " ] + Decimal ( " 0.02 " ) } ] , block_height + 2 , { " replaceable " : False } , False )
2018-08-15 03:52:16 +02:00
decoded_psbt = self . nodes [ 0 ] . decodepsbt ( psbtx_info [ " psbt " ] )
2018-08-24 23:03:55 +02:00
for tx_in , psbt_in in zip ( decoded_psbt [ " tx " ] [ " vin " ] , decoded_psbt [ " inputs " ] ) :
2019-07-27 19:35:07 +02:00
assert_greater_than ( tx_in [ " sequence " ] , MAX_BIP125_RBF_SEQUENCE )
2019-04-07 00:38:51 +02:00
assert " bip32_derivs " not in psbt_in
2018-08-15 03:52:16 +02:00
assert_equal ( decoded_psbt [ " tx " ] [ " locktime " ] , block_height + 2 )
2019-04-27 19:44:38 +02:00
# Same construction with only locktime set and RBF explicitly enabled
2019-09-20 17:33:17 +02:00
psbtx_info = self . nodes [ 0 ] . walletcreatefundedpsbt ( [ { " txid " : unspent [ " txid " ] , " vout " : unspent [ " vout " ] } ] , [ { self . nodes [ 2 ] . getnewaddress ( ) : unspent [ " amount " ] + Decimal ( " 0.02 " ) } ] , block_height , { " replaceable " : True } , True )
2018-08-15 03:52:16 +02:00
decoded_psbt = self . nodes [ 0 ] . decodepsbt ( psbtx_info [ " psbt " ] )
2018-08-24 23:03:55 +02:00
for tx_in , psbt_in in zip ( decoded_psbt [ " tx " ] [ " vin " ] , decoded_psbt [ " inputs " ] ) :
2019-04-27 19:44:38 +02:00
assert_equal ( tx_in [ " sequence " ] , MAX_BIP125_RBF_SEQUENCE )
2018-08-24 23:03:55 +02:00
assert " bip32_derivs " in psbt_in
2018-08-15 03:52:16 +02:00
assert_equal ( decoded_psbt [ " tx " ] [ " locktime " ] , block_height )
# Same construction without optional arguments
2019-09-20 17:33:17 +02:00
psbtx_info = self . nodes [ 0 ] . walletcreatefundedpsbt ( [ { " txid " : unspent [ " txid " ] , " vout " : unspent [ " vout " ] } ] , [ { self . nodes [ 2 ] . getnewaddress ( ) : unspent [ " amount " ] + Decimal ( " 0.02 " ) } ] )
2018-08-15 03:52:16 +02:00
decoded_psbt = self . nodes [ 0 ] . decodepsbt ( psbtx_info [ " psbt " ] )
for tx_in in decoded_psbt [ " tx " ] [ " vin " ] :
2019-04-27 19:44:38 +02:00
assert_equal ( tx_in [ " sequence " ] , MAX_BIP125_RBF_SEQUENCE )
2018-08-15 03:52:16 +02:00
assert_equal ( decoded_psbt [ " tx " ] [ " locktime " ] , 0 )
2019-07-27 19:35:07 +02:00
# Same construction without optional arguments, for a node with -walletrbf=0
unspent1 = self . nodes [ 1 ] . listunspent ( ) [ 0 ]
psbtx_info = self . nodes [ 1 ] . walletcreatefundedpsbt ( [ { " txid " : unspent1 [ " txid " ] , " vout " : unspent1 [ " vout " ] } ] , [ { self . nodes [ 2 ] . getnewaddress ( ) : unspent1 [ " amount " ] + 1 } ] , block_height )
decoded_psbt = self . nodes [ 1 ] . decodepsbt ( psbtx_info [ " psbt " ] )
for tx_in in decoded_psbt [ " tx " ] [ " vin " ] :
assert_greater_than ( tx_in [ " sequence " ] , MAX_BIP125_RBF_SEQUENCE )
2018-10-03 07:41:03 +02:00
# Make sure change address wallet does not have P2SH innerscript access to results in success
# when attempting BnB coin selection
self . nodes [ 0 ] . walletcreatefundedpsbt ( [ ] , [ { self . nodes [ 2 ] . getnewaddress ( ) : unspent [ " amount " ] + 1 } ] , block_height + 2 , { " changeAddress " : self . nodes [ 1 ] . getnewaddress ( ) } , False )
2018-10-30 08:41:19 +01:00
# Regression test for 14473 (mishandling of already-signed witness transaction):
2019-09-20 17:33:17 +02:00
psbtx_info = self . nodes [ 0 ] . walletcreatefundedpsbt ( [ { " txid " : unspent [ " txid " ] , " vout " : unspent [ " vout " ] } ] , [ { self . nodes [ 2 ] . getnewaddress ( ) : unspent [ " amount " ] + Decimal ( " 0.02 " ) } ] )
2018-10-30 08:41:19 +01:00
complete_psbt = self . nodes [ 0 ] . walletprocesspsbt ( psbtx_info [ " psbt " ] )
double_processed_psbt = self . nodes [ 0 ] . walletprocesspsbt ( complete_psbt [ " psbt " ] )
assert_equal ( complete_psbt , double_processed_psbt )
# We don't care about the decode result, but decoding must succeed.
self . nodes [ 0 ] . decodepsbt ( double_processed_psbt [ " psbt " ] )
2018-08-15 03:52:16 +02:00
2018-10-03 07:41:03 +02:00
# Make sure change address wallet does not have P2SH innerscript access to results in success
# when attempting BnB coin selection
self . nodes [ 0 ] . walletcreatefundedpsbt ( [ ] , [ { self . nodes [ 2 ] . getnewaddress ( ) : unspent [ " amount " ] + 1 } ] , block_height + 2 , { " changeAddress " : self . nodes [ 1 ] . getnewaddress ( ) } , False )
2018-06-28 02:05:54 +02:00
# BIP 174 Test Vectors
# Check that unknown values are just passed through
unknown_psbt = " cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAACg8BAgMEBQYHCAkPAQIDBAUGBwgJCgsMDQ4PAAA= "
unknown_out = self . nodes [ 0 ] . walletprocesspsbt ( unknown_psbt ) [ ' psbt ' ]
assert_equal ( unknown_psbt , unknown_out )
# Open the data file
with open ( os . path . join ( os . path . dirname ( os . path . realpath ( __file__ ) ) , ' data/rpc_psbt.json ' ) , encoding = ' utf-8 ' ) as f :
d = json . load ( f )
invalids = d [ ' invalid ' ]
valids = d [ ' valid ' ]
creators = d [ ' creator ' ]
signers = d [ ' signer ' ]
combiners = d [ ' combiner ' ]
finalizers = d [ ' finalizer ' ]
extractors = d [ ' extractor ' ]
# Invalid PSBTs
for invalid in invalids :
assert_raises_rpc_error ( - 22 , " TX decode failed " , self . nodes [ 0 ] . decodepsbt , invalid )
# Valid PSBTs
for valid in valids :
self . nodes [ 0 ] . decodepsbt ( valid )
# Creator Tests
for creator in creators :
created_tx = self . nodes [ 0 ] . createpsbt ( creator [ ' inputs ' ] , creator [ ' outputs ' ] )
assert_equal ( created_tx , creator [ ' result ' ] )
# Signer tests
for i , signer in enumerate ( signers ) :
2018-08-09 01:14:01 +02:00
self . nodes [ 2 ] . createwallet ( " wallet {} " . format ( i ) )
wrpc = self . nodes [ 2 ] . get_wallet_rpc ( " wallet {} " . format ( i ) )
2018-06-28 02:05:54 +02:00
for key in signer [ ' privkeys ' ] :
2018-08-09 01:14:01 +02:00
wrpc . importprivkey ( key )
signed_tx = wrpc . walletprocesspsbt ( signer [ ' psbt ' ] ) [ ' psbt ' ]
2018-06-28 02:05:54 +02:00
assert_equal ( signed_tx , signer [ ' result ' ] )
# Combiner test
for combiner in combiners :
combined = self . nodes [ 2 ] . combinepsbt ( combiner [ ' combine ' ] )
assert_equal ( combined , combiner [ ' result ' ] )
2019-02-05 04:26:43 +01:00
# Empty combiner test
assert_raises_rpc_error ( - 8 , " Parameter ' txs ' cannot be empty " , self . nodes [ 0 ] . combinepsbt , [ ] )
2018-06-28 02:05:54 +02:00
# Finalizer test
for finalizer in finalizers :
finalized = self . nodes [ 2 ] . finalizepsbt ( finalizer [ ' finalize ' ] , False ) [ ' psbt ' ]
assert_equal ( finalized , finalizer [ ' result ' ] )
# Extractor test
for extractor in extractors :
extracted = self . nodes [ 2 ] . finalizepsbt ( extractor [ ' extract ' ] , True ) [ ' hex ' ]
assert_equal ( extracted , extractor [ ' result ' ] )
2018-09-20 20:43:06 +02:00
# Unload extra wallets
for i , signer in enumerate ( signers ) :
self . nodes [ 2 ] . unloadwallet ( " wallet {} " . format ( i ) )
self . test_utxo_conversion ( )
2018-11-08 16:08:46 +01:00
# Test that psbts with p2pkh outputs are created properly
p2pkh = self . nodes [ 0 ] . getnewaddress ( address_type = ' legacy ' )
psbt = self . nodes [ 1 ] . walletcreatefundedpsbt ( [ ] , [ { p2pkh : 1 } ] , 0 , { " includeWatching " : True } , True )
self . nodes [ 0 ] . decodepsbt ( psbt [ ' psbt ' ] )
2018-06-28 02:05:54 +02:00
2019-01-30 06:32:38 +01:00
# Test decoding error: invalid base64
assert_raises_rpc_error ( - 22 , " TX decode failed invalid base64 " , self . nodes [ 0 ] . decodepsbt , " ;definitely not base64; " )
2018-07-21 02:08:25 +02:00
# Send to all types of addresses
addr1 = self . nodes [ 1 ] . getnewaddress ( " " , " bech32 " )
txid1 = self . nodes [ 0 ] . sendtoaddress ( addr1 , 11 )
vout1 = find_output ( self . nodes [ 0 ] , txid1 , 11 )
addr2 = self . nodes [ 1 ] . getnewaddress ( " " , " legacy " )
txid2 = self . nodes [ 0 ] . sendtoaddress ( addr2 , 11 )
vout2 = find_output ( self . nodes [ 0 ] , txid2 , 11 )
addr3 = self . nodes [ 1 ] . getnewaddress ( " " , " p2sh-segwit " )
txid3 = self . nodes [ 0 ] . sendtoaddress ( addr3 , 11 )
vout3 = find_output ( self . nodes [ 0 ] , txid3 , 11 )
self . sync_all ( )
2019-02-16 23:59:16 +01:00
def test_psbt_input_keys ( psbt_input , keys ) :
""" Check that the psbt input has only the expected keys. """
assert_equal ( set ( keys ) , set ( psbt_input . keys ( ) ) )
# Create a PSBT. None of the inputs are filled initially
2018-07-21 02:08:25 +02:00
psbt = self . nodes [ 1 ] . createpsbt ( [ { " txid " : txid1 , " vout " : vout1 } , { " txid " : txid2 , " vout " : vout2 } , { " txid " : txid3 , " vout " : vout3 } ] , { self . nodes [ 0 ] . getnewaddress ( ) : 32.999 } )
decoded = self . nodes [ 1 ] . decodepsbt ( psbt )
2019-02-16 23:59:16 +01:00
test_psbt_input_keys ( decoded [ ' inputs ' ] [ 0 ] , [ ] )
test_psbt_input_keys ( decoded [ ' inputs ' ] [ 1 ] , [ ] )
test_psbt_input_keys ( decoded [ ' inputs ' ] [ 2 ] , [ ] )
# Update a PSBT with UTXOs from the node
# Bech32 inputs should be filled with witness UTXO. Other inputs should not be filled because they are non-witness
2018-07-21 02:08:25 +02:00
updated = self . nodes [ 1 ] . utxoupdatepsbt ( psbt )
decoded = self . nodes [ 1 ] . decodepsbt ( updated )
2019-02-16 23:59:16 +01:00
test_psbt_input_keys ( decoded [ ' inputs ' ] [ 0 ] , [ ' witness_utxo ' ] )
test_psbt_input_keys ( decoded [ ' inputs ' ] [ 1 ] , [ ] )
test_psbt_input_keys ( decoded [ ' inputs ' ] [ 2 ] , [ ] )
# Try again, now while providing descriptors, making P2SH-segwit work, and causing bip32_derivs and redeem_script to be filled in
descs = [ self . nodes [ 1 ] . getaddressinfo ( addr ) [ ' desc ' ] for addr in [ addr1 , addr2 , addr3 ] ]
2019-07-02 19:34:18 +02:00
updated = self . nodes [ 1 ] . utxoupdatepsbt ( psbt = psbt , descriptors = descs )
2019-02-16 23:59:16 +01:00
decoded = self . nodes [ 1 ] . decodepsbt ( updated )
test_psbt_input_keys ( decoded [ ' inputs ' ] [ 0 ] , [ ' witness_utxo ' , ' bip32_derivs ' ] )
test_psbt_input_keys ( decoded [ ' inputs ' ] [ 1 ] , [ ] )
test_psbt_input_keys ( decoded [ ' inputs ' ] [ 2 ] , [ ' witness_utxo ' , ' bip32_derivs ' , ' redeem_script ' ] )
2018-07-21 02:08:25 +02:00
2018-07-21 03:24:16 +02:00
# Two PSBTs with a common input should not be joinable
psbt1 = self . nodes [ 1 ] . createpsbt ( [ { " txid " : txid1 , " vout " : vout1 } ] , { self . nodes [ 0 ] . getnewaddress ( ) : Decimal ( ' 10.999 ' ) } )
assert_raises_rpc_error ( - 8 , " exists in multiple PSBTs " , self . nodes [ 1 ] . joinpsbts , [ psbt1 , updated ] )
# Join two distinct PSBTs
addr4 = self . nodes [ 1 ] . getnewaddress ( " " , " p2sh-segwit " )
txid4 = self . nodes [ 0 ] . sendtoaddress ( addr4 , 5 )
vout4 = find_output ( self . nodes [ 0 ] , txid4 , 5 )
self . nodes [ 0 ] . generate ( 6 )
self . sync_all ( )
psbt2 = self . nodes [ 1 ] . createpsbt ( [ { " txid " : txid4 , " vout " : vout4 } ] , { self . nodes [ 0 ] . getnewaddress ( ) : Decimal ( ' 4.999 ' ) } )
psbt2 = self . nodes [ 1 ] . walletprocesspsbt ( psbt2 ) [ ' psbt ' ]
psbt2_decoded = self . nodes [ 0 ] . decodepsbt ( psbt2 )
assert " final_scriptwitness " in psbt2_decoded [ ' inputs ' ] [ 0 ] and " final_scriptSig " in psbt2_decoded [ ' inputs ' ] [ 0 ]
joined = self . nodes [ 0 ] . joinpsbts ( [ psbt , psbt2 ] )
joined_decoded = self . nodes [ 0 ] . decodepsbt ( joined )
assert len ( joined_decoded [ ' inputs ' ] ) == 4 and len ( joined_decoded [ ' outputs ' ] ) == 2 and " final_scriptwitness " not in joined_decoded [ ' inputs ' ] [ 3 ] and " final_scriptSig " not in joined_decoded [ ' inputs ' ] [ 3 ]
2018-07-21 02:08:25 +02:00
2019-08-14 20:29:55 +02:00
# Check that joining shuffles the inputs and outputs
# 10 attempts should be enough to get a shuffled join
shuffled = False
for i in range ( 0 , 10 ) :
shuffled_joined = self . nodes [ 0 ] . joinpsbts ( [ psbt , psbt2 ] )
shuffled | = joined != shuffled_joined
if shuffled :
break
assert shuffled
2018-08-01 02:58:01 +02:00
# Newly created PSBT needs UTXOs and updating
addr = self . nodes [ 1 ] . getnewaddress ( " " , " p2sh-segwit " )
txid = self . nodes [ 0 ] . sendtoaddress ( addr , 7 )
addrinfo = self . nodes [ 1 ] . getaddressinfo ( addr )
2019-02-14 04:22:40 +01:00
blockhash = self . nodes [ 0 ] . generate ( 6 ) [ 0 ]
2018-08-01 02:58:01 +02:00
self . sync_all ( )
2019-02-14 04:22:40 +01:00
vout = find_output ( self . nodes [ 0 ] , txid , 7 , blockhash = blockhash )
2018-08-01 02:58:01 +02:00
psbt = self . nodes [ 1 ] . createpsbt ( [ { " txid " : txid , " vout " : vout } ] , { self . nodes [ 0 ] . getnewaddress ( " " , " p2sh-segwit " ) : Decimal ( ' 6.999 ' ) } )
analyzed = self . nodes [ 0 ] . analyzepsbt ( psbt )
assert not analyzed [ ' inputs ' ] [ 0 ] [ ' has_utxo ' ] and not analyzed [ ' inputs ' ] [ 0 ] [ ' is_final ' ] and analyzed [ ' inputs ' ] [ 0 ] [ ' next ' ] == ' updater ' and analyzed [ ' next ' ] == ' updater '
# After update with wallet, only needs signing
updated = self . nodes [ 1 ] . walletprocesspsbt ( psbt , False , ' ALL ' , True ) [ ' psbt ' ]
analyzed = self . nodes [ 0 ] . analyzepsbt ( updated )
assert analyzed [ ' inputs ' ] [ 0 ] [ ' has_utxo ' ] and not analyzed [ ' inputs ' ] [ 0 ] [ ' is_final ' ] and analyzed [ ' inputs ' ] [ 0 ] [ ' next ' ] == ' signer ' and analyzed [ ' next ' ] == ' signer ' and analyzed [ ' inputs ' ] [ 0 ] [ ' missing ' ] [ ' signatures ' ] [ 0 ] == addrinfo [ ' embedded ' ] [ ' witness_program ' ]
# Check fee and size things
2019-03-09 07:50:44 +01:00
assert analyzed [ ' fee ' ] == Decimal ( ' 0.001 ' ) and analyzed [ ' estimated_vsize ' ] == 134 and analyzed [ ' estimated_feerate ' ] == Decimal ( ' 0.00746268 ' )
2018-08-01 02:58:01 +02:00
# After signing and finalizing, needs extracting
signed = self . nodes [ 1 ] . walletprocesspsbt ( updated ) [ ' psbt ' ]
analyzed = self . nodes [ 0 ] . analyzepsbt ( signed )
assert analyzed [ ' inputs ' ] [ 0 ] [ ' has_utxo ' ] and analyzed [ ' inputs ' ] [ 0 ] [ ' is_final ' ] and analyzed [ ' next ' ] == ' extractor '
2018-07-21 02:08:25 +02:00
2019-11-19 20:54:13 +01:00
self . log . info ( " PSBT spending unspendable outputs should have error message and Creator as next " )
analysis = self . nodes [ 0 ] . analyzepsbt ( ' cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWAEHYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFv8/wADXYP/7//////8JxOh0LR2HAI8AAAAAAAEBIADC6wsAAAAAF2oUt/X69ELjeX2nTof+fZ10l+OyAokDAQcJAwEHEAABAACAAAEBIADC6wsAAAAAF2oUt/X69ELjeX2nTof+fZ10l+OyAokDAQcJAwEHENkMak8AAAAA ' )
assert_equal ( analysis [ ' next ' ] , ' creator ' )
assert_equal ( analysis [ ' error ' ] , ' PSBT is not valid. Input 0 spends unspendable output ' )
2019-10-15 23:15:22 +02:00
self . log . info ( " PSBT with invalid values should have error message and Creator as next " )
analysis = self . nodes [ 0 ] . analyzepsbt ( ' cHNidP8BAHECAAAAAfA00BFgAm6tp86RowwH6BMImQNL5zXUcTT97XoLGz0BAAAAAAD/////AgD5ApUAAAAAFgAUKNw0x8HRctAgmvoevm4u1SbN7XL87QKVAAAAABYAFPck4gF7iL4NL4wtfRAKgQbghiTUAAAAAAABAR8AgIFq49AHABYAFJUDtxf2PHo641HEOBOAIvFMNTr2AAAA ' )
assert_equal ( analysis [ ' next ' ] , ' creator ' )
assert_equal ( analysis [ ' error ' ] , ' PSBT is not valid. Input 0 has invalid value ' )
analysis = self . nodes [ 0 ] . analyzepsbt ( ' cHNidP8BAHECAAAAAfA00BFgAm6tp86RowwH6BMImQNL5zXUcTT97XoLGz0BAAAAAAD/////AgCAgWrj0AcAFgAUKNw0x8HRctAgmvoevm4u1SbN7XL87QKVAAAAABYAFPck4gF7iL4NL4wtfRAKgQbghiTUAAAAAAABAR8A8gUqAQAAABYAFJUDtxf2PHo641HEOBOAIvFMNTr2AAAA ' )
assert_equal ( analysis [ ' next ' ] , ' creator ' )
assert_equal ( analysis [ ' error ' ] , ' PSBT is not valid. Output amount invalid ' )
2019-10-15 23:26:46 +02:00
analysis = self . nodes [ 0 ] . analyzepsbt ( ' cHNidP8BAJoCAAAAAkvEW8NnDtdNtDpsmze+Ht2LH35IJcKv00jKAlUs21RrAwAAAAD/////S8Rbw2cO1020OmybN74e3Ysffkglwq/TSMoCVSzbVGsBAAAAAP7///8CwLYClQAAAAAWABSNJKzjaUb3uOxixsvh1GGE3fW7zQD5ApUAAAAAFgAUKNw0x8HRctAgmvoevm4u1SbN7XIAAAAAAAEAnQIAAAACczMa321tVHuN4GKWKRncycI22aX3uXgwSFUKM2orjRsBAAAAAP7///9zMxrfbW1Ue43gYpYpGdzJwjbZpfe5eDBIVQozaiuNGwAAAAAA/v///wIA+QKVAAAAABl2qRT9zXUVA8Ls5iVqynLHe5/vSe1XyYisQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAAAAAQEfQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAA== ' )
assert_equal ( analysis [ ' next ' ] , ' creator ' )
assert_equal ( analysis [ ' error ' ] , ' PSBT is not valid. Input 0 specifies invalid prevout ' )
assert_raises_rpc_error ( - 25 , ' Missing inputs ' , self . nodes [ 0 ] . walletprocesspsbt , ' cHNidP8BAJoCAAAAAkvEW8NnDtdNtDpsmze+Ht2LH35IJcKv00jKAlUs21RrAwAAAAD/////S8Rbw2cO1020OmybN74e3Ysffkglwq/TSMoCVSzbVGsBAAAAAP7///8CwLYClQAAAAAWABSNJKzjaUb3uOxixsvh1GGE3fW7zQD5ApUAAAAAFgAUKNw0x8HRctAgmvoevm4u1SbN7XIAAAAAAAEAnQIAAAACczMa321tVHuN4GKWKRncycI22aX3uXgwSFUKM2orjRsBAAAAAP7///9zMxrfbW1Ue43gYpYpGdzJwjbZpfe5eDBIVQozaiuNGwAAAAAA/v///wIA+QKVAAAAABl2qRT9zXUVA8Ls5iVqynLHe5/vSe1XyYisQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAAAAAQEfQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAA== ' )
2018-06-28 02:05:54 +02:00
if __name__ == ' __main__ ' :
PSBTTest ( ) . main ( )