cln/tests/test_splice.py
Dusty Daemon ee2d5f8117 splice: Make feerate opening + feerate_offset
Splicing feerate was being derived by `default_feerate` which was actually `unilateral_feerate`, which is incorrect.

Instead we define an explicit `splice_feerate` in the chain topology that is `opening` + `feerate_offset`.

This slipped through testing because regtest isn’t a great test of fees we see in the wild.

Changelog-Fixed: Fixes high fee issue that caused `splicein` and `spliceout` to abort.
2026-05-11 15:54:37 +09:30

921 lines
44 KiB
Python

from fixtures import * # noqa: F401,F403
from pathlib import Path
from pyln.client import Millisatoshi
import pytest
import re
import unittest
from utils import (
bkpr_account_balance, check_coin_moves, first_channel_id,
TEST_NETWORK, only_one, wait_for
)
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_script_splice_out(node_factory, bitcoind, chainparams):
fundamt = 1000000
coin_mvt_plugin = Path(__file__).parent / "plugins" / "coin_movements.py"
l1, l2 = node_factory.line_graph(2, fundamount=fundamt, wait_for_announce=True,
opts={'plugin': coin_mvt_plugin})
initial_wallet_balance = Millisatoshi(bkpr_account_balance(l1, 'wallet'))
initial_channel_balance = Millisatoshi(bkpr_account_balance(l1, first_channel_id(l1, l2)))
assert initial_channel_balance == Millisatoshi(fundamt * 1000)
# Splice out 100k from first channel, explicitly putting result less fees into onchain wallet
spliceamt = 100000
l1.rpc.splice(f"*:? -> {spliceamt}; 100%-fee -> wallet", debug_log=True)
p1 = only_one(l1.rpc.listpeerchannels(peer_id=l2.info['id'])['channels'])
p2 = only_one(l2.rpc.listpeerchannels(l1.info['id'])['channels'])
assert p1['inflight'][0]['splice_amount'] == -1 * spliceamt
assert p1['inflight'][0]['total_funding_msat'] == (fundamt - spliceamt) * 1000
assert p1['inflight'][0]['our_funding_msat'] == fundamt * 1000
assert p2['inflight'][0]['splice_amount'] == 0
assert p2['inflight'][0]['total_funding_msat'] == (fundamt - spliceamt) * 1000
assert p2['inflight'][0]['our_funding_msat'] == 0
bitcoind.generate_block(6, wait_for_mempool=1)
l2.daemon.wait_for_log(r'lightningd, splice_locked clearing inflights')
p1 = only_one(l1.rpc.listpeerchannels(peer_id=l2.info['id'])['channels'])
p2 = only_one(l2.rpc.listpeerchannels(l1.info['id'])['channels'])
assert p1['to_us_msat'] == (fundamt - spliceamt) * 1000
assert p1['total_msat'] == (fundamt - spliceamt) * 1000
assert p2['to_us_msat'] == 0
assert p2['total_msat'] == (fundamt - spliceamt) * 1000
assert 'inflight' not in p1
assert 'inflight' not in p2
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == 2)
wait_for(lambda: len(l1.rpc.listfunds()['channels']) == 1)
# At the end we'd expect the balance of channel 1 to be down by the splice amount
end_channel_balance = Millisatoshi(bkpr_account_balance(l1, first_channel_id(l1, l2)))
end_wallet_balance = Millisatoshi(bkpr_account_balance(l1, 'wallet'))
assert initial_channel_balance - Millisatoshi(spliceamt * 1000) == end_channel_balance
# The fee is assumed to be the difference between the start+end balances?
fee_guess = initial_wallet_balance + initial_channel_balance - end_channel_balance - end_wallet_balance
# We'd expect the following coin movements
starting_wallet_msat = 2000000000
expected_wallet_moves = [
# initial deposit
{'type': 'chain_mvt', 'credit_msat': starting_wallet_msat, 'debit_msat': 0, 'tags': ['deposit']},
# channel open spend
{'type': 'chain_mvt', 'credit_msat': 0, 'debit_msat': starting_wallet_msat, 'tags': ['withdrawal']},
# channel open change
{'type': 'chain_mvt', 'credit_msat': initial_wallet_balance, 'debit_msat': 0, 'tags': ['deposit']},
# deposit of spliceamt - fees
{'type': 'chain_mvt', 'credit_msat': Millisatoshi(spliceamt * 1000) - fee_guess, 'debit_msat': 0, 'tags': ['deposit']},
]
check_coin_moves(l1, 'wallet', expected_wallet_moves, chainparams)
expected_channel_moves = [
# channel_open [utxo created], chain_mvt (fundamt - spliceamt)
{'type': 'chain_mvt', 'credit_msat': fundamt * 1000, 'debit_msat': 0, 'tags': ['channel_open', 'opener']},
# channel_close [utxo spend], chain_mvt (fundamt)
{'type': 'chain_mvt', 'debit_msat': fundamt * 1000, 'credit_msat': 0, 'tags': ['channel_close', 'splice']},
# channel_open [utxo created], chain_mvt (fundamt - spliceamt)
{'type': 'chain_mvt', 'credit_msat': (fundamt - spliceamt) * 1000, 'debit_msat': 0, 'tags': ['channel_open', 'opener']},
]
check_coin_moves(l1, first_channel_id(l1, l2), expected_channel_moves, chainparams)
# Make sure the channel isn't marked as closed in bookkeeper
account_id = first_channel_id(l1, l2)
account_info = only_one([acct for acct in l1.rpc.bkpr_listbalances()['accounts'] if acct['account'] == account_id])
assert not account_info['account_closed']
# We'd also expect the wallet to be up by splice amt - fees
onchain_fees = [fee for fee in l1.rpc.bkpr_listincome()['income_events'] if fee['tag'] == 'onchain_fee']
assert len(onchain_fees) == 2
total_fees = sum([x['debit_msat'] for x in onchain_fees])
assert starting_wallet_msat == end_wallet_balance + total_fees + end_channel_balance
# Now close the channel and check that everything resolves as expected
l1.rpc.close(l2.info['id'])
l1.wait_for_channel_onchain(l2.info['id'])
account_info = only_one([acct for acct in l1.rpc.bkpr_listbalances()['accounts'] if acct['account'] == account_id])
assert not account_info['account_closed']
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_script_splice_in(node_factory, bitcoind, chainparams):
fundamt = 1000000
coin_mvt_plugin = Path(__file__).parent / "plugins" / "coin_movements.py"
l1, l2 = node_factory.line_graph(2, fundamount=fundamt, wait_for_announce=True,
opts={'plugin': coin_mvt_plugin})
initial_wallet_balance = Millisatoshi(bkpr_account_balance(l1, 'wallet'))
initial_channel_balance = Millisatoshi(bkpr_account_balance(l1, first_channel_id(l1, l2)))
assert initial_channel_balance == Millisatoshi(fundamt * 1000)
# Splice in 100k sats into first channel, explicitly taking out 200k sats from wallet
# and letting change go automatically back to wallet (100k less onchain fees)
spliceamt = 100000
withdraw_amt = 200000
starting_wallet_msat = withdraw_amt * 10000
l1.rpc.splice(f"wallet -> {withdraw_amt}; {spliceamt} -> *:?", debug_log=True)
p1 = only_one(l1.rpc.listpeerchannels(peer_id=l2.info['id'])['channels'])
p2 = only_one(l2.rpc.listpeerchannels(l1.info['id'])['channels'])
assert p1['inflight'][0]['splice_amount'] == spliceamt
assert p1['inflight'][0]['total_funding_msat'] == (fundamt + spliceamt) * 1000
assert p1['inflight'][0]['our_funding_msat'] == fundamt * 1000
assert p2['inflight'][0]['splice_amount'] == 0
assert p2['inflight'][0]['total_funding_msat'] == (fundamt + spliceamt) * 1000
assert p2['inflight'][0]['our_funding_msat'] == 0
bitcoind.generate_block(6, wait_for_mempool=1)
l2.daemon.wait_for_log(r'lightningd, splice_locked clearing inflights')
p1 = only_one(l1.rpc.listpeerchannels(peer_id=l2.info['id'])['channels'])
p2 = only_one(l2.rpc.listpeerchannels(l1.info['id'])['channels'])
assert p1['to_us_msat'] == (fundamt + spliceamt) * 1000
assert p1['total_msat'] == (fundamt + spliceamt) * 1000
assert p2['to_us_msat'] == 0
assert p2['total_msat'] == (fundamt + spliceamt) * 1000
assert 'inflight' not in p1
assert 'inflight' not in p2
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == 1)
wait_for(lambda: len(l1.rpc.listfunds()['channels']) == 1)
# At the end we'd expect the balance of channel 1 to be up by the splice amount
end_channel_balance = Millisatoshi(bkpr_account_balance(l1, first_channel_id(l1, l2)))
end_wallet_balance = Millisatoshi(bkpr_account_balance(l1, 'wallet'))
assert initial_channel_balance + Millisatoshi(spliceamt * 1000) == end_channel_balance
# The fee is assumed to be the difference between the start+end balances?
fee_guess = initial_wallet_balance + initial_channel_balance - end_channel_balance - end_wallet_balance
# We'd expect the following coin movements
expected_wallet_moves = [
# initial deposit
{'type': 'chain_mvt', 'credit_msat': starting_wallet_msat, 'debit_msat': 0, 'tags': ['deposit']},
# channel open spend
{'type': 'chain_mvt', 'credit_msat': 0, 'debit_msat': starting_wallet_msat, 'tags': ['withdrawal']},
# channel open change
{'type': 'chain_mvt', 'credit_msat': initial_wallet_balance, 'debit_msat': 0, 'tags': ['deposit']},
# splice-in spend
{'type': 'chain_mvt', 'debit_msat': initial_wallet_balance, 'credit_msat': 0, 'tags': ['withdrawal']},
# post-splice deposit
{'type': 'chain_mvt', 'credit_msat': initial_wallet_balance - Millisatoshi(spliceamt * 1000) - fee_guess, 'debit_msat': 0, 'tags': ['deposit']},
]
check_coin_moves(l1, 'wallet', expected_wallet_moves, chainparams)
expected_channel_moves = [
# channel_open [utxo created], chain_mvt
{'type': 'chain_mvt', 'credit_msat': fundamt * 1000, 'debit_msat': 0, 'tags': ['channel_open', 'opener']},
# channel_close [utxo spend], chain_mvt (fundamt)
{'type': 'chain_mvt', 'debit_msat': fundamt * 1000, 'credit_msat': 0, 'tags': ['channel_close', 'splice']},
# channel_open [utxo created], chain_mvt (fundamt - spliceamt)
{'type': 'chain_mvt', 'credit_msat': (fundamt + spliceamt) * 1000, 'debit_msat': 0, 'tags': ['channel_open', 'opener']},
]
check_coin_moves(l1, first_channel_id(l1, l2), expected_channel_moves, chainparams)
# Make sure the channel isn't marked as closed in bookkeeper
account_id = first_channel_id(l1, l2)
account_info = only_one([acct for acct in l1.rpc.bkpr_listbalances()['accounts'] if acct['account'] == account_id])
assert not account_info['account_closed']
# We'd also expect the wallet to be down by splice amt + fees
onchain_fees = [fee for fee in l1.rpc.bkpr_listincome()['income_events'] if fee['tag'] == 'onchain_fee']
assert len(onchain_fees) == 2
total_fees = sum([x['debit_msat'] for x in onchain_fees])
assert starting_wallet_msat == end_wallet_balance + total_fees + end_channel_balance
# Now close the channel and check that everything resolves as expected
l1.rpc.close(l2.info['id'])
l1.wait_for_channel_onchain(l2.info['id'])
account_info = only_one([acct for acct in l1.rpc.bkpr_listbalances()['accounts'] if acct['account'] == account_id])
assert not account_info['account_closed']
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_script_splice_msat(node_factory, bitcoind, chainparams):
# Test splices with msat level sats to confirm rounding
fundamt = 1000000
coin_mvt_plugin = Path(__file__).parent / "plugins" / "coin_movements.py"
l1, l2 = node_factory.line_graph(2, fundamount=fundamt, wait_for_announce=True,
opts={'plugin': coin_mvt_plugin})
initial_wallet_balance = Millisatoshi(bkpr_account_balance(l1, 'wallet'))
initial_channel_balance = Millisatoshi(bkpr_account_balance(l1, first_channel_id(l1, l2)))
assert initial_channel_balance == Millisatoshi(fundamt * 1000)
# Splice in 100k sats into first channel, explicitly taking out 200k sats from wallet
# and letting change go automatically back to wallet (100k less onchain fees)
spliceamt = 100000
withdraw_amt = 200000
starting_wallet_msat = withdraw_amt * 10000
sent_msats = 1111
# purposely pay in msats
inv = l2.rpc.invoice(sent_msats, '1', 'no_1')
l1.rpc.pay(inv['bolt11'])
initial_channel_balance -= sent_msats
l1.rpc.splice(f"wallet -> {withdraw_amt}; {spliceamt} -> *:?", debug_log=True)
p1 = only_one(l1.rpc.listpeerchannels(peer_id=l2.info['id'])['channels'])
p2 = only_one(l2.rpc.listpeerchannels(l1.info['id'])['channels'])
assert p1['inflight'][0]['splice_amount'] == spliceamt
assert p1['inflight'][0]['total_funding_msat'] == (fundamt + spliceamt) * 1000
assert p1['inflight'][0]['our_funding_msat'] == fundamt * 1000
assert p2['inflight'][0]['splice_amount'] == 0
assert p2['inflight'][0]['total_funding_msat'] == (fundamt + spliceamt) * 1000
assert p2['inflight'][0]['our_funding_msat'] == 0
bitcoind.generate_block(6, wait_for_mempool=1)
l2.daemon.wait_for_log(r'lightningd, splice_locked clearing inflights')
p1 = only_one(l1.rpc.listpeerchannels(peer_id=l2.info['id'])['channels'])
p2 = only_one(l2.rpc.listpeerchannels(l1.info['id'])['channels'])
assert p1['to_us_msat'] == ((fundamt + spliceamt) * 1000) - sent_msats
assert p1['total_msat'] == (fundamt + spliceamt) * 1000
assert p2['to_us_msat'] == sent_msats
assert p2['total_msat'] == (fundamt + spliceamt) * 1000
assert 'inflight' not in p1
assert 'inflight' not in p2
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == 1)
wait_for(lambda: len(l1.rpc.listfunds()['channels']) == 1)
# At the end we'd expect the balance of channel 1 to be up by the splice amount
end_channel_balance = Millisatoshi(bkpr_account_balance(l1, first_channel_id(l1, l2)))
end_wallet_balance = Millisatoshi(bkpr_account_balance(l1, 'wallet'))
assert initial_channel_balance + Millisatoshi(spliceamt * 1000) == end_channel_balance
# The fee is assumed to be the difference between the start+end balances?
fee_guess = initial_wallet_balance + initial_channel_balance - end_channel_balance - end_wallet_balance
# We'd expect the following coin movements
expected_wallet_moves = [
# initial deposit
{'type': 'chain_mvt', 'credit_msat': starting_wallet_msat, 'debit_msat': 0, 'tags': ['deposit']},
# channel open spend
{'type': 'chain_mvt', 'credit_msat': 0, 'debit_msat': starting_wallet_msat, 'tags': ['withdrawal']},
# channel open change
{'type': 'chain_mvt', 'credit_msat': initial_wallet_balance, 'debit_msat': 0, 'tags': ['deposit']},
# splice-in spend
{'type': 'chain_mvt', 'debit_msat': initial_wallet_balance, 'credit_msat': 0, 'tags': ['withdrawal']},
# post-splice deposit
{'type': 'chain_mvt', 'credit_msat': initial_wallet_balance - Millisatoshi(spliceamt * 1000) - fee_guess, 'debit_msat': 0, 'tags': ['deposit']},
]
check_coin_moves(l1, 'wallet', expected_wallet_moves, chainparams)
expected_channel_moves = [
# channel_open [utxo created], chain_mvt
{'type': 'chain_mvt', 'credit_msat': fundamt * 1000, 'debit_msat': 0, 'tags': ['channel_open', 'opener']},
# channel payment
{'type': 'channel_mvt', 'debit_msat': sent_msats, 'credit_msat': 0, 'tags': ['invoice'], 'fees_msat': 0},
# channel_close [utxo spend], chain_mvt (fundamt)
{'type': 'chain_mvt', 'debit_msat': fundamt * 1000 - sent_msats, 'credit_msat': 0, 'tags': ['channel_close', 'splice']},
# channel_open [utxo created], chain_mvt (fundamt - spliceamt)
{'type': 'chain_mvt', 'credit_msat': (fundamt + spliceamt) * 1000 - sent_msats, 'debit_msat': 0, 'tags': ['channel_open', 'opener']},
]
check_coin_moves(l1, first_channel_id(l1, l2), expected_channel_moves, chainparams)
# Make sure the channel isn't marked as closed in bookkeeper
account_id = first_channel_id(l1, l2)
account_info = only_one([acct for acct in l1.rpc.bkpr_listbalances()['accounts'] if acct['account'] == account_id])
assert not account_info['account_closed']
# We'd also expect the wallet to be down by splice amt + fees
onchain_fees = [fee for fee in l1.rpc.bkpr_listincome()['income_events'] if fee['tag'] == 'onchain_fee']
assert len(onchain_fees) == 2
total_fees = sum([x['debit_msat'] for x in onchain_fees])
assert starting_wallet_msat - sent_msats == end_wallet_balance + total_fees + end_channel_balance
# Now close the channel and check that everything resolves as expected
l1.rpc.close(l2.info['id'])
l1.wait_for_channel_onchain(l2.info['id'])
account_info = only_one([acct for acct in l1.rpc.bkpr_listbalances()['accounts'] if acct['account'] == account_id])
assert not account_info['account_closed']
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_script_splice_msat_roundup(node_factory, bitcoind, chainparams):
# Test splices with msat level sats to confirm rounding, using an amount
# That would naturally round up (even though we always round down).
fundamt = 1000000
coin_mvt_plugin = Path(__file__).parent / "plugins" / "coin_movements.py"
l1, l2 = node_factory.line_graph(2, fundamount=fundamt, wait_for_announce=True,
opts={'plugin': coin_mvt_plugin})
initial_wallet_balance = Millisatoshi(bkpr_account_balance(l1, 'wallet'))
initial_channel_balance = Millisatoshi(bkpr_account_balance(l1, first_channel_id(l1, l2)))
assert initial_channel_balance == Millisatoshi(fundamt * 1000)
# Splice in 100k sats into first channel, explicitly taking out 200k sats from wallet
# and letting change go automatically back to wallet (100k less onchain fees)
spliceamt = 100000
withdraw_amt = 200000
starting_wallet_msat = withdraw_amt * 10000
sent_msats = 1999
# purposely pay in msats
inv = l2.rpc.invoice(sent_msats, '1', 'no_1')
l1.rpc.pay(inv['bolt11'])
initial_channel_balance -= sent_msats
l1.rpc.splice(f"wallet -> {withdraw_amt}; {spliceamt} -> *:?", debug_log=True)
p1 = only_one(l1.rpc.listpeerchannels(peer_id=l2.info['id'])['channels'])
p2 = only_one(l2.rpc.listpeerchannels(l1.info['id'])['channels'])
assert p1['inflight'][0]['splice_amount'] == spliceamt
assert p1['inflight'][0]['total_funding_msat'] == (fundamt + spliceamt) * 1000
assert p1['inflight'][0]['our_funding_msat'] == fundamt * 1000
assert p2['inflight'][0]['splice_amount'] == 0
assert p2['inflight'][0]['total_funding_msat'] == (fundamt + spliceamt) * 1000
assert p2['inflight'][0]['our_funding_msat'] == 0
bitcoind.generate_block(6, wait_for_mempool=1)
l2.daemon.wait_for_log(r'lightningd, splice_locked clearing inflights')
p1 = only_one(l1.rpc.listpeerchannels(peer_id=l2.info['id'])['channels'])
p2 = only_one(l2.rpc.listpeerchannels(l1.info['id'])['channels'])
assert p1['to_us_msat'] == ((fundamt + spliceamt) * 1000) - sent_msats
assert p1['total_msat'] == (fundamt + spliceamt) * 1000
assert p2['to_us_msat'] == sent_msats
assert p2['total_msat'] == (fundamt + spliceamt) * 1000
assert 'inflight' not in p1
assert 'inflight' not in p2
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == 1)
wait_for(lambda: len(l1.rpc.listfunds()['channels']) == 1)
# At the end we'd expect the balance of channel 1 to be up by the splice amount
end_channel_balance = Millisatoshi(bkpr_account_balance(l1, first_channel_id(l1, l2)))
end_wallet_balance = Millisatoshi(bkpr_account_balance(l1, 'wallet'))
assert initial_channel_balance + Millisatoshi(spliceamt * 1000) == end_channel_balance
# The fee is assumed to be the difference between the start+end balances?
fee_guess = initial_wallet_balance + initial_channel_balance - end_channel_balance - end_wallet_balance
# We'd expect the following coin movements
expected_wallet_moves = [
# initial deposit
{'type': 'chain_mvt', 'credit_msat': starting_wallet_msat, 'debit_msat': 0, 'tags': ['deposit']},
# channel open spend
{'type': 'chain_mvt', 'credit_msat': 0, 'debit_msat': starting_wallet_msat, 'tags': ['withdrawal']},
# channel open change
{'type': 'chain_mvt', 'credit_msat': initial_wallet_balance, 'debit_msat': 0, 'tags': ['deposit']},
# splice-in spend
{'type': 'chain_mvt', 'debit_msat': initial_wallet_balance, 'credit_msat': 0, 'tags': ['withdrawal']},
# post-splice deposit
{'type': 'chain_mvt', 'credit_msat': initial_wallet_balance - Millisatoshi(spliceamt * 1000) - fee_guess, 'debit_msat': 0, 'tags': ['deposit']},
]
check_coin_moves(l1, 'wallet', expected_wallet_moves, chainparams)
expected_channel_moves = [
# channel_open [utxo created], chain_mvt
{'type': 'chain_mvt', 'credit_msat': fundamt * 1000, 'debit_msat': 0, 'tags': ['channel_open', 'opener']},
# channel payment
{'type': 'channel_mvt', 'debit_msat': sent_msats, 'credit_msat': 0, 'tags': ['invoice'], 'fees_msat': 0},
# channel_close [utxo spend], chain_mvt (fundamt)
{'type': 'chain_mvt', 'debit_msat': fundamt * 1000 - sent_msats, 'credit_msat': 0, 'tags': ['channel_close', 'splice']},
# channel_open [utxo created], chain_mvt (fundamt - spliceamt)
{'type': 'chain_mvt', 'credit_msat': (fundamt + spliceamt) * 1000 - sent_msats, 'debit_msat': 0, 'tags': ['channel_open', 'opener']},
]
check_coin_moves(l1, first_channel_id(l1, l2), expected_channel_moves, chainparams)
# Make sure the channel isn't marked as closed in bookkeeper
account_id = first_channel_id(l1, l2)
account_info = only_one([acct for acct in l1.rpc.bkpr_listbalances()['accounts'] if acct['account'] == account_id])
assert not account_info['account_closed']
# We'd also expect the wallet to be down by splice amt + fees
onchain_fees = [fee for fee in l1.rpc.bkpr_listincome()['income_events'] if fee['tag'] == 'onchain_fee']
assert len(onchain_fees) == 2
total_fees = sum([x['debit_msat'] for x in onchain_fees])
assert starting_wallet_msat - sent_msats == end_wallet_balance + total_fees + end_channel_balance
# Now close the channel and check that everything resolves as expected
l1.rpc.close(l2.info['id'])
l1.wait_for_channel_onchain(l2.info['id'])
account_info = only_one([acct for acct in l1.rpc.bkpr_listbalances()['accounts'] if acct['account'] == account_id])
assert not account_info['account_closed']
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_script_two_chan_splice_in(node_factory, bitcoind):
l1, l2, l3 = node_factory.line_graph(3, fundamount=1000000, wait_for_announce=True)
chan_id1 = l2.get_channel_id(l1)
chan_id2 = l2.get_channel_id(l3)
# l2 will splice funds into the channels with l1 and l3 at the same time
result = l2.rpc.splice(f"wallet -> 200000+fee; 100000 -> {chan_id1}; 100000 -> {chan_id2}")
l3.daemon.wait_for_log(r'CHANNELD_NORMAL to CHANNELD_AWAITING_SPLICE')
l2.daemon.wait_for_log(r'CHANNELD_NORMAL to CHANNELD_AWAITING_SPLICE')
l1.daemon.wait_for_log(r'CHANNELD_NORMAL to CHANNELD_AWAITING_SPLICE')
wait_for(lambda: len(list(bitcoind.rpc.getrawmempool(True).keys())) == 1)
assert result['txid'] in list(bitcoind.rpc.getrawmempool(True).keys())
bitcoind.generate_block(6, wait_for_mempool=1)
l3.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL')
l2.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL')
l1.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL')
# l2 should now have funds on their side to pay l1
inv = l1.rpc.invoice(10000, '1', 'no_1')
l2.rpc.pay(inv['bolt11'])
# l2 spliced extra funds into chan with l3 (but l3 still has 0 on their side)
# Send a payment l2->l3 just to check for channel stability
inv = l3.rpc.invoice(10000, '2', 'no_2')
l2.rpc.pay(inv['bolt11'])
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_script_two_chan_splice_out(node_factory, bitcoind):
l1, l2, l3 = node_factory.line_graph(3, fundamount=1000000, wait_for_announce=True)
# We need to get funds into l1 -> l2 channel so we can splice it out
inv = l2.rpc.invoice(100000000, '1', 'no_1')
l1.rpc.pay(inv['bolt11'])
chan_id1 = l2.get_channel_id(l1)
chan_id2 = l2.get_channel_id(l3)
# l2 will splice funds out of the channels with l1 and l3 at the same time
# By default extra funds get sent to wallet
result = l2.rpc.splice(f"{chan_id1} -> 100000; {chan_id2} -> 100000")
l3.daemon.wait_for_log(r'CHANNELD_NORMAL to CHANNELD_AWAITING_SPLICE')
l2.daemon.wait_for_log(r'CHANNELD_NORMAL to CHANNELD_AWAITING_SPLICE')
l1.daemon.wait_for_log(r'CHANNELD_NORMAL to CHANNELD_AWAITING_SPLICE')
wait_for(lambda: len(list(bitcoind.rpc.getrawmempool(True).keys())) == 1)
assert result['txid'] in list(bitcoind.rpc.getrawmempool(True).keys())
bitcoind.generate_block(6, wait_for_mempool=1)
l3.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL')
l2.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL')
l1.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL')
# no extra funds in channels but do some simple payments to test stability
inv = l2.rpc.invoice(10000, '2', 'no_2')
l1.rpc.pay(inv['bolt11'])
inv = l3.rpc.invoice(10000, '3', 'no_3')
l2.rpc.pay(inv['bolt11'])
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_script_two_chan_splice_inout(node_factory, bitcoind):
l1, l2, l3 = node_factory.line_graph(3, fundamount=1000000, wait_for_announce=True)
chan_id1 = l2.get_channel_id(l1)
chan_id2 = l2.get_channel_id(l3)
# move sats from chan 2 into chan 1
# By adding 100000 from wallet, the fee will be taken from this and the
# extra placed back into the wallet by default
result = l2.rpc.splice(f"wallet -> 100000; 100000 -> {chan_id1}; {chan_id2} -> 100000")
l3.daemon.wait_for_log(r'CHANNELD_NORMAL to CHANNELD_AWAITING_SPLICE')
l2.daemon.wait_for_log(r'CHANNELD_NORMAL to CHANNELD_AWAITING_SPLICE')
l1.daemon.wait_for_log(r'CHANNELD_NORMAL to CHANNELD_AWAITING_SPLICE')
wait_for(lambda: len(list(bitcoind.rpc.getrawmempool(True).keys())) == 1)
assert result['txid'] in list(bitcoind.rpc.getrawmempool(True).keys())
bitcoind.generate_block(6, wait_for_mempool=1)
l3.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL')
l2.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL')
l1.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL')
# l2 should now have funds on their side to pay l1
inv = l1.rpc.invoice(10000, '2', 'no_2')
l2.rpc.pay(inv['bolt11'])
# l2 spliced extra funds into chan with l3 (but l3 still has 0 on their side)
# Send a payment l2->l3 just to check for channel stability
inv = l3.rpc.invoice(10000, '3', 'no_3')
l2.rpc.pay(inv['bolt11'])
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_easy_splice_in(node_factory, bitcoind, chainparams):
fundamt = 1000000
coin_mvt_plugin = Path(__file__).parent / "plugins" / "coin_movements.py"
l1, l2 = node_factory.line_graph(2, fundamount=fundamt, wait_for_announce=True,
opts={'plugin': coin_mvt_plugin})
# Splice in 100k sats into first channel
spliceamt = 100000
l1.rpc.splicein("*:?", f"{spliceamt}")
p1 = only_one(l1.rpc.listpeerchannels(peer_id=l2.info['id'])['channels'])
p2 = only_one(l2.rpc.listpeerchannels(l1.info['id'])['channels'])
assert p1['inflight'][0]['splice_amount'] == spliceamt
assert p1['inflight'][0]['total_funding_msat'] == (fundamt + spliceamt) * 1000
assert p1['inflight'][0]['our_funding_msat'] == fundamt * 1000
assert p2['inflight'][0]['splice_amount'] == 0
assert p2['inflight'][0]['total_funding_msat'] == (fundamt + spliceamt) * 1000
assert p2['inflight'][0]['our_funding_msat'] == 0
bitcoind.generate_block(6, wait_for_mempool=1)
l2.daemon.wait_for_log(r'lightningd, splice_locked clearing inflights')
p1 = only_one(l1.rpc.listpeerchannels(peer_id=l2.info['id'])['channels'])
p2 = only_one(l2.rpc.listpeerchannels(l1.info['id'])['channels'])
assert p1['to_us_msat'] == (fundamt + spliceamt) * 1000
assert p1['total_msat'] == (fundamt + spliceamt) * 1000
assert p2['to_us_msat'] == 0
assert p2['total_msat'] == (fundamt + spliceamt) * 1000
assert 'inflight' not in p1
assert 'inflight' not in p2
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == 1)
wait_for(lambda: len(l1.rpc.listfunds()['channels']) == 1)
# Makes channels going from node 1 -> 2, 2 -> 3, etc up to 'qty' number channels.
# If balanced is True, than each channel will be balanced -- otherwise the lower
# index channel will have funds in the channel to the higher indexed one.
#
# The channels for the second node are returned in chanids
def make_chans(node_factory, qty=2, fundamount=1000000, balanced=True):
nodes = node_factory.line_graph(qty + 1, fundamount=fundamount, opts={'allow_bad_gossip': True})
chanids = []
for i in range(len(nodes) - 1):
nodes[i].daemon.wait_for_log(' to CHANNELD_NORMAL')
if balanced:
inv = nodes[i + 1].rpc.invoice(1000 * fundamount // 2, 'balance', 'balance')
nodes[i].rpc.xpay(inv['bolt11'])
chanids.insert(0, nodes[1].get_channel_id(nodes[0]))
if qty > 1:
chanids.insert(0, nodes[1].get_channel_id(nodes[2]))
return [nodes, chanids]
def verify_chans(nodes, bitcoind, txid, chanids, fee, expected_balances=None, fee_multiplier=None):
for node in nodes:
node.daemon.wait_for_log(r'CHANNELD_NORMAL to CHANNELD_AWAITING_SPLICE')
wait_for(lambda: len(list(bitcoind.rpc.getrawmempool(True).keys())) == 1)
bitcoind.generate_block(6, wait_for_mempool=1)
for chanid in chanids:
nodes[1].daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL')
if not expected_balances:
return
# Modify expected_balances by fee_multipler and fee
extra_funds = 0
last_index = 0
if fee_multiplier:
for index, multiplier in enumerate(fee_multiplier):
amount, remainder = divmod(multiplier * fee, 1)
expected_balances[index] += int(amount)
extra_funds += remainder
if multiplier != 0:
last_index = index
# If we have extra funds, put them all in the last item
if extra_funds > 0:
expected_balances[last_index] += int(extra_funds)
channel_funds = nodes[1].rpc.listfunds()['channels']
channel_funds.sort(key=lambda info: chanids.index(info['channel_id']))
funds = [info['our_amount_msat'] // 1000 for info in channel_funds]
assert funds == expected_balances
# * expected_balances is an array of sat values
# * fee_multiplier is an array of ints. The coorisponding channel balance expectation is adjusted
# by the fee * fee_multiplier. For example [0, -1] reduces the second channels expected balance
# by the fee value
def execute_script(node_factory, bitcoind, script, expected_balances=None, fee_multiplier=None):
nodes, chanids = make_chans(node_factory, script.count("{}"))
result = nodes[1].rpc.splice(script.format(*chanids), debug_log=True)
# Extract the fee that splice scripts thinks we're using from the logs
nodes[1].daemon.wait_for_log(r'calc_in_ppm_and_fee starting calculations FINALIZING PASS')
# It can either be "fee x" or "x fee" so we match on both
logline = nodes[1].daemon.wait_for_log(r'(fee [0-9]+sat)|([0-9]+sat fee)')
# Try both regex versions
reg_res = re.search(r'fee ([0-9]+)sat', logline) or re.search(r'([0-9]+)sat fee', logline)
# now we should know the fee
fee = reg_res.group(1)
verify_chans(nodes, bitcoind, result['txid'], chanids, int(fee), expected_balances, fee_multiplier)
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_script_two_chan_splice_b(node_factory, bitcoind):
execute_script(node_factory, bitcoind, "wallet -> 100000; {} -> 100000; {} -> 100000",
[500000 - 100000, 500000 - 100000])
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_script_two_chan_splice_c(node_factory, bitcoind):
execute_script(node_factory, bitcoind, "wallet -> 100000; 100000 -> {}; {} -> 100000",
[500000 + 100000, 500000 - 100000])
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_script_two_chan_splice_d(node_factory, bitcoind):
execute_script(node_factory, bitcoind, "wallet -> 250000; 100000 -> {}; 100000 -> {}",
[500000 + 100000, 500000 + 100000])
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_script_two_chan_splice_e(node_factory, bitcoind):
execute_script(node_factory, bitcoind, "{} -> 100000; {} -> 100000",
[500000 - 100000, 500000 - 100000])
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_script_two_chan_splice_f(node_factory, bitcoind):
execute_script(node_factory, bitcoind, "{} -> 200000; 100000 -> {}",
[500000 - 200000, 500000 + 100000])
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_script_two_chan_splice_g(node_factory, bitcoind):
execute_script(node_factory, bitcoind, "{} -> 200000; 100000 -> {}; * -> wallet",
[500000 - 200000, 500000 + 100000])
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_script_two_chan_splice_h(node_factory, bitcoind):
execute_script(node_factory, bitcoind, "wallet -> 200000+fee; 100000 -> {}; 100000 -> {}",
[500000 + 100000, 500000 + 100000])
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_script_two_chan_splice_ii(node_factory, bitcoind):
execute_script(node_factory, bitcoind, "100000 -> {}; {} -> 100000+fee",
[500000 + 100000, 500000 - 100000], [0, -1])
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_script_two_chan_splice_j(node_factory, bitcoind):
execute_script(node_factory, bitcoind, "100000-fee -> {}; {} -> 100000",
[500000 + 100000, 500000 - 100000], [-1, 0])
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_script_two_chan_splice_k(node_factory, bitcoind):
execute_script(node_factory, bitcoind, "{} -> 100000; 1000 -> {}",
[500000 - 100000, 500000 + 1000])
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_script_two_chan_splice_l(node_factory, bitcoind):
execute_script(node_factory, bitcoind, "wallet -> 100000; * -> {}; * -> {}",
[500000 + 50000, 500000 + 50000], [-0.5, -0.5])
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_script_two_chan_splice_m(node_factory, bitcoind):
execute_script(node_factory, bitcoind, "wallet -> *+fee; 100000 -> {}; 100000 -> {}",
[500000 + 100000, 500000 + 100000])
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_script_two_chan_splice_n(node_factory, bitcoind):
execute_script(node_factory, bitcoind, "wallet -> 100%+fee; {} -> 50%; 100000 -> {}",
[500000 // 2, 500000 + 100000])
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_script_two_chan_splice_oo(node_factory, bitcoind):
execute_script(node_factory, bitcoind, "wallet -> *+fee; {} -> 50%; 100000 -> {}",
[500000 // 2, 500000 + 100000])
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_script_two_chan_splice_p(node_factory, bitcoind):
execute_script(node_factory, bitcoind, "wallet -> *; {} -> 50%+fee; 100000 -> {}",
[500000 // 2, 500000 + 100000], [-1, 0])
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_script_two_chan_splice_q(node_factory, bitcoind):
execute_script(node_factory, bitcoind, "wallet -> *; {} -> 50000+fee; 100000 -> {}",
[500000 - 50000, 500000 + 100000], [-1, 0])
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_script_two_chan_splice_r(node_factory, bitcoind):
execute_script(node_factory, bitcoind, "wallet -> 0+fee; {} -> 100000; 100000 -> {}",
[500000 - 100000, 500000 + 100000])
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_script_two_chan_splice_s(node_factory, bitcoind):
execute_script(node_factory, bitcoind, "wallet -> 50000; {} -> 50000+fee; 100000 -> {}",
[500000 - 50000, 500000 + 100000], [-1, 0])
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_script_two_chan_splice_t(node_factory, bitcoind):
execute_script(node_factory, bitcoind, "wallet -> 100%; {} -> 50000+fee; 100000 -> {}",
[500000 - 50000, 500000 + 100000], [-1, 0])
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_script_two_chan_splice_u(node_factory, bitcoind):
execute_script(node_factory, bitcoind, "wallet -> 100%; {} -> 50000; 100000 -> {}",
[500000 - 50000, 500000 + 100000])
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_script_two_chan_splice_v(node_factory, bitcoind):
execute_script(node_factory, bitcoind, "wallet -> 100%; {} -> 100000; 100000 -> {}",
[500000 - 100000, 500000 + 100000])
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_script_two_chan_splice_x(node_factory, bitcoind):
execute_script(node_factory, bitcoind, "* -> wallet; * -> {}; {} -> 200000",
[500000 + 100000 + 1, 500000 - 200000], [-0.5, 0]) # (+1 to track rounded sat)
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_script_two_chan_splice_y(node_factory, bitcoind):
execute_script(node_factory, bitcoind, "wallet -> *; 100000 -> {}; 100000 -> {}",
[500000 + 100000, 500000 + 100000])
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_script_two_chan_splice_z(node_factory, bitcoind):
execute_script(node_factory, bitcoind, "wallet -> 100000; 70% -> {}; 30% -> {}",
[500000 + int(100000 * 0.7), 500000 + int(100000 * 0.3)], [-0.7, -0.3])
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_easy_splice_out(node_factory, bitcoind, chainparams):
fundamt = 1000000
coin_mvt_plugin = Path(__file__).parent / "plugins" / "coin_movements.py"
l1, l2 = node_factory.line_graph(2, fundamount=fundamt, wait_for_announce=True,
opts={'plugin': coin_mvt_plugin})
initial_wallet_balance = Millisatoshi(bkpr_account_balance(l1, 'wallet'))
# Splice out 100k from first channel, putting result less fees into onchain wallet
spliceamt = 100000
l1.rpc.spliceout("*:?", f"{spliceamt}", force_feerate=True)
bitcoind.generate_block(6, wait_for_mempool=1)
l2.daemon.wait_for_log(r'lightningd, splice_locked clearing inflights')
p1 = only_one(l1.rpc.listpeerchannels(peer_id=l2.info['id'])['channels'])
p2 = only_one(l2.rpc.listpeerchannels(l1.info['id'])['channels'])
assert 'inflight' not in p1
assert 'inflight' not in p2
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == 2)
wait_for(lambda: len(l1.rpc.listfunds()['channels']) == 1)
end_wallet_balance = Millisatoshi(bkpr_account_balance(l1, 'wallet'))
assert initial_wallet_balance + Millisatoshi(spliceamt * 1000) == end_wallet_balance
@pytest.mark.xfail(strict=True)
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_easy_splice_out_address(node_factory, bitcoind, chainparams):
fundamt = 1000000
l1, l2 = node_factory.line_graph(2, fundamount=fundamt, wait_for_announce=True)
initial_wallet_balance = Millisatoshi(bkpr_account_balance(l1, 'wallet'))
addr = l1.rpc.newaddr()['p2tr']
# Splice out 100k from first channel, putting result less fees into onchain wallet via addres
spliceamt = 100000
l1.rpc.spliceout("*:?", f"{spliceamt}", destination=addr, force_feerate=True)
bitcoind.generate_block(6, wait_for_mempool=1)
l2.daemon.wait_for_log(r'lightningd, splice_locked clearing inflights')
p1 = only_one(l1.rpc.listpeerchannels(peer_id=l2.info['id'])['channels'])
p2 = only_one(l2.rpc.listpeerchannels(l1.info['id'])['channels'])
assert 'inflight' not in p1
assert 'inflight' not in p2
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == 2)
wait_for(lambda: len(l1.rpc.listfunds()['channels']) == 1)
end_wallet_balance = Millisatoshi(bkpr_account_balance(l1, 'wallet'))
assert initial_wallet_balance + Millisatoshi(spliceamt * 1000) == end_wallet_balance
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_easy_splice_out_into_channel(node_factory, bitcoind, chainparams):
fundamt = 1000000
l1, l2, l3 = node_factory.line_graph(3, fundamount=fundamt, wait_for_announce=True)
chan1 = first_channel_id(l1, l2)
chan2 = first_channel_id(l2, l3)
initial_chan1_balance = Millisatoshi(bkpr_account_balance(l2, chan1))
assert initial_chan1_balance == 0
# Splice out 100k from first channel, putting result into channel
spliceamt = 100000
l2.rpc.spliceout(f"{chan2}", f"{spliceamt}", destination=chan1, force_feerate=True)
bitcoind.generate_block(6, wait_for_mempool=1)
l2.daemon.wait_for_log(r'lightningd, splice_locked clearing inflights')
wait_for(lambda: 'inflight' not in only_one(l1.rpc.listpeerchannels()['channels']))
wait_for(lambda: 'inflight' not in only_one(l3.rpc.listpeerchannels()['channels']))
wait_for(lambda: len(l2.rpc.listfunds()['outputs']) == 1)
wait_for(lambda: len(l2.rpc.listfunds()['channels']) == 2)
end_chan1_balance = Millisatoshi(bkpr_account_balance(l2, chan1))
assert initial_chan1_balance + Millisatoshi(spliceamt * 1000) == end_chan1_balance