Skip to content

Commit cb2163e

Browse files
niftyneirustyrussell
authored andcommitted
bkpr: add bookkeeping assertions to splice in + out tests
Make sure that the fees and channel balances for splice ins and outs work as expected. Note this is for a single-sided splice. Changelog-None: Tests!
1 parent fcebb33 commit cb2163e

File tree

2 files changed

+154
-20
lines changed

2 files changed

+154
-20
lines changed

tests/test_splice.py

Lines changed: 148 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,197 @@
11
from fixtures import * # noqa: F401,F403
2+
from pathlib import Path
3+
from pyln.client import Millisatoshi
24
import pytest
35
import unittest
46
from utils import (
7+
bkpr_account_balance, check_coin_moves, first_channel_id,
58
TEST_NETWORK, only_one, wait_for
69
)
710

811

912
@pytest.mark.openchannel('v1')
1013
@pytest.mark.openchannel('v2')
1114
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
12-
def test_script_splice_out(node_factory, bitcoind):
13-
l1, l2 = node_factory.line_graph(2, fundamount=1000000, wait_for_announce=True, opts={'experimental-splicing': None})
15+
def test_script_splice_out(node_factory, bitcoind, chainparams):
16+
fundamt = 1000000
17+
18+
coin_mvt_plugin = Path(__file__).parent / "plugins" / "coin_movements.py"
19+
l1, l2 = node_factory.line_graph(2, fundamount=fundamt, wait_for_announce=True,
20+
opts={'experimental-splicing': None,
21+
'plugin': coin_mvt_plugin})
22+
23+
initial_wallet_balance = Millisatoshi(bkpr_account_balance(l1, 'wallet'))
24+
initial_channel_balance = Millisatoshi(bkpr_account_balance(l1, first_channel_id(l1, l2)))
25+
assert initial_channel_balance == Millisatoshi(fundamt * 1000)
26+
1427
# Splice out 100k from first channel, explicitly putting result less fees into onchain wallet
15-
l1.rpc.splice("*:? -> 100000; 100%-fee -> wallet", force_feerate=True, debug_log=True)
28+
spliceamt = 100000
29+
l1.rpc.splice(f"*:? -> {spliceamt}; 100%-fee -> wallet", force_feerate=True, debug_log=True)
1630
p1 = only_one(l1.rpc.listpeerchannels(peer_id=l2.info['id'])['channels'])
1731
p2 = only_one(l2.rpc.listpeerchannels(l1.info['id'])['channels'])
18-
assert p1['inflight'][0]['splice_amount'] == -100000
19-
assert p1['inflight'][0]['total_funding_msat'] == 900000000
20-
assert p1['inflight'][0]['our_funding_msat'] == 1000000000
32+
33+
assert p1['inflight'][0]['splice_amount'] == -1 * spliceamt
34+
assert p1['inflight'][0]['total_funding_msat'] == (fundamt - spliceamt) * 1000
35+
assert p1['inflight'][0]['our_funding_msat'] == fundamt * 1000
2136
assert p2['inflight'][0]['splice_amount'] == 0
22-
assert p2['inflight'][0]['total_funding_msat'] == 900000000
37+
assert p2['inflight'][0]['total_funding_msat'] == (fundamt - spliceamt) * 1000
2338
assert p2['inflight'][0]['our_funding_msat'] == 0
2439
bitcoind.generate_block(6, wait_for_mempool=1)
2540
l2.daemon.wait_for_log(r'lightningd, splice_locked clearing inflights')
2641

2742
p1 = only_one(l1.rpc.listpeerchannels(peer_id=l2.info['id'])['channels'])
2843
p2 = only_one(l2.rpc.listpeerchannels(l1.info['id'])['channels'])
29-
assert p1['to_us_msat'] == 900000000
30-
assert p1['total_msat'] == 900000000
44+
assert p1['to_us_msat'] == (fundamt - spliceamt) * 1000
45+
assert p1['total_msat'] == (fundamt - spliceamt) * 1000
3146
assert p2['to_us_msat'] == 0
32-
assert p2['total_msat'] == 900000000
47+
assert p2['total_msat'] == (fundamt - spliceamt) * 1000
3348
assert 'inflight' not in p1
3449
assert 'inflight' not in p2
3550

3651
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == 2)
3752
wait_for(lambda: len(l1.rpc.listfunds()['channels']) == 1)
3853

54+
# At the end we'd expect the balance of channel 1 to be down by the splice amount
55+
end_channel_balance = Millisatoshi(bkpr_account_balance(l1, first_channel_id(l1, l2)))
56+
end_wallet_balance = Millisatoshi(bkpr_account_balance(l1, 'wallet'))
57+
assert initial_channel_balance - Millisatoshi(spliceamt * 1000) == end_channel_balance
58+
59+
# The fee is assumed to be the difference between the start+end balances?
60+
fee_guess = initial_wallet_balance + initial_channel_balance - end_channel_balance - end_wallet_balance
61+
62+
# We'd expect the following coin movements
63+
starting_wallet_msat = 2000000000
64+
expected_wallet_moves = [
65+
# initial deposit
66+
{'type': 'chain_mvt', 'credit_msat': starting_wallet_msat, 'debit_msat': 0, 'tags': ['deposit']},
67+
# channel open spend
68+
{'type': 'chain_mvt', 'credit_msat': 0, 'debit_msat': starting_wallet_msat, 'tags': ['withdrawal']},
69+
# channel open change
70+
{'type': 'chain_mvt', 'credit_msat': initial_wallet_balance, 'debit_msat': 0, 'tags': ['deposit']},
71+
# deposit of spliceamt - fees
72+
{'type': 'chain_mvt', 'credit_msat': Millisatoshi(spliceamt * 1000) - fee_guess, 'debit_msat': 0, 'tags': ['deposit']},
73+
]
74+
75+
check_coin_moves(l1, 'wallet', expected_wallet_moves, chainparams)
76+
expected_channel_moves = [
77+
# channel_open [utxo created], chain_mvt (fundamt - spliceamt)
78+
{'type': 'chain_mvt', 'credit_msat': fundamt * 1000, 'debit_msat': 0, 'tags': ['channel_open', 'opener']},
79+
# channel_close [utxo spend], chain_mvt (fundamt)
80+
{'type': 'chain_mvt', 'debit_msat': fundamt * 1000, 'credit_msat': 0, 'tags': ['channel_close', 'splice']},
81+
# channel_open [utxo created], chain_mvt (fundamt - spliceamt)
82+
{'type': 'chain_mvt', 'credit_msat': (fundamt - spliceamt) * 1000, 'debit_msat': 0, 'tags': ['channel_open', 'opener']},
83+
]
84+
check_coin_moves(l1, first_channel_id(l1, l2), expected_channel_moves, chainparams)
85+
86+
# Make sure the channel isn't marked as closed in bookkeeper
87+
account_id = first_channel_id(l1, l2)
88+
account_info = only_one([acct for acct in l1.rpc.bkpr_listbalances()['accounts'] if acct['account'] == account_id])
89+
assert not account_info['account_closed']
90+
91+
# We'd also expect the wallet to be up by splice amt - fees
92+
onchain_fees = [fee for fee in l1.rpc.bkpr_listincome()['income_events'] if fee['tag'] == 'onchain_fee']
93+
assert len(onchain_fees) == 2
94+
total_fees = sum([x['debit_msat'] for x in onchain_fees])
95+
assert starting_wallet_msat == end_wallet_balance + total_fees + end_channel_balance
96+
97+
# Now close the channel and check that everything resolves as expected
98+
l1.rpc.close(l2.info['id'])
99+
l1.wait_for_channel_onchain(l2.info['id'])
100+
account_info = only_one([acct for acct in l1.rpc.bkpr_listbalances()['accounts'] if acct['account'] == account_id])
101+
assert not account_info['account_closed']
102+
39103

40104
@pytest.mark.openchannel('v1')
41105
@pytest.mark.openchannel('v2')
42106
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
43-
def test_script_splice_in(node_factory, bitcoind):
44-
l1, l2 = node_factory.line_graph(2, fundamount=1000000, wait_for_announce=True, opts={'experimental-splicing': None})
107+
def test_script_splice_in(node_factory, bitcoind, chainparams):
108+
fundamt = 1000000
109+
110+
coin_mvt_plugin = Path(__file__).parent / "plugins" / "coin_movements.py"
111+
l1, l2 = node_factory.line_graph(2, fundamount=fundamt, wait_for_announce=True,
112+
opts={'experimental-splicing': None,
113+
'plugin': coin_mvt_plugin})
114+
115+
initial_wallet_balance = Millisatoshi(bkpr_account_balance(l1, 'wallet'))
116+
initial_channel_balance = Millisatoshi(bkpr_account_balance(l1, first_channel_id(l1, l2)))
117+
assert initial_channel_balance == Millisatoshi(fundamt * 1000)
118+
45119
# Splice in 100k sats into first channel, explicitly taking out 200k sats from wallet
46120
# and letting change go automatically back to wallet (100k less onchain fees)
47-
l1.rpc.splice("wallet -> 200000; 100000 -> *:?", force_feerate=True, debug_log=True)
121+
spliceamt = 100000
122+
withdraw_amt = 200000
123+
starting_wallet_msat = withdraw_amt * 10000
124+
125+
l1.rpc.splice(f"wallet -> {withdraw_amt}; {spliceamt} -> *:?", force_feerate=True, debug_log=True)
48126
p1 = only_one(l1.rpc.listpeerchannels(peer_id=l2.info['id'])['channels'])
49127
p2 = only_one(l2.rpc.listpeerchannels(l1.info['id'])['channels'])
50-
assert p1['inflight'][0]['splice_amount'] == 100000
51-
assert p1['inflight'][0]['total_funding_msat'] == 1100000000
52-
assert p1['inflight'][0]['our_funding_msat'] == 1000000000
128+
assert p1['inflight'][0]['splice_amount'] == spliceamt
129+
assert p1['inflight'][0]['total_funding_msat'] == (fundamt + spliceamt) * 1000
130+
assert p1['inflight'][0]['our_funding_msat'] == fundamt * 1000
53131
assert p2['inflight'][0]['splice_amount'] == 0
54-
assert p2['inflight'][0]['total_funding_msat'] == 1100000000
132+
assert p2['inflight'][0]['total_funding_msat'] == (fundamt + spliceamt) * 1000
55133
assert p2['inflight'][0]['our_funding_msat'] == 0
56134
bitcoind.generate_block(6, wait_for_mempool=1)
57135
l2.daemon.wait_for_log(r'lightningd, splice_locked clearing inflights')
58136

59137
p1 = only_one(l1.rpc.listpeerchannels(peer_id=l2.info['id'])['channels'])
60138
p2 = only_one(l2.rpc.listpeerchannels(l1.info['id'])['channels'])
61-
assert p1['to_us_msat'] == 1100000000
62-
assert p1['total_msat'] == 1100000000
139+
assert p1['to_us_msat'] == (fundamt + spliceamt) * 1000
140+
assert p1['total_msat'] == (fundamt + spliceamt) * 1000
63141
assert p2['to_us_msat'] == 0
64-
assert p2['total_msat'] == 1100000000
142+
assert p2['total_msat'] == (fundamt + spliceamt) * 1000
65143
assert 'inflight' not in p1
66144
assert 'inflight' not in p2
67145

68146
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == 1)
69147
wait_for(lambda: len(l1.rpc.listfunds()['channels']) == 1)
148+
149+
# At the end we'd expect the balance of channel 1 to be up by the splice amount
150+
end_channel_balance = Millisatoshi(bkpr_account_balance(l1, first_channel_id(l1, l2)))
151+
end_wallet_balance = Millisatoshi(bkpr_account_balance(l1, 'wallet'))
152+
assert initial_channel_balance + Millisatoshi(spliceamt * 1000) == end_channel_balance
153+
154+
# The fee is assumed to be the difference between the start+end balances?
155+
fee_guess = initial_wallet_balance + initial_channel_balance - end_channel_balance - end_wallet_balance
156+
157+
# We'd expect the following coin movements
158+
expected_wallet_moves = [
159+
# initial deposit
160+
{'type': 'chain_mvt', 'credit_msat': starting_wallet_msat, 'debit_msat': 0, 'tags': ['deposit']},
161+
# channel open spend
162+
{'type': 'chain_mvt', 'credit_msat': 0, 'debit_msat': starting_wallet_msat, 'tags': ['withdrawal']},
163+
# channel open change
164+
{'type': 'chain_mvt', 'credit_msat': initial_wallet_balance, 'debit_msat': 0, 'tags': ['deposit']},
165+
# splice-in spend
166+
{'type': 'chain_mvt', 'debit_msat': initial_wallet_balance, 'credit_msat': 0, 'tags': ['withdrawal']},
167+
# post-splice deposit
168+
{'type': 'chain_mvt', 'credit_msat': initial_wallet_balance - Millisatoshi(spliceamt * 1000) - fee_guess, 'debit_msat': 0, 'tags': ['deposit']},
169+
]
170+
171+
check_coin_moves(l1, 'wallet', expected_wallet_moves, chainparams)
172+
expected_channel_moves = [
173+
# channel_open [utxo created], chain_mvt
174+
{'type': 'chain_mvt', 'credit_msat': fundamt * 1000, 'debit_msat': 0, 'tags': ['channel_open', 'opener']},
175+
# channel_close [utxo spend], chain_mvt (fundamt)
176+
{'type': 'chain_mvt', 'debit_msat': fundamt * 1000, 'credit_msat': 0, 'tags': ['channel_close', 'splice']},
177+
# channel_open [utxo created], chain_mvt (fundamt - spliceamt)
178+
{'type': 'chain_mvt', 'credit_msat': (fundamt + spliceamt) * 1000, 'debit_msat': 0, 'tags': ['channel_open', 'opener']},
179+
]
180+
check_coin_moves(l1, first_channel_id(l1, l2), expected_channel_moves, chainparams)
181+
182+
# Make sure the channel isn't marked as closed in bookkeeper
183+
account_id = first_channel_id(l1, l2)
184+
account_info = only_one([acct for acct in l1.rpc.bkpr_listbalances()['accounts'] if acct['account'] == account_id])
185+
assert not account_info['account_closed']
186+
187+
# We'd also expect the wallet to be down by splice amt + fees
188+
onchain_fees = [fee for fee in l1.rpc.bkpr_listincome()['income_events'] if fee['tag'] == 'onchain_fee']
189+
assert len(onchain_fees) == 2
190+
total_fees = sum([x['debit_msat'] for x in onchain_fees])
191+
assert starting_wallet_msat == end_wallet_balance + total_fees + end_channel_balance
192+
193+
# Now close the channel and check that everything resolves as expected
194+
l1.rpc.close(l2.info['id'])
195+
l1.wait_for_channel_onchain(l2.info['id'])
196+
account_info = only_one([acct for acct in l1.rpc.bkpr_listbalances()['accounts'] if acct['account'] == account_id])
197+
assert not account_info['account_closed']

tests/utils.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ def expected_channel_features(extra=[]):
7474
return hex_bits(features + extra)
7575

7676

77+
def bkpr_account_balance(node, acct_id):
78+
balances = node.rpc.bkpr_listbalances()['accounts']
79+
acct = only_one([acct for acct in balances if acct['account'] == acct_id])
80+
return only_one(acct['balances'])['balance_msat']
81+
82+
7783
def move_matches(exp, mv):
7884
if mv['type'] != exp['type']:
7985
return False

0 commit comments

Comments
 (0)