@@ -287,6 +287,19 @@ 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+ return fee / weight * 1000
301+
302+
290303def test_txprepare (node_factory , bitcoind , chainparams ):
291304 amount = 1000000
292305 l1 = node_factory .get_node (random_hsm = True )
@@ -299,13 +312,18 @@ def test_txprepare(node_factory, bitcoind, chainparams):
299312
300313 bitcoind .generate_block (1 )
301314 wait_for (lambda : len (l1 .rpc .listfunds ()['outputs' ]) == 10 )
315+ for est in l1 .rpc .feerates ('perkw' )['perkw' ]['estimates' ]:
316+ if est ['blockcount' ] == 12 :
317+ normal_feerate_perkw = est ['feerate' ]
302318
303319 prep = l1 .rpc .txprepare (outputs = [{addr : Millisatoshi (amount * 3 * 1000 )}])
304320 decode = bitcoind .rpc .decoderawtransaction (prep ['unsigned_tx' ])
305321 assert decode ['txid' ] == prep ['txid' ]
306322 # 4 inputs, 2 outputs (3 if we have a fee output).
307323 assert len (decode ['vin' ]) == 4
308324 assert len (decode ['vout' ]) == 2 if not chainparams ['feeoutput' ] else 3
325+ # Feerate should be ~ as we asked for
326+ assert normal_feerate_perkw - 2 < feerate_from_psbt (bitcoind , l1 , prep ['psbt' ]) < normal_feerate_perkw + 2
309327
310328 # One output will be correct.
311329 outnum = [i for i , o in enumerate (decode ['vout' ]) if o ['value' ] == Decimal (amount * 3 ) / 10 ** 8 ][0 ]
@@ -333,6 +351,8 @@ def test_txprepare(node_factory, bitcoind, chainparams):
333351 assert decode ['vout' ][0 ]['value' ] > Decimal (amount * 6 ) / 10 ** 8 - Decimal (0.0002 )
334352 assert decode ['vout' ][0 ]['scriptPubKey' ]['type' ] == 'witness_v0_keyhash'
335353 assert scriptpubkey_addr (decode ['vout' ][0 ]['scriptPubKey' ]) == addr
354+ # Feerate should be ~ as we asked for
355+ assert normal_feerate_perkw - 2 < feerate_from_psbt (bitcoind , l1 , prep2 ['psbt' ]) < normal_feerate_perkw + 2
336356
337357 # If I cancel the first one, I can get those first 4 outputs.
338358 discard = l1 .rpc .txdiscard (prep ['txid' ])
@@ -351,6 +371,8 @@ def test_txprepare(node_factory, bitcoind, chainparams):
351371 assert decode ['vout' ][0 ]['value' ] > Decimal (amount * 4 ) / 10 ** 8 - Decimal (0.0002 )
352372 assert decode ['vout' ][0 ]['scriptPubKey' ]['type' ] == 'witness_v0_keyhash'
353373 assert scriptpubkey_addr (decode ['vout' ][0 ]['scriptPubKey' ]) == addr
374+ # Feerate should be ~ as we asked for
375+ assert normal_feerate_perkw - 1 < feerate_from_psbt (bitcoind , l1 , prep3 ['psbt' ]) < normal_feerate_perkw + 1
354376
355377 # Cannot discard twice.
356378 with pytest .raises (RpcError , match = r'not an unreleased txid' ):
@@ -371,20 +393,28 @@ def test_txprepare(node_factory, bitcoind, chainparams):
371393 assert decode ['vout' ][0 ]['value' ] > Decimal (amount * 10 ) / 10 ** 8 - Decimal (0.0003 )
372394 assert decode ['vout' ][0 ]['scriptPubKey' ]['type' ] == 'witness_v0_keyhash'
373395 assert scriptpubkey_addr (decode ['vout' ][0 ]['scriptPubKey' ]) == addr
396+ # Feerate should be ~ as we asked for
397+ assert normal_feerate_perkw - 2 < feerate_from_psbt (bitcoind , l1 , prep4 ['psbt' ]) < normal_feerate_perkw + 2
374398 l1 .rpc .txdiscard (prep4 ['txid' ])
375399
376400 # Try passing in a utxo set
377401 utxos = [utxo ["txid" ] + ":" + str (utxo ["output" ])
378402 for utxo in l1 .rpc .listfunds ()["outputs" ]][:4 ]
379403 prep5 = l1 .rpc .txprepare ([{addr :
380404 Millisatoshi (amount * 3.5 * 1000 )}], utxos = utxos )
405+ # Feerate should be ~ as we asked for
406+ assert normal_feerate_perkw - 2 < feerate_from_psbt (bitcoind , l1 , prep3 ['psbt' ]) < normal_feerate_perkw + 2
381407
382408 # Try passing unconfirmed utxos
383409 unconfirmed_utxo = l1 .rpc .withdraw (l1 .rpc .newaddr ()["bech32" ], 10 ** 5 )
384410 uutxos = [unconfirmed_utxo ["txid" ] + ":0" ]
385411 with pytest .raises (RpcError , match = r"Could not afford" ):
386412 l1 .rpc .txprepare ([{addr : Millisatoshi (amount * 3.5 * 1000 )}],
387413 utxos = uutxos )
414+ # Feerate should be ~ as we asked for
415+ unconfirmed_tx = bitcoind .rpc .getrawmempool (True )[unconfirmed_utxo ["txid" ]]
416+ feerate_perkw = int (unconfirmed_tx ['fees' ]['base' ] * 100_000_000 ) * 1000 / unconfirmed_tx ['weight' ]
417+ assert normal_feerate_perkw - 1 < feerate_perkw < normal_feerate_perkw + 1
388418
389419 decode = bitcoind .rpc .decoderawtransaction (prep5 ['unsigned_tx' ])
390420 assert decode ['txid' ] == prep5 ['txid' ]
@@ -413,12 +443,16 @@ def test_txprepare(node_factory, bitcoind, chainparams):
413443 # You can have one which is all, but not two.
414444 prep5 = l1 .rpc .txprepare ([{addr : Millisatoshi (amount * 3 * 1000 )},
415445 {addr : 'all' }])
446+ # Feerate should be ~ as we asked for
447+ assert normal_feerate_perkw - 2 < feerate_from_psbt (bitcoind , l1 , prep5 ['psbt' ]) < normal_feerate_perkw + 2
416448 l1 .rpc .txdiscard (prep5 ['txid' ])
417449 with pytest .raises (RpcError , match = r"'all'" ):
418450 prep5 = l1 .rpc .txprepare ([{addr : 'all' }, {addr : 'all' }])
419451
420452 prep5 = l1 .rpc .txprepare ([{addr : Millisatoshi (amount * 3 * 500 + 100000 )},
421453 {addr : Millisatoshi (amount * 3 * 500 - 100000 )}])
454+ # Feerate should be ~ as we asked for
455+ assert normal_feerate_perkw - 2 < feerate_from_psbt (bitcoind , l1 , prep5 ['psbt' ]) < normal_feerate_perkw + 2
422456 decode = bitcoind .rpc .decoderawtransaction (prep5 ['unsigned_tx' ])
423457 assert decode ['txid' ] == prep5 ['txid' ]
424458 # 4 inputs, 3 outputs(include change).
@@ -445,6 +479,91 @@ def test_txprepare(node_factory, bitcoind, chainparams):
445479 else :
446480 assert decode ['vout' ][changenum ]['scriptPubKey' ]['type' ] == 'witness_v1_taproot'
447481
482+ l1 .rpc .txdiscard (prep5 ['txid' ])
483+
484+
485+ def test_txprepare_feerate (node_factory , bitcoind , chainparams ):
486+ # Make sure it works at different feerates!
487+ l1 , l2 = node_factory .get_nodes (2 )
488+
489+ # Add some funds to withdraw later
490+ for i in range (20 ):
491+ bitcoind .rpc .sendtoaddress (l1 .rpc .newaddr ()['bech32' ],
492+ 1000 / 10 ** 8 )
493+
494+ bitcoind .generate_block (1 )
495+ out_addrs = l2 .rpc .newaddr ('all' )
496+ wait_for (lambda : len (l1 .rpc .listfunds ()['outputs' ]) == 20 )
497+
498+ for addrtype in ('bech32' , 'p2tr' ):
499+ for feerate in range (255 , 1000 , 250 ):
500+ prep = l1 .rpc .txprepare ([{out_addrs [addrtype ]: Millisatoshi (9000 )}], f"{ feerate } perkw" )
501+ actual_feerate = feerate_from_psbt (bitcoind , l1 , prep ['psbt' ])
502+ assert feerate - 2 < actual_feerate
503+ # Feerate can be larger, if it chose not to give change output.
504+ if chainparams ['elements' ]:
505+ fee_output = 1
506+ else :
507+ fee_output = 0
508+ if len (bitcoind .rpc .decoderawtransaction (prep ['unsigned_tx' ])['vout' ]) == 1 + 1 + fee_output :
509+ assert actual_feerate < feerate + 2
510+ l1 .rpc .txdiscard (prep ['txid' ])
511+
512+
513+ @pytest .mark .parametrize ("addrtype" , ["bech32" , "p2tr" ])
514+ def test_fundpsbt_feerates (node_factory , bitcoind , chainparams , addrtype ):
515+ if chainparams ['elements' ] and addrtype == 'p2tr' :
516+ pytest .skip ('No p2tr for elements' )
517+
518+ l1 = node_factory .get_node ()
519+
520+ # Add some funds to withdraw later
521+ for i in range (20 ):
522+ bitcoind .rpc .sendtoaddress (l1 .rpc .newaddr (addrtype )[addrtype ],
523+ 5000 / 10 ** 8 )
524+
525+ # See utxo_spend_weight()
526+ if addrtype == 'bech32' :
527+ witness_weight = 1 + 71 + 1 + 33
528+ elif addrtype == 'p2tr' :
529+ witness_weight = 1 + 64
530+ else :
531+ assert False
532+
533+ input_weight = 1 + witness_weight + (32 + 4 + 4 + 1 ) * 4
534+ if chainparams ['elements' ]:
535+ input_weight += 6
536+
537+ bitcoind .generate_block (1 )
538+ wait_for (lambda : len (l1 .rpc .listfunds ()['outputs' ]) == 20 )
539+
540+ # version, input count, output count, locktime, segwit marker, flag
541+ base_weight = (4 + 1 + 1 + 4 ) * 4 + 1 + 1
542+ if chainparams ['elements' ]:
543+ # Elements has empty surjection and rangeproof, and fee output
544+ base_weight += 2 * 4 + (32 + 1 + 1 + 1 ) * 4
545+ # Bech32 change output
546+ change_weight = (8 + 1 + (1 + 1 + 20 ) + (32 + 1 + 1 + 1 )) * 4
547+ else :
548+ # P2TR output
549+ change_weight = (8 + 1 + (1 + 1 + 32 )) * 4
550+
551+ # Both minimal and higher feerate
552+ for feerate in (253 , 1000 ):
553+ # Try with both 1 and 2 inputs
554+ for amount , num_inputs in ((260 , 1 ), (5000 , 2 )):
555+ prep = l1 .rpc .fundpsbt (amount , f"{ feerate } perkw" , base_weight , excess_as_change = True )
556+ assert prep ['estimated_final_weight' ] == base_weight + change_weight + input_weight * num_inputs
557+ signed = l1 .rpc .signpsbt (prep ['psbt' ])['signed_psbt' ]
558+ sent = l1 .rpc .sendpsbt (signed )
559+ txinfo = bitcoind .rpc .getmempoolentry (sent ['txid' ])
560+ assert txinfo ['weight' ] == prep ['estimated_final_weight' ]
561+ # We never actually added that `amount` output to PSBT, so that appears as "fee"
562+ fee = int (txinfo ['fees' ]['base' ] * 100_000_000 ) - amount
563+ actual_feerate = fee / (txinfo ['weight' ] / 1000 )
564+ # Out by one errors (due to rounding) change feerate by 2.
565+ assert feerate - 2 < actual_feerate < feerate + 2
566+
448567
449568def test_reserveinputs (node_factory , bitcoind , chainparams ):
450569 amount = 1000000
0 commit comments