@@ -78,6 +78,7 @@ def run_test(self):
7878 test_small_output_fails (rbf_node , dest_address )
7979 test_dust_to_fee (rbf_node , dest_address )
8080 test_settxfee (rbf_node , dest_address )
81+ test_watchonly_psbt (self , peer_node , rbf_node , dest_address )
8182 test_rebumping (rbf_node , dest_address )
8283 test_rebumping_not_replaceable (rbf_node , dest_address )
8384 test_unconfirmed_not_spendable (rbf_node , rbf_node_address )
@@ -103,6 +104,7 @@ def test_simple_bumpfee_succeeds(self, mode, rbf_node, peer_node, dest_address):
103104 assert_equal (bumped_tx ["errors" ], [])
104105 assert bumped_tx ["fee" ] > - rbftx ["fee" ]
105106 assert_equal (bumped_tx ["origfee" ], - rbftx ["fee" ])
107+ assert "psbt" not in bumped_tx
106108 # check that bumped_tx propagates, original tx was evicted and has a wallet conflict
107109 self .sync_mempools ((rbf_node , peer_node ))
108110 assert bumped_tx ["txid" ] in rbf_node .getrawmempool ()
@@ -280,6 +282,86 @@ def test_maxtxfee_fails(test, rbf_node, dest_address):
280282 test .restart_node (1 , test .extra_args [1 ])
281283 rbf_node .walletpassphrase (WALLET_PASSPHRASE , WALLET_PASSPHRASE_TIMEOUT )
282284
285+ def test_watchonly_psbt (test , peer_node , rbf_node , dest_address ):
286+ priv_rec_desc = "wpkh([00000001/84'/1'/0']tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0/*)#rweraev0"
287+ pub_rec_desc = rbf_node .getdescriptorinfo (priv_rec_desc )["descriptor" ]
288+ priv_change_desc = "wpkh([00000001/84'/1'/0']tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/*)#j6uzqvuh"
289+ pub_change_desc = rbf_node .getdescriptorinfo (priv_change_desc )["descriptor" ]
290+ # Create a wallet with private keys that can sign PSBTs
291+ rbf_node .createwallet (wallet_name = "signer" , disable_private_keys = False , blank = True )
292+ signer = rbf_node .get_wallet_rpc ("signer" )
293+ assert signer .getwalletinfo ()['private_keys_enabled' ]
294+ result = signer .importmulti ([{
295+ "desc" : priv_rec_desc ,
296+ "timestamp" : 0 ,
297+ "range" : [0 ,1 ],
298+ "internal" : False ,
299+ "keypool" : False # Keys can only be imported to the keypool when private keys are disabled
300+ },
301+ {
302+ "desc" : priv_change_desc ,
303+ "timestamp" : 0 ,
304+ "range" : [0 , 0 ],
305+ "internal" : True ,
306+ "keypool" : False
307+ }])
308+ assert_equal (result , [{'success' : True }, {'success' : True }])
309+
310+ # Create another wallet with just the public keys, which creates PSBTs
311+ rbf_node .createwallet (wallet_name = "watcher" , disable_private_keys = True , blank = True )
312+ watcher = rbf_node .get_wallet_rpc ("watcher" )
313+ assert not watcher .getwalletinfo ()['private_keys_enabled' ]
314+
315+ result = watcher .importmulti ([{
316+ "desc" : pub_rec_desc ,
317+ "timestamp" : 0 ,
318+ "range" : [0 ,10 ],
319+ "internal" : False ,
320+ "keypool" : True ,
321+ "watchonly" : True
322+ },
323+ {
324+ "desc" : pub_change_desc ,
325+ "timestamp" : 0 ,
326+ "range" : [0 , 10 ],
327+ "internal" : True ,
328+ "keypool" : True ,
329+ "watchonly" : True
330+ }])
331+ assert_equal (result , [{'success' : True }, {'success' : True }])
332+
333+ funding_address1 = watcher .getnewaddress (address_type = 'bech32' )
334+ funding_address2 = watcher .getnewaddress (address_type = 'bech32' )
335+ peer_node .sendmany ("" , {funding_address1 : 0.001 , funding_address2 : 0.001 })
336+ peer_node .generate (1 )
337+ test .sync_all ()
338+
339+ # Create single-input PSBT for transaction to be bumped
340+ psbt = watcher .walletcreatefundedpsbt ([], {dest_address :0.0005 }, 0 , {"feeRate" : 0.00001 }, True )['psbt' ]
341+ psbt_signed = signer .walletprocesspsbt (psbt = psbt , sign = True , sighashtype = "ALL" , bip32derivs = True )
342+ psbt_final = watcher .finalizepsbt (psbt_signed ["psbt" ])
343+ original_txid = watcher .sendrawtransaction (psbt_final ["hex" ])
344+ assert_equal (len (watcher .decodepsbt (psbt )["tx" ]["vin" ]), 1 )
345+
346+ # Bump fee, obnoxiously high to add additional watchonly input
347+ bumped_psbt = watcher .bumpfee (original_txid , {"fee_rate" :0.005 })
348+ assert_greater_than (len (watcher .decodepsbt (bumped_psbt ['psbt' ])["tx" ]["vin" ]), 1 )
349+ assert "txid" not in bumped_psbt
350+ assert_equal (bumped_psbt ["origfee" ], - watcher .gettransaction (original_txid )["fee" ])
351+ assert not watcher .finalizepsbt (bumped_psbt ["psbt" ])["complete" ]
352+
353+ # Sign bumped transaction
354+ bumped_psbt_signed = signer .walletprocesspsbt (psbt = bumped_psbt ["psbt" ], sign = True , sighashtype = "ALL" , bip32derivs = True )
355+ bumped_psbt_final = watcher .finalizepsbt (bumped_psbt_signed ["psbt" ])
356+ assert bumped_psbt_final ["complete" ]
357+
358+ # Broadcast bumped transaction
359+ bumped_txid = watcher .sendrawtransaction (bumped_psbt_final ["hex" ])
360+ assert bumped_txid in rbf_node .getrawmempool ()
361+ assert original_txid not in rbf_node .getrawmempool ()
362+
363+ rbf_node .unloadwallet ("watcher" )
364+ rbf_node .unloadwallet ("signer" )
283365
284366def test_rebumping (rbf_node , dest_address ):
285367 # check that re-bumping the original tx fails, but bumping the bumper succeeds
0 commit comments