@@ -78,6 +78,7 @@ def run_test(self):
78
78
test_small_output_fails (rbf_node , dest_address )
79
79
test_dust_to_fee (rbf_node , dest_address )
80
80
test_settxfee (rbf_node , dest_address )
81
+ test_watchonly_psbt (self , peer_node , rbf_node , dest_address )
81
82
test_rebumping (rbf_node , dest_address )
82
83
test_rebumping_not_replaceable (rbf_node , dest_address )
83
84
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):
103
104
assert_equal (bumped_tx ["errors" ], [])
104
105
assert bumped_tx ["fee" ] > - rbftx ["fee" ]
105
106
assert_equal (bumped_tx ["origfee" ], - rbftx ["fee" ])
107
+ assert "psbt" not in bumped_tx
106
108
# check that bumped_tx propagates, original tx was evicted and has a wallet conflict
107
109
self .sync_mempools ((rbf_node , peer_node ))
108
110
assert bumped_tx ["txid" ] in rbf_node .getrawmempool ()
@@ -280,6 +282,86 @@ def test_maxtxfee_fails(test, rbf_node, dest_address):
280
282
test .restart_node (1 , test .extra_args [1 ])
281
283
rbf_node .walletpassphrase (WALLET_PASSPHRASE , WALLET_PASSPHRASE_TIMEOUT )
282
284
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" )
283
365
284
366
def test_rebumping (rbf_node , dest_address ):
285
367
# check that re-bumping the original tx fails, but bumping the bumper succeeds
0 commit comments