Skip to content

Commit 036fb76

Browse files
committed
pytest: enhance tests to test anchor and htlc tx feerates match targets.
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
1 parent 3064731 commit 036fb76

File tree

3 files changed

+152
-4
lines changed

3 files changed

+152
-4
lines changed

tests/test_closing.py

+31-4
Original file line numberDiff line numberDiff line change
@@ -3769,9 +3769,9 @@ def test_closing_anchorspend_htlc_tx_rbf(node_factory, bitcoind):
37693769
fundsats = int(Millisatoshi(only_one(l1.rpc.listfunds()['outputs'])['amount_msat']).to_satoshi())
37703770
psbt = l1.rpc.fundpsbt("all", "1000perkw", 1000)['psbt']
37713771
# Pay 5k sats in fees, send most to l2
3772-
psbt = l1.rpc.addpsbtoutput(fundsats - 20000 - 5000, psbt, destination=l2.rpc.newaddr()['bech32'])['psbt']
3773-
# 10x2000 sat outputs for l1 to use.
3774-
for i in range(10):
3772+
psbt = l1.rpc.addpsbtoutput(fundsats - 24000 - 5000, psbt, destination=l2.rpc.newaddr()['bech32'])['psbt']
3773+
# 12x2000 sat outputs for l1 to use.
3774+
for i in range(12):
37753775
psbt = l1.rpc.addpsbtoutput(2000, psbt)['psbt']
37763776
l1.rpc.sendpsbt(l1.rpc.signpsbt(psbt)['signed_psbt'])
37773777
bitcoind.generate_block(1, wait_for_mempool=1)
@@ -3803,6 +3803,13 @@ def test_closing_anchorspend_htlc_tx_rbf(node_factory, bitcoind):
38033803

38043804
wait_for(lambda: len(bitcoind.rpc.getrawmempool()) == 2)
38053805

3806+
# Expect package feerate of 2000
3807+
details = bitcoind.rpc.getrawmempool(True).values()
3808+
total_weight = sum([d['weight'] for d in details])
3809+
total_fees = sum([float(d['fees']['base'])* 100_000_000 for d in details])
3810+
total_feerate_perkw = total_fees / total_weight * 1000
3811+
assert 2000 - 1 < total_feerate_perkw < 2000 + 1
3812+
38063813
# But we don't mine it! And fees go up again!
38073814
l1.set_feerates((3000, 3000, 3000, 3000))
38083815
bitcoind.generate_block(1, needfeerate=5000)
@@ -3811,6 +3818,13 @@ def test_closing_anchorspend_htlc_tx_rbf(node_factory, bitcoind):
38113818
# We actually resubmit the commit tx, then the RBF:
38123819
l1.daemon.wait_for_logs(['sendrawtx exit 0'] * 2)
38133820

3821+
# Expect package feerate of 3000
3822+
details = bitcoind.rpc.getrawmempool(True).values()
3823+
total_weight = sum([d['weight'] for d in details])
3824+
total_fees = sum([float(d['fees']['base'])* 100_000_000 for d in details])
3825+
total_feerate_perkw = total_fees / total_weight * 1000
3826+
assert 3000 - 1 < total_feerate_perkw < 3000 + 1
3827+
38143828
# And now we'll get it in (there's some rounding, so feerate a bit lower!)
38153829
bitcoind.generate_block(1, needfeerate=2990)
38163830

@@ -3832,8 +3846,15 @@ def test_closing_anchorspend_htlc_tx_rbf(node_factory, bitcoind):
38323846
# It will enter the mempool
38333847
wait_for(lambda: txid in bitcoind.rpc.getrawmempool())
38343848

3849+
tx = bitcoind.rpc.getrawmempool(True)[txid]
3850+
feerate_perkw = float(tx['fees']['base']) * 100_000_000 / tx['weight'] * 1000
3851+
# It actually has no change output, so it exceeds the fee quite a bit.
3852+
assert len(bitcoind.rpc.decoderawtransaction(bitcoind.rpc.getrawtransaction(txid))['vout']) == 1
3853+
assert 5000 - 1 < feerate_perkw < 5060 + 1
3854+
38353855
# And this will mine it!
3836-
bitcoind.generate_block(1, needfeerate=4990)
3856+
bitcoind.generate_block(1, needfeerate=5000)
3857+
assert bitcoind.rpc.getrawmempool() == []
38373858

38383859

38393860
@pytest.mark.parametrize("anchors", [False, True])
@@ -4011,6 +4032,12 @@ def test_peer_anchor_push(node_factory, bitcoind, executor, chainparams):
40114032
# As blocks pass, we will use anchor to boost l3's tx.
40124033
for block, feerate in zip(range(120, 124), (12000, 13000, 14000, 15000)):
40134034
l2.daemon.wait_for_log(fr"Worth fee [0-9]*sat for remote commit tx to get 100000000msat at block 125 \(\+{125 - block}\) at feerate {feerate}perkw")
4035+
# Check feerate for entire package (commitment tx + anchor) is ~ correct
4036+
details = bitcoind.rpc.getrawmempool(True).values()
4037+
total_weight = sum([d['weight'] for d in details])
4038+
total_fees = sum([float(d['fees']['base']) * 100_000_000 for d in details])
4039+
total_feerate_perkw = total_fees / total_weight * 1000
4040+
assert feerate - 1 < total_feerate_perkw < feerate + 1
40144041
bitcoind.generate_block(1, needfeerate=16000)
40154042
sync_blockheight(bitcoind, [l2])
40164043
assert len(bitcoind.rpc.getrawmempool()) == 2

tests/test_wallet.py

+109
Original file line numberDiff line numberDiff line change
@@ -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+
290304
def 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

449558
def test_reserveinputs(node_factory, bitcoind, chainparams):
450559
amount = 1000000

wallet/reservation.c

+12
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,13 @@ static struct command_result *finish_psbt(struct command *cmd,
397397
change_outnum = psbt->num_outputs;
398398
psbt_append_output(psbt, b32script, change);
399399
/* Add additional weight of output */
400+
log_debug(cmd->ld->log, "Adding change %s: weight %zu + change %zu = %zu",
401+
fmt_amount_sat(tmpctx, change),
402+
weight,
403+
bitcoin_tx_output_weight(
404+
chainparams->is_elements ? BITCOIN_SCRIPTPUBKEY_P2WPKH_LEN : BITCOIN_SCRIPTPUBKEY_P2TR_LEN),
405+
weight + bitcoin_tx_output_weight(
406+
chainparams->is_elements ? BITCOIN_SCRIPTPUBKEY_P2WPKH_LEN : BITCOIN_SCRIPTPUBKEY_P2TR_LEN));
400407
weight += bitcoin_tx_output_weight(
401408
chainparams->is_elements ? BITCOIN_SCRIPTPUBKEY_P2WPKH_LEN : BITCOIN_SCRIPTPUBKEY_P2TR_LEN);
402409
} else {
@@ -525,6 +532,8 @@ static struct command_result *json_fundpsbt(struct command *cmd,
525532
NULL))
526533
return command_param_failed();
527534

535+
log_debug(cmd->ld->log, "fundpsbt feerate = %uperkw", *feerate_per_kw);
536+
528537
/* If we have anchor channels, we definitely need to keep
529538
* emergency funds. */
530539
if (have_anchor_channel(cmd->ld))
@@ -574,6 +583,9 @@ static struct command_result *json_fundpsbt(struct command *cmd,
574583
"impossible UTXO value");
575584

576585
/* But also adds weight */
586+
log_debug(cmd->ld->log, "Adding utxo %s: weight %u + utxo_weight %u = %u",
587+
fmt_amount_sat(tmpctx, utxo->amount),
588+
*weight, utxo_weight, *weight + utxo_weight);
577589
*weight += utxo_weight;
578590
continue;
579591
}

0 commit comments

Comments
 (0)