2018-06-27 20:05:54 -04:00
#!/usr/bin/env python3
2022-12-24 20:49:50 -03:00
# Copyright (c) 2018-2022 The Bitcoin Core developers
2018-06-27 20:05:54 -04: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-07-31 20:58:01 -04:00
from decimal import Decimal
2020-12-04 07:28:47 -03:00
from itertools import product
2023-10-25 01:45:29 -03:00
from random import randbytes
2020-12-04 07:28:47 -03:00
2019-10-21 17:55:07 -03:00
from test_framework . descriptors import descsum_create
2023-05-23 17:38:31 -04:00
from test_framework . key import H_POINT
2022-03-01 11:09:10 -03:00
from test_framework . messages import (
2022-07-16 10:04:45 -04:00
COutPoint ,
CTransaction ,
CTxIn ,
CTxOut ,
2022-07-12 12:55:43 -04:00
MAX_BIP125_RBF_SEQUENCE ,
2022-03-01 11:09:10 -03:00
WITNESS_SCALE_FACTOR ,
2022-07-12 12:55:43 -04:00
ser_compact_size ,
2022-03-01 11:09:10 -03:00
)
2022-07-16 10:04:45 -04:00
from test_framework . psbt import (
PSBT ,
PSBTMap ,
PSBT_GLOBAL_UNSIGNED_TX ,
PSBT_IN_RIPEMD160 ,
PSBT_IN_SHA256 ,
PSBT_IN_HASH160 ,
PSBT_IN_HASH256 ,
2023-03-04 22:43:39 -03:00
PSBT_IN_NON_WITNESS_UTXO ,
2022-10-17 12:11:27 -03:00
PSBT_IN_WITNESS_UTXO ,
2022-10-06 16:32:33 -03:00
PSBT_OUT_TAP_TREE ,
2022-07-16 10:04:45 -04:00
)
2022-10-17 12:11:27 -03:00
from test_framework . script import CScript , OP_TRUE
2018-06-27 20:05:54 -04:00
from test_framework . test_framework import BitcoinTestFramework
2019-04-06 19:38:51 -03:00
from test_framework . util import (
2019-07-12 08:30:10 -04:00
assert_approx ,
2019-04-06 19:38:51 -03:00
assert_equal ,
2019-06-28 22:44:38 -04:00
assert_greater_than ,
2021-05-24 09:22:10 -04:00
assert_greater_than_or_equal ,
2019-04-06 19:38:51 -03:00
assert_raises_rpc_error ,
2022-07-11 14:21:46 -04:00
find_vout_for_address ,
2019-04-06 19:38:51 -03:00
)
2023-05-05 11:22:05 -04:00
from test_framework . wallet_util import (
2023-05-23 17:38:31 -04:00
generate_keypair ,
get_generate_key ,
2023-05-05 11:22:05 -04:00
)
2018-07-06 18:10:35 -04:00
import json
import os
2018-06-27 20:05:54 -04:00
2018-08-14 22:52:16 -03:00
2018-06-27 20:05:54 -04:00
class PSBTTest ( BitcoinTestFramework ) :
2022-11-09 08:53:13 -03:00
def add_options ( self , parser ) :
self . add_wallet_options ( parser )
2018-06-27 20:05:54 -04:00
def set_test_params ( self ) :
self . num_nodes = 3
2019-04-27 13:44:38 -04:00
self . extra_args = [
2021-06-23 16:11:51 -04:00
[ " -walletrbf=1 " , " -addresstype=bech32 " , " -changetype=bech32 " ] , #TODO: Remove address type restrictions once taproot has psbt extensions
2020-09-12 12:07:59 -03:00
[ " -walletrbf=0 " , " -changetype=legacy " ] ,
2019-04-27 13:44:38 -04:00
[ ]
]
2023-03-04 21:45:23 -03:00
# whitelist peers to speed up tx relay / mempool sync
for args in self . extra_args :
args . append ( " -whitelist=noban@127.0.0.1 " )
2019-12-06 11:37:49 -03:00
self . supports_cli = False
2018-06-27 20:05:54 -04:00
2018-09-09 14:32:37 -03:00
def skip_test_if_missing_module ( self ) :
self . skip_if_no_wallet ( )
2024-06-28 07:45:47 -04:00
def test_psbt_incomplete_after_invalid_modification ( self ) :
self . log . info ( " Check that PSBT is correctly marked as incomplete after invalid modification " )
node = self . nodes [ 2 ]
wallet = node . get_wallet_rpc ( self . default_wallet_name )
address = wallet . getnewaddress ( )
wallet . sendtoaddress ( address = address , amount = 1.0 )
self . generate ( node , nblocks = 1 , sync_fun = lambda : self . sync_all ( self . nodes [ : 2 ] ) )
utxos = wallet . listunspent ( addresses = [ address ] )
psbt = wallet . createpsbt ( [ { " txid " : utxos [ 0 ] [ " txid " ] , " vout " : utxos [ 0 ] [ " vout " ] } ] , [ { wallet . getnewaddress ( ) : 0.9999 } ] )
signed_psbt = wallet . walletprocesspsbt ( psbt ) [ " psbt " ]
# Modify the raw transaction by changing the output address, so the signature is no longer valid
signed_psbt_obj = PSBT . from_base64 ( signed_psbt )
substitute_addr = wallet . getnewaddress ( )
raw = wallet . createrawtransaction ( [ { " txid " : utxos [ 0 ] [ " txid " ] , " vout " : utxos [ 0 ] [ " vout " ] } ] , [ { substitute_addr : 0.9999 } ] )
signed_psbt_obj . g . map [ PSBT_GLOBAL_UNSIGNED_TX ] = bytes . fromhex ( raw )
# Check that the walletprocesspsbt call succeeds but also recognizes that the transaction is not complete
signed_psbt_incomplete = wallet . walletprocesspsbt ( signed_psbt_obj . to_base64 ( ) , finalize = False )
assert signed_psbt_incomplete [ " complete " ] is False
2018-09-20 15:43:06 -03:00
def test_utxo_conversion ( self ) :
2023-03-04 22:43:39 -03:00
self . log . info ( " Check that non-witness UTXOs are removed for segwit v1+ inputs " )
2018-09-20 15:43:06 -03:00
mining_node = self . nodes [ 2 ]
offline_node = self . nodes [ 0 ]
online_node = self . nodes [ 1 ]
# Disconnect offline node from others
2020-05-05 16:02:01 -04:00
# Topology of test network is linear, so this one call is enough
2020-09-17 04:32:01 -03:00
self . disconnect_nodes ( 0 , 1 )
2018-09-20 15:43:06 -03:00
2019-07-16 15:33:35 -04:00
# Create watchonly on online_node
online_node . createwallet ( wallet_name = ' wonline ' , disable_private_keys = True )
wonline = online_node . get_wallet_rpc ( ' wonline ' )
2023-03-04 22:43:39 -03:00
w2 = online_node . get_wallet_rpc ( self . default_wallet_name )
2019-07-16 15:33:35 -04:00
2018-09-20 15:43:06 -03:00
# Mine a transaction that credits the offline address
2023-03-04 22:43:39 -03:00
offline_addr = offline_node . getnewaddress ( address_type = " bech32m " )
online_addr = w2 . getnewaddress ( address_type = " bech32m " )
2019-07-16 15:33:35 -04:00
wonline . importaddress ( offline_addr , " " , False )
2023-03-04 22:43:39 -03:00
mining_wallet = mining_node . get_wallet_rpc ( self . default_wallet_name )
mining_wallet . sendtoaddress ( address = offline_addr , amount = 1.0 )
self . generate ( mining_node , nblocks = 1 , sync_fun = lambda : self . sync_all ( [ online_node , mining_node ] ) )
2018-09-20 15:43:06 -03:00
2023-03-04 22:43:39 -03:00
# Construct an unsigned PSBT on the online node
2019-07-16 15:33:35 -04:00
utxos = wonline . listunspent ( addresses = [ offline_addr ] )
raw = wonline . createrawtransaction ( [ { " txid " : utxos [ 0 ] [ " txid " ] , " vout " : utxos [ 0 ] [ " vout " ] } ] , [ { online_addr : 0.9999 } ] )
psbt = wonline . walletprocesspsbt ( online_node . converttopsbt ( raw ) ) [ " psbt " ]
2023-03-04 22:43:39 -03:00
assert not " not_witness_utxo " in mining_node . decodepsbt ( psbt ) [ " inputs " ] [ 0 ]
# add non-witness UTXO manually
psbt_new = PSBT . from_base64 ( psbt )
prev_tx = wonline . gettransaction ( utxos [ 0 ] [ " txid " ] ) [ " hex " ]
psbt_new . i [ 0 ] . map [ PSBT_IN_NON_WITNESS_UTXO ] = bytes . fromhex ( prev_tx )
assert " non_witness_utxo " in mining_node . decodepsbt ( psbt_new . to_base64 ( ) ) [ " inputs " ] [ 0 ]
2018-09-20 15:43:06 -03:00
2023-03-04 22:43:39 -03:00
# Have the offline node sign the PSBT (which will remove the non-witness UTXO)
2023-09-05 13:10:21 -03:00
signed_psbt = offline_node . walletprocesspsbt ( psbt_new . to_base64 ( ) )
assert not " non_witness_utxo " in mining_node . decodepsbt ( signed_psbt [ " psbt " ] ) [ " inputs " ] [ 0 ]
2018-09-20 15:43:06 -03:00
# Make sure we can mine the resulting transaction
2023-09-05 13:10:21 -03:00
txid = mining_node . sendrawtransaction ( signed_psbt [ " hex " ] )
2023-03-04 22:43:39 -03:00
self . generate ( mining_node , nblocks = 1 , sync_fun = lambda : self . sync_all ( [ online_node , mining_node ] ) )
2018-09-20 15:43:06 -03:00
assert_equal ( online_node . gettxout ( txid , 0 ) [ " confirmations " ] , 1 )
2019-07-16 15:33:35 -04:00
wonline . unloadwallet ( )
2018-09-20 15:43:06 -03:00
# Reconnect
2023-03-04 22:43:39 -03:00
self . connect_nodes ( 1 , 0 )
2020-09-17 04:46:07 -03:00
self . connect_nodes ( 0 , 2 )
2018-09-20 15:43:06 -03:00
2021-05-24 09:22:10 -04:00
def test_input_confs_control ( self ) :
self . nodes [ 0 ] . createwallet ( " minconf " )
wallet = self . nodes [ 0 ] . get_wallet_rpc ( " minconf " )
# Fund the wallet with different chain heights
for _ in range ( 2 ) :
self . nodes [ 1 ] . sendmany ( " " , { wallet . getnewaddress ( ) : 1 , wallet . getnewaddress ( ) : 1 } )
self . generate ( self . nodes [ 1 ] , 1 )
unconfirmed_txid = wallet . sendtoaddress ( wallet . getnewaddress ( ) , 0.5 )
self . log . info ( " Crafting PSBT using an unconfirmed input " )
target_address = self . nodes [ 1 ] . getnewaddress ( )
psbtx1 = wallet . walletcreatefundedpsbt ( [ ] , { target_address : 0.1 } , 0 , { ' fee_rate ' : 1 , ' maxconf ' : 0 } ) [ ' psbt ' ]
# Make sure we only had the one input
tx1_inputs = self . nodes [ 0 ] . decodepsbt ( psbtx1 ) [ ' tx ' ] [ ' vin ' ]
assert_equal ( len ( tx1_inputs ) , 1 )
utxo1 = tx1_inputs [ 0 ]
assert_equal ( unconfirmed_txid , utxo1 [ ' txid ' ] )
2023-09-05 13:10:21 -03:00
signed_tx1 = wallet . walletprocesspsbt ( psbtx1 )
txid1 = self . nodes [ 0 ] . sendrawtransaction ( signed_tx1 [ ' hex ' ] )
2021-05-24 09:22:10 -04:00
mempool = self . nodes [ 0 ] . getrawmempool ( )
assert txid1 in mempool
self . log . info ( " Fail to craft a new PSBT that sends more funds with add_inputs = False " )
assert_raises_rpc_error ( - 4 , " The preselected coins total amount does not cover the transaction target. Please allow other inputs to be automatically selected or include more coins manually " , wallet . walletcreatefundedpsbt , [ { ' txid ' : utxo1 [ ' txid ' ] , ' vout ' : utxo1 [ ' vout ' ] } ] , { target_address : 1 } , 0 , { ' add_inputs ' : False } )
self . log . info ( " Fail to craft a new PSBT with minconf above highest one " )
assert_raises_rpc_error ( - 4 , " Insufficient funds " , wallet . walletcreatefundedpsbt , [ { ' txid ' : utxo1 [ ' txid ' ] , ' vout ' : utxo1 [ ' vout ' ] } ] , { target_address : 1 } , 0 , { ' add_inputs ' : True , ' minconf ' : 3 , ' fee_rate ' : 10 } )
self . log . info ( " Fail to broadcast a new PSBT with maxconf 0 due to BIP125 rules to verify it actually chose unconfirmed outputs " )
psbt_invalid = wallet . walletcreatefundedpsbt ( [ { ' txid ' : utxo1 [ ' txid ' ] , ' vout ' : utxo1 [ ' vout ' ] } ] , { target_address : 1 } , 0 , { ' add_inputs ' : True , ' maxconf ' : 0 , ' fee_rate ' : 10 } ) [ ' psbt ' ]
2023-09-05 13:10:21 -03:00
signed_invalid = wallet . walletprocesspsbt ( psbt_invalid )
assert_raises_rpc_error ( - 26 , " bad-txns-spends-conflicting-tx " , self . nodes [ 0 ] . sendrawtransaction , signed_invalid [ ' hex ' ] )
2021-05-24 09:22:10 -04:00
self . log . info ( " Craft a replacement adding inputs with highest confs possible " )
psbtx2 = wallet . walletcreatefundedpsbt ( [ { ' txid ' : utxo1 [ ' txid ' ] , ' vout ' : utxo1 [ ' vout ' ] } ] , { target_address : 1 } , 0 , { ' add_inputs ' : True , ' minconf ' : 2 , ' fee_rate ' : 10 } ) [ ' psbt ' ]
tx2_inputs = self . nodes [ 0 ] . decodepsbt ( psbtx2 ) [ ' tx ' ] [ ' vin ' ]
assert_greater_than_or_equal ( len ( tx2_inputs ) , 2 )
for vin in tx2_inputs :
if vin [ ' txid ' ] != unconfirmed_txid :
assert_greater_than_or_equal ( self . nodes [ 0 ] . gettxout ( vin [ ' txid ' ] , vin [ ' vout ' ] ) [ ' confirmations ' ] , 2 )
2023-09-05 13:10:21 -03:00
signed_tx2 = wallet . walletprocesspsbt ( psbtx2 )
txid2 = self . nodes [ 0 ] . sendrawtransaction ( signed_tx2 [ ' hex ' ] )
2021-05-24 09:22:10 -04:00
mempool = self . nodes [ 0 ] . getrawmempool ( )
assert txid1 not in mempool
assert txid2 in mempool
wallet . unloadwallet ( )
2020-09-12 12:07:59 -03:00
def assert_change_type ( self , psbtx , expected_type ) :
""" Assert that the given PSBT has a change output with the given type. """
# The decodepsbt RPC is stateless and independent of any settings, we can always just call it on the first node
decoded_psbt = self . nodes [ 0 ] . decodepsbt ( psbtx [ " psbt " ] )
changepos = psbtx [ " changepos " ]
assert_equal ( decoded_psbt [ " tx " ] [ " vout " ] [ changepos ] [ " scriptPubKey " ] [ " type " ] , expected_type )
2018-06-27 20:05:54 -04: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 ' ]
2019-07-12 08:30:10 -04:00
# If inputs are specified, do not automatically add more:
utxo1 = self . nodes [ 0 ] . listunspent ( ) [ 0 ]
2022-12-07 14:35:46 -03:00
assert_raises_rpc_error ( - 4 , " The preselected coins total amount does not cover the transaction target. "
" Please allow other inputs to be automatically selected or include more coins manually " ,
self . nodes [ 0 ] . walletcreatefundedpsbt , [ { " txid " : utxo1 [ ' txid ' ] , " vout " : utxo1 [ ' vout ' ] } ] , { self . nodes [ 2 ] . getnewaddress ( ) : 90 } )
2019-07-12 08:30:10 -04:00
psbtx1 = self . nodes [ 0 ] . walletcreatefundedpsbt ( [ { " txid " : utxo1 [ ' txid ' ] , " vout " : utxo1 [ ' vout ' ] } ] , { self . nodes [ 2 ] . getnewaddress ( ) : 90 } , 0 , { " add_inputs " : True } ) [ ' psbt ' ]
assert_equal ( len ( self . nodes [ 0 ] . decodepsbt ( psbtx1 ) [ ' tx ' ] [ ' vin ' ] ) , 2 )
2019-07-11 13:50:45 -04:00
# Inputs argument can be null
self . nodes [ 0 ] . walletcreatefundedpsbt ( None , { self . nodes [ 2 ] . getnewaddress ( ) : 10 } )
2018-06-27 20:05:54 -04:00
# 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 )
2021-09-27 20:40:26 -03:00
# Node 0 should not be able to sign the transaction with the wallet is locked
self . nodes [ 0 ] . encryptwallet ( " password " )
assert_raises_rpc_error ( - 13 , " Please enter the wallet passphrase with walletpassphrase first " , self . nodes [ 0 ] . walletprocesspsbt , psbtx )
# Node 0 should be able to process without signing though
unsigned_tx = self . nodes [ 0 ] . walletprocesspsbt ( psbtx , False )
assert_equal ( unsigned_tx [ ' complete ' ] , False )
self . nodes [ 0 ] . walletpassphrase ( passphrase = " password " , timeout = 1000000 )
2023-09-05 09:45:07 -03:00
# Sign the transaction but don't finalize
processed_psbt = self . nodes [ 0 ] . walletprocesspsbt ( psbt = psbtx , finalize = False )
assert " hex " not in processed_psbt
signed_psbt = processed_psbt [ ' psbt ' ]
# Finalize and send
finalized_hex = self . nodes [ 0 ] . finalizepsbt ( signed_psbt ) [ ' hex ' ]
self . nodes [ 0 ] . sendrawtransaction ( finalized_hex )
# Alternative method: sign AND finalize in one command
processed_finalized_psbt = self . nodes [ 0 ] . walletprocesspsbt ( psbt = psbtx , finalize = True )
finalized_psbt = processed_finalized_psbt [ ' psbt ' ]
finalized_psbt_hex = processed_finalized_psbt [ ' hex ' ]
assert signed_psbt != finalized_psbt
assert finalized_psbt_hex == finalized_hex
2018-06-27 20:05:54 -04:00
2020-08-07 07:53:51 -04:00
# Manually selected inputs can be locked:
assert_equal ( len ( self . nodes [ 0 ] . listlockunspent ( ) ) , 0 )
utxo1 = self . nodes [ 0 ] . listunspent ( ) [ 0 ]
psbtx1 = self . nodes [ 0 ] . walletcreatefundedpsbt ( [ { " txid " : utxo1 [ ' txid ' ] , " vout " : utxo1 [ ' vout ' ] } ] , { self . nodes [ 2 ] . getnewaddress ( ) : 1 } , 0 , { " lockUnspents " : True } ) [ " psbt " ]
assert_equal ( len ( self . nodes [ 0 ] . listlockunspent ( ) ) , 1 )
# Locks are ignored for manually selected inputs
self . nodes [ 0 ] . walletcreatefundedpsbt ( [ { " txid " : utxo1 [ ' txid ' ] , " vout " : utxo1 [ ' vout ' ] } ] , { self . nodes [ 2 ] . getnewaddress ( ) : 1 } , 0 )
# Create p2sh, p2wpkh, and p2wsh addresses
2018-06-27 20:05:54 -04:00
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 ' ]
2019-07-16 15:33:35 -04:00
# Setup watchonly wallets
self . nodes [ 2 ] . createwallet ( wallet_name = ' wmulti ' , disable_private_keys = True )
wmulti = self . nodes [ 2 ] . get_wallet_rpc ( ' wmulti ' )
# Create all the addresses
p2sh = wmulti . addmultisigaddress ( 2 , [ pubkey0 , pubkey1 , pubkey2 ] , " " , " legacy " ) [ ' address ' ]
p2wsh = wmulti . addmultisigaddress ( 2 , [ pubkey0 , pubkey1 , pubkey2 ] , " " , " bech32 " ) [ ' address ' ]
p2sh_p2wsh = wmulti . addmultisigaddress ( 2 , [ pubkey0 , pubkey1 , pubkey2 ] , " " , " p2sh-segwit " ) [ ' address ' ]
if not self . options . descriptors :
wmulti . importaddress ( p2sh )
wmulti . importaddress ( p2wsh )
wmulti . importaddress ( p2sh_p2wsh )
2018-06-27 20:05:54 -04:00
p2wpkh = self . nodes [ 1 ] . getnewaddress ( " " , " bech32 " )
p2pkh = self . nodes [ 1 ] . getnewaddress ( " " , " legacy " )
p2sh_p2wpkh = self . nodes [ 1 ] . getnewaddress ( " " , " p2sh-segwit " )
# fund those addresses
rawtx = self . nodes [ 0 ] . createrawtransaction ( [ ] , { p2sh : 10 , p2wsh : 10 , p2wpkh : 10 , p2sh_p2wsh : 10 , p2sh_p2wpkh : 10 , p2pkh : 10 } )
rawtx = self . nodes [ 0 ] . fundrawtransaction ( rawtx , { " changePosition " : 3 } )
signed_tx = self . nodes [ 0 ] . signrawtransactionwithwallet ( rawtx [ ' hex ' ] ) [ ' hex ' ]
txid = self . nodes [ 0 ] . sendrawtransaction ( signed_tx )
2021-08-19 11:10:24 -04:00
self . generate ( self . nodes [ 0 ] , 6 )
2018-06-27 20:05:54 -04:00
# 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 ' ] :
2021-02-01 12:52:07 -03:00
if out [ ' scriptPubKey ' ] [ ' address ' ] == p2sh :
2018-06-27 20:05:54 -04:00
p2sh_pos = out [ ' n ' ]
2021-02-01 12:52:07 -03:00
elif out [ ' scriptPubKey ' ] [ ' address ' ] == p2wsh :
2018-06-27 20:05:54 -04:00
p2wsh_pos = out [ ' n ' ]
2021-02-01 12:52:07 -03:00
elif out [ ' scriptPubKey ' ] [ ' address ' ] == p2wpkh :
2018-06-27 20:05:54 -04:00
p2wpkh_pos = out [ ' n ' ]
2021-02-01 12:52:07 -03:00
elif out [ ' scriptPubKey ' ] [ ' address ' ] == p2sh_p2wsh :
2018-06-27 20:05:54 -04:00
p2sh_p2wsh_pos = out [ ' n ' ]
2021-02-01 12:52:07 -03:00
elif out [ ' scriptPubKey ' ] [ ' address ' ] == p2sh_p2wpkh :
2018-06-27 20:05:54 -04:00
p2sh_p2wpkh_pos = out [ ' n ' ]
2021-02-01 12:52:07 -03:00
elif out [ ' scriptPubKey ' ] [ ' address ' ] == p2pkh :
2018-06-27 20:05:54 -04:00
p2pkh_pos = out [ ' n ' ]
2020-06-26 10:05:03 -04:00
inputs = [ { " txid " : txid , " vout " : p2wpkh_pos } , { " txid " : txid , " vout " : p2sh_p2wpkh_pos } , { " txid " : txid , " vout " : p2pkh_pos } ]
outputs = [ { self . nodes [ 1 ] . getnewaddress ( ) : 29.99 } ]
2018-06-27 20:05:54 -04:00
# spend single key from node 1
2020-06-26 10:05:03 -04:00
created_psbt = self . nodes [ 1 ] . walletcreatefundedpsbt ( inputs , outputs )
2020-07-26 07:07:45 -04:00
walletprocesspsbt_out = self . nodes [ 1 ] . walletprocesspsbt ( created_psbt [ ' psbt ' ] )
2020-06-08 19:27:16 -04:00
# Make sure it has both types of UTXOs
decoded = self . nodes [ 1 ] . decodepsbt ( walletprocesspsbt_out [ ' psbt ' ] )
assert ' non_witness_utxo ' in decoded [ ' inputs ' ] [ 0 ]
assert ' witness_utxo ' in decoded [ ' inputs ' ] [ 0 ]
2020-07-26 07:07:45 -04:00
# Check decodepsbt fee calculation (input values shall only be counted once per UTXO)
assert_equal ( decoded [ ' fee ' ] , created_psbt [ ' fee ' ] )
2018-06-27 20:05:54 -04:00
assert_equal ( walletprocesspsbt_out [ ' complete ' ] , True )
2023-09-05 13:10:21 -03:00
self . nodes [ 1 ] . sendrawtransaction ( walletprocesspsbt_out [ ' hex ' ] )
2018-06-27 20:05:54 -04:00
2020-11-04 09:13:17 -03:00
self . log . info ( " Test walletcreatefundedpsbt fee rate of 10000 sat/vB and 0.1 BTC/kvB produces a total fee at or slightly below -maxtxfee (~0.05290000) " )
res1 = self . nodes [ 1 ] . walletcreatefundedpsbt ( inputs , outputs , 0 , { " fee_rate " : 10000 , " add_inputs " : True } )
assert_approx ( res1 [ " fee " ] , 0.055 , 0.005 )
2020-12-04 07:22:34 -03:00
res2 = self . nodes [ 1 ] . walletcreatefundedpsbt ( inputs , outputs , 0 , { " feeRate " : " 0.1 " , " add_inputs " : True } )
2020-11-04 09:13:17 -03:00
assert_approx ( res2 [ " fee " ] , 0.055 , 0.005 )
2020-11-19 14:38:00 -03:00
2020-11-12 07:16:48 -03:00
self . log . info ( " Test min fee rate checks with walletcreatefundedpsbt are bypassed, e.g. a fee_rate under 1 sat/vB is allowed " )
2021-04-27 05:04:10 -04:00
res3 = self . nodes [ 1 ] . walletcreatefundedpsbt ( inputs , outputs , 0 , { " fee_rate " : " 0.999 " , " add_inputs " : True } )
2020-11-12 07:16:48 -03:00
assert_approx ( res3 [ " fee " ] , 0.00000381 , 0.0000001 )
res4 = self . nodes [ 1 ] . walletcreatefundedpsbt ( inputs , outputs , 0 , { " feeRate " : 0.00000999 , " add_inputs " : True } )
assert_approx ( res4 [ " fee " ] , 0.00000381 , 0.0000001 )
2020-11-04 09:13:17 -03:00
2020-11-19 14:38:00 -03:00
self . log . info ( " Test min fee rate checks with walletcreatefundedpsbt are bypassed and that funding non-standard ' zero-fee ' transactions is valid " )
2020-12-04 07:28:47 -03:00
for param , zero_value in product ( [ " fee_rate " , " feeRate " ] , [ 0 , 0.000 , 0.00000000 , " 0 " , " 0.000 " , " 0.00000000 " ] ) :
assert_equal ( 0 , self . nodes [ 1 ] . walletcreatefundedpsbt ( inputs , outputs , 0 , { param : zero_value , " add_inputs " : True } ) [ " fee " ] )
2020-11-19 14:38:00 -03:00
2020-11-04 09:13:17 -03:00
self . log . info ( " Test invalid fee rate settings " )
for param , value in { ( " fee_rate " , 100000 ) , ( " feeRate " , 1 ) } :
assert_raises_rpc_error ( - 4 , " Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate) " ,
self . nodes [ 1 ] . walletcreatefundedpsbt , inputs , outputs , 0 , { param : value , " add_inputs " : True } )
assert_raises_rpc_error ( - 3 , " Amount out of range " ,
2020-11-19 14:38:00 -03:00
self . nodes [ 1 ] . walletcreatefundedpsbt , inputs , outputs , 0 , { param : - 1 , " add_inputs " : True } )
2020-11-04 09:13:17 -03:00
assert_raises_rpc_error ( - 3 , " Amount is not a number or string " ,
2020-11-19 14:38:00 -03:00
self . nodes [ 1 ] . walletcreatefundedpsbt , inputs , outputs , 0 , { param : { " foo " : " bar " } , " add_inputs " : True } )
2020-12-03 06:55:15 -03:00
# Test fee rate values that don't pass fixed-point parsing checks.
for invalid_value in [ " " , 0.000000001 , 1e-09 , 1.111111111 , 1111111111111111 , " 31.999999999999999999999 " ] :
assert_raises_rpc_error ( - 3 , " Invalid amount " ,
self . nodes [ 1 ] . walletcreatefundedpsbt , inputs , outputs , 0 , { param : invalid_value , " add_inputs " : True } )
2020-12-02 09:55:26 -03:00
# Test fee_rate values that cannot be represented in sat/vB.
2023-07-26 21:14:19 -04:00
for invalid_value in [ 0.0001 , 0.00000001 , 0.00099999 , 31.99999999 ] :
2020-12-02 09:55:26 -03:00
assert_raises_rpc_error ( - 3 , " Invalid amount " ,
self . nodes [ 1 ] . walletcreatefundedpsbt , inputs , outputs , 0 , { " fee_rate " : invalid_value , " add_inputs " : True } )
2020-11-04 09:13:17 -03:00
self . log . info ( " - raises RPC error if both feeRate and fee_rate are passed " )
assert_raises_rpc_error ( - 8 , " Cannot specify both fee_rate (sat/vB) and feeRate (BTC/kvB) " ,
self . nodes [ 1 ] . walletcreatefundedpsbt , inputs , outputs , 0 , { " fee_rate " : 0.1 , " feeRate " : 0.1 , " add_inputs " : True } )
self . log . info ( " - raises RPC error if both feeRate and estimate_mode passed " )
assert_raises_rpc_error ( - 8 , " Cannot specify both estimate_mode and feeRate " ,
self . nodes [ 1 ] . walletcreatefundedpsbt , inputs , outputs , 0 , { " estimate_mode " : " economical " , " feeRate " : 0.1 , " add_inputs " : True } )
for param in [ " feeRate " , " fee_rate " ] :
self . log . info ( " - raises RPC error if both {} and conf_target are passed " . format ( param ) )
assert_raises_rpc_error ( - 8 , " Cannot specify both conf_target and {} . Please provide either a confirmation "
" target in blocks for automatic fee estimation, or an explicit fee rate. " . format ( param ) ,
self . nodes [ 1 ] . walletcreatefundedpsbt , inputs , outputs , 0 , { param : 1 , " conf_target " : 1 , " add_inputs " : True } )
self . log . info ( " - raises RPC error if both fee_rate and estimate_mode are passed " )
assert_raises_rpc_error ( - 8 , " Cannot specify both estimate_mode and fee_rate " ,
self . nodes [ 1 ] . walletcreatefundedpsbt , inputs , outputs , 0 , { " fee_rate " : 1 , " estimate_mode " : " economical " , " add_inputs " : True } )
2020-06-26 10:06:43 -04:00
self . log . info ( " - raises RPC error with invalid estimate_mode settings " )
for k , v in { " number " : 42 , " object " : { " foo " : " bar " } } . items ( ) :
2022-10-04 13:51:24 -03:00
assert_raises_rpc_error ( - 3 , f " JSON value of type { k } for field estimate_mode is not of expected type string " ,
2020-11-04 09:13:17 -03:00
self . nodes [ 1 ] . walletcreatefundedpsbt , inputs , outputs , 0 , { " estimate_mode " : v , " conf_target " : 0.1 , " add_inputs " : True } )
for mode in [ " " , " foo " , Decimal ( " 3.141592 " ) ] :
2020-11-10 08:29:01 -03:00
assert_raises_rpc_error ( - 8 , ' Invalid estimate_mode parameter, must be one of: " unset " , " economical " , " conservative " ' ,
2020-11-04 09:13:17 -03:00
self . nodes [ 1 ] . walletcreatefundedpsbt , inputs , outputs , 0 , { " estimate_mode " : mode , " conf_target " : 0.1 , " add_inputs " : True } )
2020-06-26 10:06:43 -04:00
self . log . info ( " - raises RPC error with invalid conf_target settings " )
2020-11-04 09:13:17 -03:00
for mode in [ " unset " , " economical " , " conservative " ] :
2020-06-26 10:06:43 -04:00
self . log . debug ( " {} " . format ( mode ) )
for k , v in { " string " : " " , " object " : { " foo " : " bar " } } . items ( ) :
2022-10-04 13:51:24 -03:00
assert_raises_rpc_error ( - 3 , f " JSON value of type { k } for field conf_target is not of expected type number " ,
2020-11-04 09:13:17 -03:00
self . nodes [ 1 ] . walletcreatefundedpsbt , inputs , outputs , 0 , { " estimate_mode " : mode , " conf_target " : v , " add_inputs " : True } )
for n in [ - 1 , 0 , 1009 ] :
assert_raises_rpc_error ( - 8 , " Invalid conf_target, must be between 1 and 1008 " , # max value of 1008 per src/policy/fees.h
self . nodes [ 1 ] . walletcreatefundedpsbt , inputs , outputs , 0 , { " estimate_mode " : mode , " conf_target " : n , " add_inputs " : True } )
self . log . info ( " Test walletcreatefundedpsbt with too-high fee rate produces total fee well above -maxtxfee and raises RPC error " )
2019-07-02 10:16:36 -04:00
# previously this was silently capped at -maxtxfee
2020-06-26 10:05:03 -04:00
for bool_add , outputs_array in { True : outputs , False : [ { self . nodes [ 1 ] . getnewaddress ( ) : 1 } ] } . items ( ) :
2020-11-04 09:13:17 -03:00
msg = " Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate) "
assert_raises_rpc_error ( - 4 , msg , self . nodes [ 1 ] . walletcreatefundedpsbt , inputs , outputs_array , 0 , { " fee_rate " : 1000000 , " add_inputs " : bool_add } )
assert_raises_rpc_error ( - 4 , msg , self . nodes [ 1 ] . walletcreatefundedpsbt , inputs , outputs_array , 0 , { " feeRate " : 1 , " add_inputs " : bool_add } )
2019-06-28 22:44:38 -04:00
2020-06-26 10:05:03 -04:00
self . log . info ( " Test various PSBT operations " )
2018-06-27 20:05:54 -04:00
# partially sign multisig things with node 1
2022-11-10 14:04:07 -03:00
psbtx = wmulti . walletcreatefundedpsbt ( inputs = [ { " txid " : txid , " vout " : p2wsh_pos } , { " txid " : txid , " vout " : p2sh_pos } , { " txid " : txid , " vout " : p2sh_p2wsh_pos } ] , outputs = { self . nodes [ 1 ] . getnewaddress ( ) : 29.99 } , changeAddress = self . nodes [ 1 ] . getrawchangeaddress ( ) ) [ ' psbt ' ]
2018-06-27 20:05:54 -04:00
walletprocesspsbt_out = self . nodes [ 1 ] . walletprocesspsbt ( psbtx )
psbtx = walletprocesspsbt_out [ ' psbt ' ]
assert_equal ( walletprocesspsbt_out [ ' complete ' ] , False )
2019-07-16 15:33:35 -04:00
# Unload wmulti, we don't need it anymore
wmulti . unloadwallet ( )
2018-06-27 20:05:54 -04:00
# partially sign with node 2. This should be complete and sendable
walletprocesspsbt_out = self . nodes [ 2 ] . walletprocesspsbt ( psbtx )
assert_equal ( walletprocesspsbt_out [ ' complete ' ] , True )
2023-09-05 13:10:21 -03:00
self . nodes [ 2 ] . sendrawtransaction ( walletprocesspsbt_out [ ' hex ' ] )
2018-06-27 20:05:54 -04:00
# check that walletprocesspsbt fails to decode a non-psbt
rawtx = self . nodes [ 1 ] . createrawtransaction ( [ { " txid " : txid , " vout " : p2wpkh_pos } ] , { self . nodes [ 1 ] . getnewaddress ( ) : 9.99 } )
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
rawtx = self . nodes [ 0 ] . createrawtransaction ( [ ] , { self . nodes [ 1 ] . getnewaddress ( ) : 10 } )
rawtx = self . nodes [ 0 ] . fundrawtransaction ( rawtx )
new_psbt = self . nodes [ 0 ] . converttopsbt ( rawtx [ ' hex ' ] )
self . nodes [ 0 ] . decodepsbt ( new_psbt )
2018-11-27 14:46:20 -03:00
# Make sure that a non-psbt with signatures cannot be converted
2018-06-27 20:05:54 -04:00
signedtx = self . nodes [ 0 ] . signrawtransactionwithwallet ( rawtx [ ' hex ' ] )
2023-03-24 13:46:15 -03:00
assert_raises_rpc_error ( - 22 , " Inputs must not have scriptSigs and scriptWitnesses " ,
self . nodes [ 0 ] . converttopsbt , hexstring = signedtx [ ' hex ' ] ) # permitsigdata=False by default
assert_raises_rpc_error ( - 22 , " Inputs must not have scriptSigs and scriptWitnesses " ,
self . nodes [ 0 ] . converttopsbt , hexstring = signedtx [ ' hex ' ] , permitsigdata = False )
assert_raises_rpc_error ( - 22 , " Inputs must not have scriptSigs and scriptWitnesses " ,
self . nodes [ 0 ] . converttopsbt , hexstring = signedtx [ ' hex ' ] , permitsigdata = False , iswitness = True )
2018-09-29 23:09:15 -03:00
# Unless we allow it to convert and strip signatures
2023-03-24 13:46:15 -03:00
self . nodes [ 0 ] . converttopsbt ( hexstring = signedtx [ ' hex ' ] , permitsigdata = True )
2018-06-27 20:05:54 -04:00
# Create outputs to nodes 1 and 2
2023-08-13 10:02:10 -04:00
# (note that we intentionally create two different txs here, as we want
# to check that each node is missing prevout data for one of the two
# utxos, see "should only have data for one input" test below)
2018-06-27 20:05:54 -04:00
node1_addr = self . nodes [ 1 ] . getnewaddress ( )
node2_addr = self . nodes [ 2 ] . getnewaddress ( )
2023-08-13 10:02:10 -04:00
utxo1 = self . create_outpoints ( self . nodes [ 0 ] , outputs = [ { node1_addr : 13 } ] ) [ 0 ]
utxo2 = self . create_outpoints ( self . nodes [ 0 ] , outputs = [ { node2_addr : 13 } ] ) [ 0 ]
self . generate ( self . nodes [ 0 ] , 6 ) [ 0 ]
2018-06-27 20:05:54 -04:00
# Create a psbt spending outputs from nodes 1 and 2
2023-08-13 10:02:10 -04:00
psbt_orig = self . nodes [ 0 ] . createpsbt ( [ utxo1 , utxo2 ] , { self . nodes [ 0 ] . getnewaddress ( ) : 25.999 } )
2018-06-27 20:05:54 -04:00
# Update psbts, should only have data for one input and not the other
2020-01-31 06:37:41 -03:00
psbt1 = self . nodes [ 1 ] . walletprocesspsbt ( psbt_orig , False , " ALL " ) [ ' psbt ' ]
2018-06-27 20:05:54 -04:00
psbt1_decoded = self . nodes [ 0 ] . decodepsbt ( psbt1 )
assert psbt1_decoded [ ' inputs ' ] [ 0 ] and not psbt1_decoded [ ' inputs ' ] [ 1 ]
2020-01-31 06:37:41 -03:00
# Check that BIP32 path was added
assert " bip32_derivs " in psbt1_decoded [ ' inputs ' ] [ 0 ]
psbt2 = self . nodes [ 2 ] . walletprocesspsbt ( psbt_orig , False , " ALL " , False ) [ ' psbt ' ]
2018-06-27 20:05:54 -04:00
psbt2_decoded = self . nodes [ 0 ] . decodepsbt ( psbt2 )
assert not psbt2_decoded [ ' inputs ' ] [ 0 ] and psbt2_decoded [ ' inputs ' ] [ 1 ]
2020-01-31 06:37:41 -03:00
# Check that BIP32 paths were not added
assert " bip32_derivs " not in psbt2_decoded [ ' inputs ' ] [ 1 ]
# Sign PSBTs (workaround issue #18039)
psbt1 = self . nodes [ 1 ] . walletprocesspsbt ( psbt_orig ) [ ' psbt ' ]
psbt2 = self . nodes [ 2 ] . walletprocesspsbt ( psbt_orig ) [ ' psbt ' ]
2018-06-27 20:05:54 -04:00
# 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 )
2021-08-19 11:10:24 -04:00
self . generate ( self . nodes [ 0 ] , 6 )
2018-06-27 20:05:54 -04:00
2018-08-14 22:52:16 -03: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-07-12 08:30:10 -04:00
psbtx_info = self . nodes [ 0 ] . walletcreatefundedpsbt ( [ { " txid " : unspent [ " txid " ] , " vout " : unspent [ " vout " ] } ] , [ { self . nodes [ 2 ] . getnewaddress ( ) : unspent [ " amount " ] + 1 } ] , block_height + 2 , { " replaceable " : False , " add_inputs " : True } , False )
2018-08-14 22:52:16 -03:00
decoded_psbt = self . nodes [ 0 ] . decodepsbt ( psbtx_info [ " psbt " ] )
2018-08-24 18:03:55 -03:00
for tx_in , psbt_in in zip ( decoded_psbt [ " tx " ] [ " vin " ] , decoded_psbt [ " inputs " ] ) :
2019-07-27 13:35:07 -04:00
assert_greater_than ( tx_in [ " sequence " ] , MAX_BIP125_RBF_SEQUENCE )
2019-04-06 19:38:51 -03:00
assert " bip32_derivs " not in psbt_in
2018-08-14 22:52:16 -03:00
assert_equal ( decoded_psbt [ " tx " ] [ " locktime " ] , block_height + 2 )
2019-04-27 13:44:38 -04:00
# Same construction with only locktime set and RBF explicitly enabled
2019-07-12 08:30:10 -04:00
psbtx_info = self . nodes [ 0 ] . walletcreatefundedpsbt ( [ { " txid " : unspent [ " txid " ] , " vout " : unspent [ " vout " ] } ] , [ { self . nodes [ 2 ] . getnewaddress ( ) : unspent [ " amount " ] + 1 } ] , block_height , { " replaceable " : True , " add_inputs " : True } , True )
2018-08-14 22:52:16 -03:00
decoded_psbt = self . nodes [ 0 ] . decodepsbt ( psbtx_info [ " psbt " ] )
2018-08-24 18:03:55 -03:00
for tx_in , psbt_in in zip ( decoded_psbt [ " tx " ] [ " vin " ] , decoded_psbt [ " inputs " ] ) :
2019-04-27 13:44:38 -04:00
assert_equal ( tx_in [ " sequence " ] , MAX_BIP125_RBF_SEQUENCE )
2018-08-24 18:03:55 -03:00
assert " bip32_derivs " in psbt_in
2018-08-14 22:52:16 -03:00
assert_equal ( decoded_psbt [ " tx " ] [ " locktime " ] , block_height )
# Same construction without optional arguments
2019-07-12 08:30:10 -04:00
psbtx_info = self . nodes [ 0 ] . walletcreatefundedpsbt ( [ ] , [ { self . nodes [ 2 ] . getnewaddress ( ) : unspent [ " amount " ] + 1 } ] )
2018-08-14 22:52:16 -03:00
decoded_psbt = self . nodes [ 0 ] . decodepsbt ( psbtx_info [ " psbt " ] )
2020-01-31 06:37:41 -03:00
for tx_in , psbt_in in zip ( decoded_psbt [ " tx " ] [ " vin " ] , decoded_psbt [ " inputs " ] ) :
2019-04-27 13:44:38 -04:00
assert_equal ( tx_in [ " sequence " ] , MAX_BIP125_RBF_SEQUENCE )
2020-01-31 06:37:41 -03:00
assert " bip32_derivs " in psbt_in
2018-08-14 22:52:16 -03:00
assert_equal ( decoded_psbt [ " tx " ] [ " locktime " ] , 0 )
2019-07-27 13:35:07 -04:00
# Same construction without optional arguments, for a node with -walletrbf=0
unspent1 = self . nodes [ 1 ] . listunspent ( ) [ 0 ]
2019-07-12 08:30:10 -04:00
psbtx_info = self . nodes [ 1 ] . walletcreatefundedpsbt ( [ { " txid " : unspent1 [ " txid " ] , " vout " : unspent1 [ " vout " ] } ] , [ { self . nodes [ 2 ] . getnewaddress ( ) : unspent1 [ " amount " ] + 1 } ] , block_height , { " add_inputs " : True } )
2019-07-27 13:35:07 -04:00
decoded_psbt = self . nodes [ 1 ] . decodepsbt ( psbtx_info [ " psbt " ] )
2020-01-31 06:37:41 -03:00
for tx_in , psbt_in in zip ( decoded_psbt [ " tx " ] [ " vin " ] , decoded_psbt [ " inputs " ] ) :
2019-07-27 13:35:07 -04:00
assert_greater_than ( tx_in [ " sequence " ] , MAX_BIP125_RBF_SEQUENCE )
2020-01-31 06:37:41 -03:00
assert " bip32_derivs " in psbt_in
2019-07-27 13:35:07 -04:00
2018-10-03 02:41:03 -03: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 )
2020-09-12 12:07:59 -03:00
# Make sure the wallet's change type is respected by default
small_output = { self . nodes [ 0 ] . getnewaddress ( ) : 0.1 }
psbtx_native = self . nodes [ 0 ] . walletcreatefundedpsbt ( [ ] , [ small_output ] )
self . assert_change_type ( psbtx_native , " witness_v0_keyhash " )
psbtx_legacy = self . nodes [ 1 ] . walletcreatefundedpsbt ( [ ] , [ small_output ] )
self . assert_change_type ( psbtx_legacy , " pubkeyhash " )
# Make sure the change type of the wallet can also be overwritten
psbtx_np2wkh = self . nodes [ 1 ] . walletcreatefundedpsbt ( [ ] , [ small_output ] , 0 , { " change_type " : " p2sh-segwit " } )
self . assert_change_type ( psbtx_np2wkh , " scripthash " )
2020-09-12 12:16:23 -03:00
# Make sure the change type cannot be specified if a change address is given
invalid_options = { " change_type " : " legacy " , " changeAddress " : self . nodes [ 0 ] . getnewaddress ( ) }
assert_raises_rpc_error ( - 8 , " both change address and address type options " , self . nodes [ 0 ] . walletcreatefundedpsbt , [ ] , [ small_output ] , 0 , invalid_options )
2018-10-30 04:41:19 -03:00
# Regression test for 14473 (mishandling of already-signed witness transaction):
2019-07-12 08:30:10 -04:00
psbtx_info = self . nodes [ 0 ] . walletcreatefundedpsbt ( [ { " txid " : unspent [ " txid " ] , " vout " : unspent [ " vout " ] } ] , [ { self . nodes [ 2 ] . getnewaddress ( ) : unspent [ " amount " ] + 1 } ] , 0 , { " add_inputs " : True } )
2018-10-30 04:41:19 -03: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-14 22:52:16 -03:00
2021-03-10 11:37:18 -03:00
# Make sure unsafe inputs are included if specified
self . nodes [ 2 ] . createwallet ( wallet_name = " unsafe " )
wunsafe = self . nodes [ 2 ] . get_wallet_rpc ( " unsafe " )
self . nodes [ 0 ] . sendtoaddress ( wunsafe . getnewaddress ( ) , 2 )
self . sync_mempools ( )
assert_raises_rpc_error ( - 4 , " Insufficient funds " , wunsafe . walletcreatefundedpsbt , [ ] , [ { self . nodes [ 0 ] . getnewaddress ( ) : 1 } ] )
wunsafe . walletcreatefundedpsbt ( [ ] , [ { self . nodes [ 0 ] . getnewaddress ( ) : 1 } ] , 0 , { " include_unsafe " : True } )
2018-06-27 20:05:54 -04: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 ' ]
2022-08-09 17:20:23 -04:00
invalid_with_msgs = d [ " invalid_with_msg " ]
2018-06-27 20:05:54 -04:00
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 )
2022-08-09 17:20:23 -04:00
for invalid in invalid_with_msgs :
psbt , msg = invalid
assert_raises_rpc_error ( - 22 , f " TX decode failed { msg } " , self . nodes [ 0 ] . decodepsbt , psbt )
2018-06-27 20:05:54 -04:00
# Valid PSBTs
for valid in valids :
self . nodes [ 0 ] . decodepsbt ( valid )
# Creator Tests
for creator in creators :
2022-07-13 16:29:27 -04:00
created_tx = self . nodes [ 0 ] . createpsbt ( inputs = creator [ ' inputs ' ] , outputs = creator [ ' outputs ' ] , replaceable = False )
2018-06-27 20:05:54 -04:00
assert_equal ( created_tx , creator [ ' result ' ] )
# Signer tests
for i , signer in enumerate ( signers ) :
2019-07-16 15:33:35 -04:00
self . nodes [ 2 ] . createwallet ( wallet_name = " wallet {} " . format ( i ) )
2018-08-08 19:14:01 -04:00
wrpc = self . nodes [ 2 ] . get_wallet_rpc ( " wallet {} " . format ( i ) )
2018-06-27 20:05:54 -04:00
for key in signer [ ' privkeys ' ] :
2018-08-08 19:14:01 -04:00
wrpc . importprivkey ( key )
2021-07-20 22:05:28 -04:00
signed_tx = wrpc . walletprocesspsbt ( signer [ ' psbt ' ] , True , " ALL " ) [ ' psbt ' ]
2018-06-27 20:05:54 -04: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 00:26:43 -03:00
# Empty combiner test
assert_raises_rpc_error ( - 8 , " Parameter ' txs ' cannot be empty " , self . nodes [ 0 ] . combinepsbt , [ ] )
2018-06-27 20:05:54 -04: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 15:43:06 -03:00
# Unload extra wallets
for i , signer in enumerate ( signers ) :
self . nodes [ 2 ] . unloadwallet ( " wallet {} " . format ( i ) )
2023-03-04 22:43:39 -03:00
if self . options . descriptors :
self . test_utxo_conversion ( )
2024-06-28 07:45:47 -04:00
self . test_psbt_incomplete_after_invalid_modification ( )
2018-09-20 15:43:06 -03:00
2021-05-24 09:22:10 -04:00
self . test_input_confs_control ( )
2018-11-08 12:08:46 -03: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-27 20:05:54 -04:00
2019-01-30 02:32:38 -03: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-20 20:08:25 -04:00
# Send to all types of addresses
addr1 = self . nodes [ 1 ] . getnewaddress ( " " , " bech32 " )
addr2 = self . nodes [ 1 ] . getnewaddress ( " " , " legacy " )
addr3 = self . nodes [ 1 ] . getnewaddress ( " " , " p2sh-segwit " )
2023-08-13 10:02:10 -04:00
utxo1 , utxo2 , utxo3 = self . create_outpoints ( self . nodes [ 1 ] , outputs = [ { addr1 : 11 } , { addr2 : 11 } , { addr3 : 11 } ] )
2018-07-20 20:08:25 -04:00
self . sync_all ( )
2019-02-16 19:59:16 -03: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
2023-08-13 10:02:10 -04:00
psbt = self . nodes [ 1 ] . createpsbt ( [ utxo1 , utxo2 , utxo3 ] , { self . nodes [ 0 ] . getnewaddress ( ) : 32.999 } )
2018-07-20 20:08:25 -04:00
decoded = self . nodes [ 1 ] . decodepsbt ( psbt )
2019-02-16 19:59:16 -03: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-20 20:08:25 -04:00
updated = self . nodes [ 1 ] . utxoupdatepsbt ( psbt )
decoded = self . nodes [ 1 ] . decodepsbt ( updated )
2022-08-23 15:24:00 -04:00
test_psbt_input_keys ( decoded [ ' inputs ' ] [ 0 ] , [ ' witness_utxo ' , ' non_witness_utxo ' ] )
test_psbt_input_keys ( decoded [ ' inputs ' ] [ 1 ] , [ ' non_witness_utxo ' ] )
test_psbt_input_keys ( decoded [ ' inputs ' ] [ 2 ] , [ ' non_witness_utxo ' ] )
2019-02-16 19:59:16 -03:00
# 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 13:34:18 -04:00
updated = self . nodes [ 1 ] . utxoupdatepsbt ( psbt = psbt , descriptors = descs )
2019-02-16 19:59:16 -03:00
decoded = self . nodes [ 1 ] . decodepsbt ( updated )
2022-08-23 15:24:00 -04:00
test_psbt_input_keys ( decoded [ ' inputs ' ] [ 0 ] , [ ' witness_utxo ' , ' non_witness_utxo ' , ' bip32_derivs ' ] )
test_psbt_input_keys ( decoded [ ' inputs ' ] [ 1 ] , [ ' non_witness_utxo ' , ' bip32_derivs ' ] )
test_psbt_input_keys ( decoded [ ' inputs ' ] [ 2 ] , [ ' non_witness_utxo ' , ' witness_utxo ' , ' bip32_derivs ' , ' redeem_script ' ] )
2018-07-20 20:08:25 -04:00
2018-07-20 21:24:16 -04:00
# Two PSBTs with a common input should not be joinable
2023-08-13 10:02:10 -04:00
psbt1 = self . nodes [ 1 ] . createpsbt ( [ utxo1 ] , { self . nodes [ 0 ] . getnewaddress ( ) : Decimal ( ' 10.999 ' ) } )
2018-07-20 21:24:16 -04:00
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 " )
2023-08-13 10:02:10 -04:00
utxo4 = self . create_outpoints ( self . nodes [ 0 ] , outputs = [ { addr4 : 5 } ] ) [ 0 ]
2021-08-19 11:10:24 -04:00
self . generate ( self . nodes [ 0 ] , 6 )
2023-08-13 10:02:10 -04:00
psbt2 = self . nodes [ 1 ] . createpsbt ( [ utxo4 ] , { self . nodes [ 0 ] . getnewaddress ( ) : Decimal ( ' 4.999 ' ) } )
2018-07-20 21:24:16 -04:00
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-20 20:08:25 -04:00
2019-08-14 14:29:55 -04:00
# Check that joining shuffles the inputs and outputs
# 10 attempts should be enough to get a shuffled join
shuffled = False
2020-08-02 19:10:56 -04:00
for _ in range ( 10 ) :
2019-08-14 14:29:55 -04:00
shuffled_joined = self . nodes [ 0 ] . joinpsbts ( [ psbt , psbt2 ] )
shuffled | = joined != shuffled_joined
if shuffled :
break
assert shuffled
2018-07-31 20:58:01 -04:00
# Newly created PSBT needs UTXOs and updating
addr = self . nodes [ 1 ] . getnewaddress ( " " , " p2sh-segwit " )
2023-08-13 10:02:10 -04:00
utxo = self . create_outpoints ( self . nodes [ 0 ] , outputs = [ { addr : 7 } ] ) [ 0 ]
2018-07-31 20:58:01 -04:00
addrinfo = self . nodes [ 1 ] . getaddressinfo ( addr )
2023-08-13 10:02:10 -04:00
self . generate ( self . nodes [ 0 ] , 6 ) [ 0 ]
psbt = self . nodes [ 1 ] . createpsbt ( [ utxo ] , { self . nodes [ 0 ] . getnewaddress ( " " , " p2sh-segwit " ) : Decimal ( ' 6.999 ' ) } )
2018-07-31 20:58:01 -04:00
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 03:50:44 -03:00
assert analyzed [ ' fee ' ] == Decimal ( ' 0.001 ' ) and analyzed [ ' estimated_vsize ' ] == 134 and analyzed [ ' estimated_feerate ' ] == Decimal ( ' 0.00746268 ' )
2018-07-31 20:58:01 -04: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-20 20:08:25 -04:00
2019-11-19 16:54:13 -03: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 18:15:22 -03: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 ' )
2020-02-28 13:27:13 -03:00
self . log . info ( " PSBT with signed, but not finalized, inputs should have Finalizer as next " )
analysis = self . nodes [ 0 ] . analyzepsbt ( ' cHNidP8BAHECAAAAAZYezcxdnbXoQCmrD79t/LzDgtUo9ERqixk8wgioAobrAAAAAAD9////AlDDAAAAAAAAFgAUy/UxxZuzZswcmFnN/E9DGSiHLUsuGPUFAAAAABYAFLsH5o0R38wXx+X2cCosTMCZnQ4baAAAAAABAR8A4fUFAAAAABYAFOBI2h5thf3+Lflb2LGCsVSZwsltIgIC/i4dtVARCRWtROG0HHoGcaVklzJUcwo5homgGkSNAnJHMEQCIGx7zKcMIGr7cEES9BR4Kdt/pzPTK3fKWcGyCJXb7MVnAiALOBgqlMH4GbC1HDh/HmylmO54fyEy4lKde7/BT/PWxwEBAwQBAAAAIgYC/i4dtVARCRWtROG0HHoGcaVklzJUcwo5homgGkSNAnIYDwVpQ1QAAIABAACAAAAAgAAAAAAAAAAAAAAiAgL+CIiB59NSCssOJRGiMYQK1chahgAaaJpIXE41Cyir+xgPBWlDVAAAgAEAAIAAAACAAQAAAAAAAAAA ' )
assert_equal ( analysis [ ' next ' ] , ' finalizer ' )
2019-10-15 18:15:22 -03:00
analysis = self . nodes [ 0 ] . analyzepsbt ( ' cHNidP8BAHECAAAAAfA00BFgAm6tp86RowwH6BMImQNL5zXUcTT97XoLGz0BAAAAAAD/////AgCAgWrj0AcAFgAUKNw0x8HRctAgmvoevm4u1SbN7XL87QKVAAAAABYAFPck4gF7iL4NL4wtfRAKgQbghiTUAAAAAAABAR8A8gUqAQAAABYAFJUDtxf2PHo641HEOBOAIvFMNTr2AAAA ' )
assert_equal ( analysis [ ' next ' ] , ' creator ' )
assert_equal ( analysis [ ' error ' ] , ' PSBT is not valid. Output amount invalid ' )
2024-04-11 10:51:43 -04:00
assert_raises_rpc_error ( - 22 , " TX decode failed " , self . nodes [ 0 ] . analyzepsbt , " cHNidP8BAJoCAAAAAkvEW8NnDtdNtDpsmze+Ht2LH35IJcKv00jKAlUs21RrAwAAAAD/////S8Rbw2cO1020OmybN74e3Ysffkglwq/TSMoCVSzbVGsBAAAAAP7///8CwLYClQAAAAAWABSNJKzjaUb3uOxixsvh1GGE3fW7zQD5ApUAAAAAFgAUKNw0x8HRctAgmvoevm4u1SbN7XIAAAAAAAEAnQIAAAACczMa321tVHuN4GKWKRncycI22aX3uXgwSFUKM2orjRsBAAAAAP7///9zMxrfbW1Ue43gYpYpGdzJwjbZpfe5eDBIVQozaiuNGwAAAAAA/v///wIA+QKVAAAAABl2qRT9zXUVA8Ls5iVqynLHe5/vSe1XyYisQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAAAAAQEfQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAA== " )
2019-10-15 18:26:46 -03:00
2024-04-11 10:51:43 -04:00
assert_raises_rpc_error ( - 22 , " TX decode failed " , self . nodes [ 0 ] . walletprocesspsbt , " cHNidP8BAJoCAAAAAkvEW8NnDtdNtDpsmze+Ht2LH35IJcKv00jKAlUs21RrAwAAAAD/////S8Rbw2cO1020OmybN74e3Ysffkglwq/TSMoCVSzbVGsBAAAAAP7///8CwLYClQAAAAAWABSNJKzjaUb3uOxixsvh1GGE3fW7zQD5ApUAAAAAFgAUKNw0x8HRctAgmvoevm4u1SbN7XIAAAAAAAEAnQIAAAACczMa321tVHuN4GKWKRncycI22aX3uXgwSFUKM2orjRsBAAAAAP7///9zMxrfbW1Ue43gYpYpGdzJwjbZpfe5eDBIVQozaiuNGwAAAAAA/v///wIA+QKVAAAAABl2qRT9zXUVA8Ls5iVqynLHe5/vSe1XyYisQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAAAAAQEfQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAA== " )
2019-10-15 18:26:46 -03:00
2021-10-05 17:45:10 -03:00
self . log . info ( " Test that we can fund psbts with external inputs specified " )
2023-05-23 17:38:31 -04:00
privkey , _ = generate_keypair ( wif = True )
2019-10-21 17:55:07 -03:00
2021-10-05 17:45:10 -03:00
self . nodes [ 1 ] . createwallet ( " extfund " )
wallet = self . nodes [ 1 ] . get_wallet_rpc ( " extfund " )
2022-03-24 12:50:02 -03:00
# Make a weird but signable script. sh(wsh(pkh())) descriptor accomplishes this
desc = descsum_create ( " sh(wsh(pkh( {} ))) " . format ( privkey ) )
2019-10-21 17:55:07 -03:00
if self . options . descriptors :
res = self . nodes [ 0 ] . importdescriptors ( [ { " desc " : desc , " timestamp " : " now " } ] )
else :
res = self . nodes [ 0 ] . importmulti ( [ { " desc " : desc , " timestamp " : " now " } ] )
assert res [ 0 ] [ " success " ]
addr = self . nodes [ 0 ] . deriveaddresses ( desc ) [ 0 ]
addr_info = self . nodes [ 0 ] . getaddressinfo ( addr )
self . nodes [ 0 ] . sendtoaddress ( addr , 10 )
2021-10-05 17:45:10 -03:00
self . nodes [ 0 ] . sendtoaddress ( wallet . getnewaddress ( ) , 10 )
2021-10-05 20:18:33 -03:00
self . generate ( self . nodes [ 0 ] , 6 )
2019-10-21 17:55:07 -03:00
ext_utxo = self . nodes [ 0 ] . listunspent ( addresses = [ addr ] ) [ 0 ]
# An external input without solving data should result in an error
2022-08-05 11:51:44 -04:00
assert_raises_rpc_error ( - 4 , " Not solvable pre-selected input COutPoint( %s , %s ) " % ( ext_utxo [ " txid " ] [ 0 : 10 ] , ext_utxo [ " vout " ] ) , wallet . walletcreatefundedpsbt , [ ext_utxo ] , { self . nodes [ 0 ] . getnewaddress ( ) : 15 } )
2019-10-21 17:55:07 -03:00
# But funding should work when the solving data is provided
2022-03-24 12:50:02 -03:00
psbt = wallet . walletcreatefundedpsbt ( [ ext_utxo ] , { self . nodes [ 0 ] . getnewaddress ( ) : 15 } , 0 , { " add_inputs " : True , " solving_data " : { " pubkeys " : [ addr_info [ ' pubkey ' ] ] , " scripts " : [ addr_info [ " embedded " ] [ " scriptPubKey " ] , addr_info [ " embedded " ] [ " embedded " ] [ " scriptPubKey " ] ] } } )
2021-10-05 17:45:10 -03:00
signed = wallet . walletprocesspsbt ( psbt [ ' psbt ' ] )
2019-10-21 17:55:07 -03:00
assert not signed [ ' complete ' ]
signed = self . nodes [ 0 ] . walletprocesspsbt ( signed [ ' psbt ' ] )
assert signed [ ' complete ' ]
2021-10-05 17:45:10 -03:00
psbt = wallet . walletcreatefundedpsbt ( [ ext_utxo ] , { self . nodes [ 0 ] . getnewaddress ( ) : 15 } , 0 , { " add_inputs " : True , " solving_data " : { " descriptors " : [ desc ] } } )
signed = wallet . walletprocesspsbt ( psbt [ ' psbt ' ] )
2019-10-21 17:55:07 -03:00
assert not signed [ ' complete ' ]
signed = self . nodes [ 0 ] . walletprocesspsbt ( signed [ ' psbt ' ] )
assert signed [ ' complete ' ]
2023-09-05 13:10:21 -03:00
final = signed [ ' hex ' ]
2021-10-05 17:45:10 -03:00
dec = self . nodes [ 0 ] . decodepsbt ( signed [ " psbt " ] )
for i , txin in enumerate ( dec [ " tx " ] [ " vin " ] ) :
if txin [ " txid " ] == ext_utxo [ " txid " ] and txin [ " vout " ] == ext_utxo [ " vout " ] :
input_idx = i
break
psbt_in = dec [ " inputs " ] [ input_idx ]
# Calculate the input weight
2024-03-01 08:43:36 -03:00
# (prevout + sequence + length of scriptSig + scriptsig) * WITNESS_SCALE_FACTOR + len of num scriptWitness stack items + (length of stack item + stack item) * N stack items
# Note that occasionally this weight estimate may be slightly larger or smaller than the real weight
# as sometimes ECDSA signatures are one byte shorter than expected with a probability of 1/128
2021-10-05 17:45:10 -03:00
len_scriptsig = len ( psbt_in [ " final_scriptSig " ] [ " hex " ] ) / / 2 if " final_scriptSig " in psbt_in else 0
2024-03-01 08:43:36 -03:00
len_scriptsig + = len ( ser_compact_size ( len_scriptsig ) )
len_scriptwitness = ( sum ( [ ( len ( x ) / / 2 ) + len ( ser_compact_size ( len ( x ) / / 2 ) ) for x in psbt_in [ " final_scriptwitness " ] ] ) + len ( ser_compact_size ( len ( psbt_in [ " final_scriptwitness " ] ) ) ) ) if " final_scriptwitness " in psbt_in else 0
len_prevout_txid = 32
len_prevout_index = 4
len_sequence = 4
input_weight = ( ( len_prevout_txid + len_prevout_index + len_sequence + len_scriptsig ) * WITNESS_SCALE_FACTOR ) + len_scriptwitness
2021-10-05 17:45:10 -03:00
low_input_weight = input_weight / / 2
high_input_weight = input_weight * 2
# Input weight error conditions
assert_raises_rpc_error (
- 8 ,
" Input weights should be specified in inputs rather than in options. " ,
wallet . walletcreatefundedpsbt ,
inputs = [ ext_utxo ] ,
outputs = { self . nodes [ 0 ] . getnewaddress ( ) : 15 } ,
options = { " input_weights " : [ { " txid " : ext_utxo [ " txid " ] , " vout " : ext_utxo [ " vout " ] , " weight " : 1000 } ] }
)
# Funding should also work if the input weight is provided
psbt = wallet . walletcreatefundedpsbt (
inputs = [ { " txid " : ext_utxo [ " txid " ] , " vout " : ext_utxo [ " vout " ] , " weight " : input_weight } ] ,
outputs = { self . nodes [ 0 ] . getnewaddress ( ) : 15 } ,
2022-11-10 14:04:07 -03:00
add_inputs = True ,
2021-10-05 17:45:10 -03:00
)
signed = wallet . walletprocesspsbt ( psbt [ " psbt " ] )
signed = self . nodes [ 0 ] . walletprocesspsbt ( signed [ " psbt " ] )
2023-09-05 13:10:21 -03:00
final = signed [ " hex " ]
assert self . nodes [ 0 ] . testmempoolaccept ( [ final ] ) [ 0 ] [ " allowed " ]
2021-10-05 17:45:10 -03:00
# Reducing the weight should have a lower fee
psbt2 = wallet . walletcreatefundedpsbt (
inputs = [ { " txid " : ext_utxo [ " txid " ] , " vout " : ext_utxo [ " vout " ] , " weight " : low_input_weight } ] ,
outputs = { self . nodes [ 0 ] . getnewaddress ( ) : 15 } ,
2022-11-10 14:04:07 -03:00
add_inputs = True ,
2021-10-05 17:45:10 -03:00
)
assert_greater_than ( psbt [ " fee " ] , psbt2 [ " fee " ] )
# Increasing the weight should have a higher fee
psbt2 = wallet . walletcreatefundedpsbt (
inputs = [ { " txid " : ext_utxo [ " txid " ] , " vout " : ext_utxo [ " vout " ] , " weight " : high_input_weight } ] ,
outputs = { self . nodes [ 0 ] . getnewaddress ( ) : 15 } ,
2022-11-10 14:04:07 -03:00
add_inputs = True ,
2021-10-05 17:45:10 -03:00
)
assert_greater_than ( psbt2 [ " fee " ] , psbt [ " fee " ] )
# The provided weight should override the calculated weight when solving data is provided
psbt3 = wallet . walletcreatefundedpsbt (
inputs = [ { " txid " : ext_utxo [ " txid " ] , " vout " : ext_utxo [ " vout " ] , " weight " : high_input_weight } ] ,
outputs = { self . nodes [ 0 ] . getnewaddress ( ) : 15 } ,
2022-11-10 14:04:07 -03:00
add_inputs = True , solving_data = { " descriptors " : [ desc ] } ,
2021-10-05 17:45:10 -03:00
)
assert_equal ( psbt2 [ " fee " ] , psbt3 [ " fee " ] )
# Import the external utxo descriptor so that we can sign for it from the test wallet
if self . options . descriptors :
res = wallet . importdescriptors ( [ { " desc " : desc , " timestamp " : " now " } ] )
else :
res = wallet . importmulti ( [ { " desc " : desc , " timestamp " : " now " } ] )
assert res [ 0 ] [ " success " ]
# The provided weight should override the calculated weight for a wallet input
psbt3 = wallet . walletcreatefundedpsbt (
inputs = [ { " txid " : ext_utxo [ " txid " ] , " vout " : ext_utxo [ " vout " ] , " weight " : high_input_weight } ] ,
outputs = { self . nodes [ 0 ] . getnewaddress ( ) : 15 } ,
2022-11-10 14:04:07 -03:00
add_inputs = True ,
2021-10-05 17:45:10 -03:00
)
assert_equal ( psbt2 [ " fee " ] , psbt3 [ " fee " ] )
2019-10-21 17:55:07 -03:00
2022-06-23 13:46:06 -04:00
self . log . info ( " Test signing inputs that the wallet has keys for but is not watching the scripts " )
self . nodes [ 1 ] . createwallet ( wallet_name = " scriptwatchonly " , disable_private_keys = True )
watchonly = self . nodes [ 1 ] . get_wallet_rpc ( " scriptwatchonly " )
2023-05-23 17:38:31 -04:00
privkey , pubkey = generate_keypair ( wif = True )
2022-06-23 13:46:06 -04:00
2023-05-23 17:38:31 -04:00
desc = descsum_create ( " wsh(pkh( {} )) " . format ( pubkey . hex ( ) ) )
2022-06-23 13:46:06 -04:00
if self . options . descriptors :
res = watchonly . importdescriptors ( [ { " desc " : desc , " timestamp " : " now " } ] )
else :
res = watchonly . importmulti ( [ { " desc " : desc , " timestamp " : " now " } ] )
assert res [ 0 ] [ " success " ]
addr = self . nodes [ 0 ] . deriveaddresses ( desc ) [ 0 ]
self . nodes [ 0 ] . sendtoaddress ( addr , 10 )
self . generate ( self . nodes [ 0 ] , 1 )
self . nodes [ 0 ] . importprivkey ( privkey )
psbt = watchonly . sendall ( [ wallet . getnewaddress ( ) ] ) [ " psbt " ]
2023-09-05 13:10:21 -03:00
signed_tx = self . nodes [ 0 ] . walletprocesspsbt ( psbt )
self . nodes [ 0 ] . sendrawtransaction ( signed_tx [ " hex " ] )
2022-06-23 13:46:06 -04:00
# Same test but for taproot
if self . options . descriptors :
2023-05-23 17:38:31 -04:00
privkey , pubkey = generate_keypair ( wif = True )
2022-06-23 13:46:06 -04:00
2023-05-23 17:38:31 -04:00
desc = descsum_create ( " tr( {} ,pk( {} )) " . format ( H_POINT , pubkey . hex ( ) ) )
2022-06-23 13:46:06 -04:00
res = watchonly . importdescriptors ( [ { " desc " : desc , " timestamp " : " now " } ] )
assert res [ 0 ] [ " success " ]
addr = self . nodes [ 0 ] . deriveaddresses ( desc ) [ 0 ]
self . nodes [ 0 ] . sendtoaddress ( addr , 10 )
self . generate ( self . nodes [ 0 ] , 1 )
self . nodes [ 0 ] . importdescriptors ( [ { " desc " : descsum_create ( " tr( {} ) " . format ( privkey ) ) , " timestamp " : " now " } ] )
2022-10-06 16:32:33 -03:00
psbt = watchonly . sendall ( [ wallet . getnewaddress ( ) , addr ] ) [ " psbt " ]
2023-09-05 13:10:21 -03:00
processed_psbt = self . nodes [ 0 ] . walletprocesspsbt ( psbt )
txid = self . nodes [ 0 ] . sendrawtransaction ( processed_psbt [ " hex " ] )
2022-10-06 16:32:33 -03:00
vout = find_vout_for_address ( self . nodes [ 0 ] , txid , addr )
# Make sure tap tree is in psbt
parsed_psbt = PSBT . from_base64 ( psbt )
assert_greater_than ( len ( parsed_psbt . o [ vout ] . map [ PSBT_OUT_TAP_TREE ] ) , 0 )
assert " taproot_tree " in self . nodes [ 0 ] . decodepsbt ( psbt ) [ " outputs " ] [ vout ]
parsed_psbt . make_blank ( )
comb_psbt = self . nodes [ 0 ] . combinepsbt ( [ psbt , parsed_psbt . to_base64 ( ) ] )
assert_equal ( comb_psbt , psbt )
2022-06-23 13:46:06 -04:00
2022-07-11 14:21:46 -04:00
self . log . info ( " Test that walletprocesspsbt both updates and signs a non-updated psbt containing Taproot inputs " )
addr = self . nodes [ 0 ] . getnewaddress ( " " , " bech32m " )
2023-08-13 10:02:10 -04:00
utxo = self . create_outpoints ( self . nodes [ 0 ] , outputs = [ { addr : 1 } ] ) [ 0 ]
psbt = self . nodes [ 0 ] . createpsbt ( [ utxo ] , [ { self . nodes [ 0 ] . getnewaddress ( ) : 0.9999 } ] )
2022-07-11 14:21:46 -04:00
signed = self . nodes [ 0 ] . walletprocesspsbt ( psbt )
2023-09-05 13:10:21 -03:00
rawtx = signed [ " hex " ]
2022-07-11 14:21:46 -04:00
self . nodes [ 0 ] . sendrawtransaction ( rawtx )
self . generate ( self . nodes [ 0 ] , 1 )
2022-10-06 16:32:33 -03:00
# Make sure tap tree is not in psbt
parsed_psbt = PSBT . from_base64 ( psbt )
assert PSBT_OUT_TAP_TREE not in parsed_psbt . o [ 0 ] . map
assert " taproot_tree " not in self . nodes [ 0 ] . decodepsbt ( psbt ) [ " outputs " ] [ 0 ]
parsed_psbt . make_blank ( )
comb_psbt = self . nodes [ 0 ] . combinepsbt ( [ psbt , parsed_psbt . to_base64 ( ) ] )
assert_equal ( comb_psbt , psbt )
2023-07-26 18:56:48 -04:00
self . log . info ( " Test walletprocesspsbt raises if an invalid sighashtype is passed " )
2024-04-15 03:57:28 -04:00
assert_raises_rpc_error ( - 8 , " ' all ' is not a valid sighash parameter. " , self . nodes [ 0 ] . walletprocesspsbt , psbt , sighashtype = " all " )
2023-07-26 18:56:48 -04:00
2022-07-16 10:04:45 -04:00
self . log . info ( " Test decoding PSBT with per-input preimage types " )
# note that the decodepsbt RPC doesn't check whether preimages and hashes match
2023-10-25 01:45:29 -03:00
hash_ripemd160 , preimage_ripemd160 = randbytes ( 20 ) , randbytes ( 50 )
hash_sha256 , preimage_sha256 = randbytes ( 32 ) , randbytes ( 50 )
hash_hash160 , preimage_hash160 = randbytes ( 20 ) , randbytes ( 50 )
hash_hash256 , preimage_hash256 = randbytes ( 32 ) , randbytes ( 50 )
2022-07-16 10:04:45 -04:00
tx = CTransaction ( )
tx . vin = [ CTxIn ( outpoint = COutPoint ( hash = int ( ' aa ' * 32 , 16 ) , n = 0 ) , scriptSig = b " " ) ,
CTxIn ( outpoint = COutPoint ( hash = int ( ' bb ' * 32 , 16 ) , n = 0 ) , scriptSig = b " " ) ,
CTxIn ( outpoint = COutPoint ( hash = int ( ' cc ' * 32 , 16 ) , n = 0 ) , scriptSig = b " " ) ,
CTxIn ( outpoint = COutPoint ( hash = int ( ' dd ' * 32 , 16 ) , n = 0 ) , scriptSig = b " " ) ]
tx . vout = [ CTxOut ( nValue = 0 , scriptPubKey = b " " ) ]
psbt = PSBT ( )
psbt . g = PSBTMap ( { PSBT_GLOBAL_UNSIGNED_TX : tx . serialize ( ) } )
psbt . i = [ PSBTMap ( { bytes ( [ PSBT_IN_RIPEMD160 ] ) + hash_ripemd160 : preimage_ripemd160 } ) ,
PSBTMap ( { bytes ( [ PSBT_IN_SHA256 ] ) + hash_sha256 : preimage_sha256 } ) ,
PSBTMap ( { bytes ( [ PSBT_IN_HASH160 ] ) + hash_hash160 : preimage_hash160 } ) ,
PSBTMap ( { bytes ( [ PSBT_IN_HASH256 ] ) + hash_hash256 : preimage_hash256 } ) ]
psbt . o = [ PSBTMap ( ) ]
res_inputs = self . nodes [ 0 ] . decodepsbt ( psbt . to_base64 ( ) ) [ " inputs " ]
assert_equal ( len ( res_inputs ) , 4 )
preimage_keys = [ " ripemd160_preimages " , " sha256_preimages " , " hash160_preimages " , " hash256_preimages " ]
expected_hashes = [ hash_ripemd160 , hash_sha256 , hash_hash160 , hash_hash256 ]
expected_preimages = [ preimage_ripemd160 , preimage_sha256 , preimage_hash160 , preimage_hash256 ]
for res_input , preimage_key , hash , preimage in zip ( res_inputs , preimage_keys , expected_hashes , expected_preimages ) :
assert preimage_key in res_input
assert_equal ( len ( res_input [ preimage_key ] ) , 1 )
assert hash . hex ( ) in res_input [ preimage_key ]
assert_equal ( res_input [ preimage_key ] [ hash . hex ( ) ] , preimage . hex ( ) )
2022-07-19 09:01:06 -04:00
self . log . info ( " Test that combining PSBTs with different transactions fails " )
tx = CTransaction ( )
tx . vin = [ CTxIn ( outpoint = COutPoint ( hash = int ( ' aa ' * 32 , 16 ) , n = 0 ) , scriptSig = b " " ) ]
tx . vout = [ CTxOut ( nValue = 0 , scriptPubKey = b " " ) ]
psbt1 = PSBT ( g = PSBTMap ( { PSBT_GLOBAL_UNSIGNED_TX : tx . serialize ( ) } ) , i = [ PSBTMap ( ) ] , o = [ PSBTMap ( ) ] ) . to_base64 ( )
tx . vout [ 0 ] . nValue + = 1 # slightly modify tx
psbt2 = PSBT ( g = PSBTMap ( { PSBT_GLOBAL_UNSIGNED_TX : tx . serialize ( ) } ) , i = [ PSBTMap ( ) ] , o = [ PSBTMap ( ) ] ) . to_base64 ( )
assert_raises_rpc_error ( - 8 , " PSBTs not compatible (different transactions) " , self . nodes [ 0 ] . combinepsbt , [ psbt1 , psbt2 ] )
assert_equal ( self . nodes [ 0 ] . combinepsbt ( [ psbt1 , psbt1 ] ) , psbt1 )
2022-10-17 12:11:27 -03:00
self . log . info ( " Test that PSBT inputs are being checked via script execution " )
acs_prevout = CTxOut ( nValue = 0 , scriptPubKey = CScript ( [ OP_TRUE ] ) )
tx = CTransaction ( )
tx . vin = [ CTxIn ( outpoint = COutPoint ( hash = int ( ' dd ' * 32 , 16 ) , n = 0 ) , scriptSig = b " " ) ]
tx . vout = [ CTxOut ( nValue = 0 , scriptPubKey = b " " ) ]
psbt = PSBT ( )
psbt . g = PSBTMap ( { PSBT_GLOBAL_UNSIGNED_TX : tx . serialize ( ) } )
psbt . i = [ PSBTMap ( { bytes ( [ PSBT_IN_WITNESS_UTXO ] ) : acs_prevout . serialize ( ) } ) ]
psbt . o = [ PSBTMap ( ) ]
assert_equal ( self . nodes [ 0 ] . finalizepsbt ( psbt . to_base64 ( ) ) ,
{ ' hex ' : ' 0200000001dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd0000000000000000000100000000000000000000000000 ' , ' complete ' : True } )
2023-03-16 15:58:41 -03:00
self . log . info ( " Test we don ' t crash when making a 0-value funded transaction at 0 fee without forcing an input selection " )
assert_raises_rpc_error ( - 4 , " Transaction requires one destination of non-0 value, a non-0 feerate, or a pre-selected input " , self . nodes [ 0 ] . walletcreatefundedpsbt , [ ] , [ { " data " : " deadbeef " } ] , 0 , { " fee_rate " : " 0 " } )
2023-05-05 11:22:05 -04:00
self . log . info ( " Test descriptorprocesspsbt updates and signs a psbt with descriptors " )
self . generate ( self . nodes [ 2 ] , 1 )
# Disable the wallet for node 2 since `descriptorprocesspsbt` does not use the wallet
self . restart_node ( 2 , extra_args = [ " -disablewallet " ] )
self . connect_nodes ( 0 , 2 )
self . connect_nodes ( 1 , 2 )
key_info = get_generate_key ( )
key = key_info . privkey
address = key_info . p2wpkh_addr
descriptor = descsum_create ( f " wpkh( { key } ) " )
2023-08-13 10:02:10 -04:00
utxo = self . create_outpoints ( self . nodes [ 0 ] , outputs = [ { address : 1 } ] ) [ 0 ]
2023-05-05 11:22:05 -04:00
self . sync_all ( )
2023-08-13 10:02:10 -04:00
psbt = self . nodes [ 2 ] . createpsbt ( [ utxo ] , { self . nodes [ 0 ] . getnewaddress ( ) : 0.99999 } )
2023-05-05 11:22:05 -04:00
decoded = self . nodes [ 2 ] . decodepsbt ( psbt )
test_psbt_input_keys ( decoded [ ' inputs ' ] [ 0 ] , [ ] )
# Test that even if the wrong descriptor is given, `witness_utxo` and `non_witness_utxo`
# are still added to the psbt
alt_descriptor = descsum_create ( f " wpkh( { get_generate_key ( ) . privkey } ) " )
alt_psbt = self . nodes [ 2 ] . descriptorprocesspsbt ( psbt = psbt , descriptors = [ alt_descriptor ] , sighashtype = " ALL " ) [ " psbt " ]
decoded = self . nodes [ 2 ] . decodepsbt ( alt_psbt )
test_psbt_input_keys ( decoded [ ' inputs ' ] [ 0 ] , [ ' witness_utxo ' , ' non_witness_utxo ' ] )
# Test that the psbt is not finalized and does not have bip32_derivs unless specified
2023-09-15 12:48:36 -03:00
processed_psbt = self . nodes [ 2 ] . descriptorprocesspsbt ( psbt = psbt , descriptors = [ descriptor ] , sighashtype = " ALL " , bip32derivs = True , finalize = False )
decoded = self . nodes [ 2 ] . decodepsbt ( processed_psbt [ ' psbt ' ] )
2023-05-05 11:22:05 -04:00
test_psbt_input_keys ( decoded [ ' inputs ' ] [ 0 ] , [ ' witness_utxo ' , ' non_witness_utxo ' , ' partial_signatures ' , ' bip32_derivs ' ] )
2023-09-15 12:48:36 -03:00
# If psbt not finalized, test that result does not have hex
assert " hex " not in processed_psbt
processed_psbt = self . nodes [ 2 ] . descriptorprocesspsbt ( psbt = psbt , descriptors = [ descriptor ] , sighashtype = " ALL " , bip32derivs = False , finalize = True )
decoded = self . nodes [ 2 ] . decodepsbt ( processed_psbt [ ' psbt ' ] )
2023-05-05 11:22:05 -04:00
test_psbt_input_keys ( decoded [ ' inputs ' ] [ 0 ] , [ ' witness_utxo ' , ' non_witness_utxo ' , ' final_scriptwitness ' ] )
2023-09-15 12:48:36 -03:00
# Test psbt is complete
assert_equal ( processed_psbt [ ' complete ' ] , True )
2023-05-05 11:22:05 -04:00
# Broadcast transaction
2023-09-15 12:48:36 -03:00
self . nodes [ 2 ] . sendrawtransaction ( processed_psbt [ ' hex ' ] )
2022-07-16 10:04:45 -04:00
2023-07-26 18:56:48 -04:00
self . log . info ( " Test descriptorprocesspsbt raises if an invalid sighashtype is passed " )
2024-04-15 03:57:28 -04:00
assert_raises_rpc_error ( - 8 , " ' all ' is not a valid sighash parameter. " , self . nodes [ 2 ] . descriptorprocesspsbt , psbt , [ descriptor ] , sighashtype = " all " )
2023-07-26 18:56:48 -04:00
2018-06-27 20:05:54 -04:00
if __name__ == ' __main__ ' :
PSBTTest ( ) . main ( )