@@ -287,6 +287,20 @@ def test_txprepare_multi(node_factory, bitcoind):
287287 l1 .rpc .txdiscard (prep ['txid' ])
288288
289289
290+ def feerate_from_psbt (bitcoind , node , psbt ):
291+ # signpsbt insists they are reserved!
292+ node .rpc .reserveinputs (psbt , exclusive = False )
293+ final = node .rpc .dev_finalizepsbt (node .rpc .signpsbt (psbt )['signed_psbt' ])
294+ node .rpc .unreserveinputs (psbt )
295+ psbt = node .rpc .setpsbtversion (final ['psbt' ], 0 )['psbt' ]
296+ # analyzepsbt gives a vsize, but not a weight!
297+ # e.g. 'estimated_vsize': 356, 'estimated_feerate': Decimal('0.00030042'), 'fee': Decimal('0.00010695')
298+ fee = int (bitcoind .rpc .analyzepsbt (psbt )['fee' ] * 100_000_000 )
299+ weight = bitcoind .rpc .decoderawtransaction (final ['tx' ])['weight' ]
300+ print (f"XXX actual weight = { weight } " )
301+ return fee / weight * 1000
302+
303+
290304def test_txprepare (node_factory , bitcoind , chainparams ):
291305 amount = 1000000
292306 l1 = node_factory .get_node (random_hsm = True )
@@ -299,13 +313,18 @@ def test_txprepare(node_factory, bitcoind, chainparams):
299313
300314 bitcoind .generate_block (1 )
301315 wait_for (lambda : len (l1 .rpc .listfunds ()['outputs' ]) == 10 )
316+ for est in l1 .rpc .feerates ('perkw' )['perkw' ]['estimates' ]:
317+ if est ['blockcount' ] == 12 :
318+ normal_feerate_perkw = est ['feerate' ]
302319
303320 prep = l1 .rpc .txprepare (outputs = [{addr : Millisatoshi (amount * 3 * 1000 )}])
304321 decode = bitcoind .rpc .decoderawtransaction (prep ['unsigned_tx' ])
305322 assert decode ['txid' ] == prep ['txid' ]
306323 # 4 inputs, 2 outputs (3 if we have a fee output).
307324 assert len (decode ['vin' ]) == 4
308325 assert len (decode ['vout' ]) == 2 if not chainparams ['feeoutput' ] else 3
326+ # Feerate should be ~ as we asked for
327+ assert normal_feerate_perkw - 1 < feerate_from_psbt (bitcoind , l1 , prep ['psbt' ]) < normal_feerate_perkw + 1
309328
310329 # One output will be correct.
311330 outnum = [i for i , o in enumerate (decode ['vout' ]) if o ['value' ] == Decimal (amount * 3 ) / 10 ** 8 ][0 ]
@@ -333,6 +352,8 @@ def test_txprepare(node_factory, bitcoind, chainparams):
333352 assert decode ['vout' ][0 ]['value' ] > Decimal (amount * 6 ) / 10 ** 8 - Decimal (0.0002 )
334353 assert decode ['vout' ][0 ]['scriptPubKey' ]['type' ] == 'witness_v0_keyhash'
335354 assert scriptpubkey_addr (decode ['vout' ][0 ]['scriptPubKey' ]) == addr
355+ # Feerate should be ~ as we asked for
356+ assert normal_feerate_perkw - 1 < feerate_from_psbt (bitcoind , l1 , prep2 ['psbt' ]) < normal_feerate_perkw + 1
336357
337358 # If I cancel the first one, I can get those first 4 outputs.
338359 discard = l1 .rpc .txdiscard (prep ['txid' ])
@@ -351,6 +372,8 @@ def test_txprepare(node_factory, bitcoind, chainparams):
351372 assert decode ['vout' ][0 ]['value' ] > Decimal (amount * 4 ) / 10 ** 8 - Decimal (0.0002 )
352373 assert decode ['vout' ][0 ]['scriptPubKey' ]['type' ] == 'witness_v0_keyhash'
353374 assert scriptpubkey_addr (decode ['vout' ][0 ]['scriptPubKey' ]) == addr
375+ # Feerate should be ~ as we asked for
376+ assert normal_feerate_perkw - 1 < feerate_from_psbt (bitcoind , l1 , prep3 ['psbt' ]) < normal_feerate_perkw + 1
354377
355378 # Cannot discard twice.
356379 with pytest .raises (RpcError , match = r'not an unreleased txid' ):
@@ -371,20 +394,28 @@ def test_txprepare(node_factory, bitcoind, chainparams):
371394 assert decode ['vout' ][0 ]['value' ] > Decimal (amount * 10 ) / 10 ** 8 - Decimal (0.0003 )
372395 assert decode ['vout' ][0 ]['scriptPubKey' ]['type' ] == 'witness_v0_keyhash'
373396 assert scriptpubkey_addr (decode ['vout' ][0 ]['scriptPubKey' ]) == addr
397+ # Feerate should be ~ as we asked for
398+ assert normal_feerate_perkw - 1 < feerate_from_psbt (bitcoind , l1 , prep4 ['psbt' ]) < normal_feerate_perkw + 1
374399 l1 .rpc .txdiscard (prep4 ['txid' ])
375400
376401 # Try passing in a utxo set
377402 utxos = [utxo ["txid" ] + ":" + str (utxo ["output" ])
378403 for utxo in l1 .rpc .listfunds ()["outputs" ]][:4 ]
379404 prep5 = l1 .rpc .txprepare ([{addr :
380405 Millisatoshi (amount * 3.5 * 1000 )}], utxos = utxos )
406+ # Feerate should be ~ as we asked for
407+ assert normal_feerate_perkw - 1 < feerate_from_psbt (bitcoind , l1 , prep3 ['psbt' ]) < normal_feerate_perkw + 1
381408
382409 # Try passing unconfirmed utxos
383410 unconfirmed_utxo = l1 .rpc .withdraw (l1 .rpc .newaddr ()["bech32" ], 10 ** 5 )
384411 uutxos = [unconfirmed_utxo ["txid" ] + ":0" ]
385412 with pytest .raises (RpcError , match = r"Could not afford" ):
386413 l1 .rpc .txprepare ([{addr : Millisatoshi (amount * 3.5 * 1000 )}],
387414 utxos = uutxos )
415+ # Feerate should be ~ as we asked for
416+ unconfirmed_tx = bitcoind .rpc .getrawmempool (True )[unconfirmed_utxo ["txid" ]]
417+ feerate_perkw = int (unconfirmed_tx ['fees' ]['base' ] * 100_000_000 ) * 1000 / unconfirmed_tx ['weight' ]
418+ assert normal_feerate_perkw - 1 < feerate_perkw < normal_feerate_perkw + 1
388419
389420 decode = bitcoind .rpc .decoderawtransaction (prep5 ['unsigned_tx' ])
390421 assert decode ['txid' ] == prep5 ['txid' ]
@@ -413,12 +444,16 @@ def test_txprepare(node_factory, bitcoind, chainparams):
413444 # You can have one which is all, but not two.
414445 prep5 = l1 .rpc .txprepare ([{addr : Millisatoshi (amount * 3 * 1000 )},
415446 {addr : 'all' }])
447+ # Feerate should be ~ as we asked for
448+ assert normal_feerate_perkw - 1 < feerate_from_psbt (bitcoind , l1 , prep5 ['psbt' ]) < normal_feerate_perkw + 1
416449 l1 .rpc .txdiscard (prep5 ['txid' ])
417450 with pytest .raises (RpcError , match = r"'all'" ):
418451 prep5 = l1 .rpc .txprepare ([{addr : 'all' }, {addr : 'all' }])
419452
420453 prep5 = l1 .rpc .txprepare ([{addr : Millisatoshi (amount * 3 * 500 + 100000 )},
421454 {addr : Millisatoshi (amount * 3 * 500 - 100000 )}])
455+ # Feerate should be ~ as we asked for
456+ assert normal_feerate_perkw - 1 < feerate_from_psbt (bitcoind , l1 , prep5 ['psbt' ]) < normal_feerate_perkw + 1
422457 decode = bitcoind .rpc .decoderawtransaction (prep5 ['unsigned_tx' ])
423458 assert decode ['txid' ] == prep5 ['txid' ]
424459 # 4 inputs, 3 outputs(include change).
@@ -445,6 +480,80 @@ def test_txprepare(node_factory, bitcoind, chainparams):
445480 else :
446481 assert decode ['vout' ][changenum ]['scriptPubKey' ]['type' ] == 'witness_v1_taproot'
447482
483+ l1 .rpc .txdiscard (prep5 ['txid' ])
484+
485+
486+ def test_txprepare_feerate (node_factory , bitcoind ):
487+ # Make sure it works at different feerates!
488+ l1 , l2 = node_factory .get_nodes (2 )
489+
490+ # Add some funds to withdraw later
491+ for i in range (20 ):
492+ bitcoind .rpc .sendtoaddress (l1 .rpc .newaddr ()['bech32' ],
493+ 1000 / 10 ** 8 )
494+
495+ bitcoind .generate_block (1 )
496+ out_addrs = l2 .rpc .newaddr ('all' )
497+ wait_for (lambda : len (l1 .rpc .listfunds ()['outputs' ]) == 20 )
498+
499+ for addrtype in ('bech32' , 'p2tr' ):
500+ for feerate in range (255 , 10000 , 250 ):
501+ prep = l1 .rpc .txprepare ([{out_addrs [addrtype ]: Millisatoshi (9000 )}], f"{ feerate } perkw" )
502+ assert feerate - 1 < feerate_from_psbt (bitcoind , l1 , prep ['psbt' ]) < feerate + 1
503+ l1 .rpc .txdiscard (prep6 ['txid' ])
504+
505+
506+ @pytest .mark .parametrize ("addrtype" , ["bech32" , "p2tr" ])
507+ def test_fundpsbt_feerates (node_factory , bitcoind , chainparams , addrtype ):
508+ if chainparams ['elements' ] and addrtype == 'p2tr' :
509+ pytest .skip ('No p2tr for elements' )
510+
511+ l1 = node_factory .get_node ()
512+
513+ # Add some funds to withdraw later
514+ for i in range (20 ):
515+ bitcoind .rpc .sendtoaddress (l1 .rpc .newaddr ()[addrtype ],
516+ 1000 / 10 ** 8 )
517+
518+ # See utxo_spend_weight()
519+ if addrtype == 'bech32' :
520+ witness_weight = 1 + 71 + 1 + 33
521+ elif addrtype == 'p2tr' :
522+ witness_weight = 1 + 64
523+ else :
524+ assert False
525+
526+ input_weight = 1 + witness_weight + (32 + 4 + 4 + 1 ) * 4
527+ if chainparams ['elements' ]:
528+ input_weight += 6
529+
530+ bitcoind .generate_block (1 )
531+ wait_for (lambda : len (l1 .rpc .listfunds ()['outputs' ]) == 20 )
532+
533+ # version, input count, output count, locktime, segwit marker, flag
534+ base_weight = (4 + 1 + 1 + 4 ) * 4 + 1 + 1
535+ if chainparams ['elements' ]:
536+ # Elements has empty surjection and rangeproof, and fee output
537+ base_weight += 2 * 4 + (32 + 1 + 1 + 1 ) * 4
538+ # Bech32 change output
539+ change_weight = (8 + 1 + (1 + 1 + 20 ) + (32 + 1 + 1 + 1 )) * 4
540+ else :
541+ # P2TR output
542+ change_weight = (8 + 1 + (1 + 1 + 32 )) * 4
543+
544+ # Both minimal and higher feerate
545+ for feerate in (253 , 1000 ):
546+ # Try with both 1 and 2 inputs
547+ for amount , num_inputs in ((260 , 1 ), (1000 , 2 )):
548+ prep = l1 .rpc .fundpsbt (amount , f"{ feerate } perkw" , base_weight , excess_as_change = True )
549+ assert prep ['estimated_final_weight' ] == base_weight + change_weight + input_weight * num_inputs
550+ signed = l1 .rpc .signpsbt (prep ['psbt' ])['signed_psbt' ]
551+ sent = l1 .rpc .sendpsbt (signed )
552+ txinfo = bitcoind .rpc .getmempoolentry (sent ['txid' ])
553+ assert txinfo ['weight' ] == prep ['estimated_final_weight' ]
554+ actual_feerate = txinfo ['fees' ]['base' ] / txinfo ['weight' ]
555+ assert feerate - 1 < actual_feerate < feerate + 1
556+
448557
449558def test_reserveinputs (node_factory , bitcoind , chainparams ):
450559 amount = 1000000
0 commit comments