Files
palladum-lightning/tests/test_splicing.py

589 lines
26 KiB
Python
Raw Normal View History

from fixtures import * # noqa: F401,F403
from pyln.client import RpcError
import pytest
import unittest
import time
from utils import (
sync_blockheight, wait_for, TEST_NETWORK, first_scid, only_one
)
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_splice(node_factory, bitcoind):
l1, l2 = node_factory.line_graph(2, fundamount=1000000, wait_for_announce=True, opts={'experimental-splicing': None})
chan_id = l1.get_channel_id(l2)
# add extra sats to pay fee
funds_result = l1.rpc.fundpsbt("109000sat", "slow", 166, excess_as_change=True)
result = l1.rpc.splice_init(chan_id, 100000, funds_result['psbt'])
result = l1.rpc.splice_update(chan_id, result['psbt'])
assert(result['commitments_secured'] is False)
result = l1.rpc.splice_update(chan_id, result['psbt'])
assert(result['commitments_secured'] is True)
result = l1.rpc.signpsbt(result['psbt'])
result = l1.rpc.splice_signed(chan_id, result['signed_psbt'])
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)
mempool = bitcoind.rpc.getrawmempool(True)
assert result['txid'] in list(mempool.keys())
bitcoind.generate_block(6, wait_for_mempool=1)
l2.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL')
l1.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL')
inv = l2.rpc.invoice(10**2, '3', 'no_3')
l1.rpc.pay(inv['bolt11'])
# Check that the splice doesn't generate a unilateral close transaction
time.sleep(5)
assert l1.db_query("SELECT count(*) as c FROM channeltxs;")[0]['c'] == 0
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_two_chan_splice_in(node_factory, bitcoind):
l1, l2, l3 = node_factory.line_graph(3, fundamount=1000000, wait_for_announce=True, opts={'experimental-splicing': None})
# l2 will splice funds into the channels with l1 and l3 at the same time
chan_id1 = l2.get_channel_id(l1)
chan_id2 = l2.get_channel_id(l3)
# add extra sats to pay fee
funds_result = l2.rpc.fundpsbt("209000sat", "slow", 166, excess_as_change=True)
# Intiate splices to both channels
result = l2.rpc.splice_init(chan_id1, 100000, funds_result['psbt'])
result = l2.rpc.splice_init(chan_id2, 100000, result['psbt']) # start with psbt from first channel
done1 = False
done2 = False
sigs1 = False
sigs2 = False
while not done1 or not done2:
if not done1:
result = l2.rpc.splice_update(chan_id1, result['psbt'])
done1 = result['commitments_secured']
sigs1 = result['signatures_secured']
print("chan 1 " + result['psbt'])
if not done2:
result = l2.rpc.splice_update(chan_id2, result['psbt'])
done2 = result['commitments_secured']
sigs2 = result['signatures_secured']
print("chan 2 " + result['psbt'])
# Due to splice signing order, we may or may not have signatures
# from all peers, but we must have them from one.
print("Sigs1 " + str(sigs1) + ", Sigs2 " + str(sigs2))
assert(sigs1 or sigs2)
# Sign the inputs provided by `fundpsbt`
result = l2.rpc.signpsbt(result['psbt'])
result['psbt'] = result['signed_psbt']
if sigs2:
# If chan2 gave us sigs, start with chan1
result = l2.rpc.splice_signed(chan_id1, result['psbt'])
result = l2.rpc.splice_signed(chan_id2, result['psbt'])
else:
# If chan1 gave us sigs, start with chan2
result = l2.rpc.splice_signed(chan_id2, result['psbt'])
result = l2.rpc.splice_signed(chan_id1, result['psbt'])
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')
inv = l2.rpc.invoice(10**2, '1', 'no_1')
l1.rpc.pay(inv['bolt11'])
inv = l3.rpc.invoice(10**2, '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_splice_rbf(node_factory, bitcoind):
l1, l2 = node_factory.line_graph(2, fundamount=1000000, wait_for_announce=True, opts={'experimental-splicing': None})
chan_id = l1.get_channel_id(l2)
funds_result = l1.rpc.addpsbtoutput(100000)
# Pay with fee by subjtracting 5000 from channel balance
result = l1.rpc.splice_init(chan_id, -105000, funds_result['psbt'])
result = l1.rpc.splice_update(chan_id, result['psbt'])
assert(result['commitments_secured'] is False)
result = l1.rpc.splice_update(chan_id, result['psbt'])
assert(result['commitments_secured'] is True)
result = l1.rpc.splice_signed(chan_id, result['psbt'])
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)
mempool = bitcoind.rpc.getrawmempool(True)
assert result['txid'] in list(mempool.keys())
inv = l2.rpc.invoice(10**2, '1', 'no_1')
l1.rpc.pay(inv['bolt11'])
funds_result = l1.rpc.addpsbtoutput(100000)
# Pay with fee by subjtracting 5000 from channel balance
result = l1.rpc.splice_init(chan_id, -110000, funds_result['psbt'])
result = l1.rpc.splice_update(chan_id, result['psbt'])
assert(result['commitments_secured'] is False)
result = l1.rpc.splice_update(chan_id, result['psbt'])
assert(result['commitments_secured'] is True)
result = l1.rpc.splice_signed(chan_id, result['psbt'])
l2.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_AWAITING_SPLICE')
l1.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_AWAITING_SPLICE')
inv = l2.rpc.invoice(10**2, '2', 'no_2')
l1.rpc.pay(inv['bolt11'])
# Make sure l1 doesn't unilateral close if HTLC hasn't completely settled before deadline.
wait_for(lambda: only_one(l1.rpc.listpeerchannels()['channels'])['htlcs'] == [])
bitcoind.generate_block(6, wait_for_mempool=1)
l2.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL')
l1.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL')
inv = l2.rpc.invoice(10**2, '3', 'no_3')
l1.rpc.pay(inv['bolt11'])
# Check that the splice doesn't generate a unilateral close transaction
time.sleep(5)
assert l1.db_query("SELECT count(*) as c FROM channeltxs;")[0]['c'] == 0
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_splice_nosign(node_factory, bitcoind):
l1, l2 = node_factory.line_graph(2, fundamount=1000000, wait_for_announce=True, opts={'experimental-splicing': None})
chan_id = l1.get_channel_id(l2)
# add extra sats to pay fee
funds_result = l1.rpc.fundpsbt("109000sat", "slow", 166, excess_as_change=True)
result = l1.rpc.splice_init(chan_id, 100000, funds_result['psbt'])
result = l1.rpc.splice_update(chan_id, result['psbt'])
assert(result['commitments_secured'] is False)
result = l1.rpc.splice_update(chan_id, result['psbt'])
assert(result['commitments_secured'] is True)
try:
l1.rpc.splice_signed(chan_id, result['psbt'])
assert(False)
except RpcError as e:
assert(e.error['code'] == 358)
assert(e.error['message'] == "The PSBT is missing a signature. Have you signed it with `signpsbt`?")
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_splice_gossip(node_factory, bitcoind):
l1, l2, l3 = node_factory.line_graph(3, fundamount=1000000, wait_for_announce=True, opts={'experimental-splicing': None})
chan_id = l1.get_channel_id(l2)
pre_splice_scid = first_scid(l1, l2)
# add extra sats to pay fee
funds_result = l1.rpc.fundpsbt("109000sat", "slow", 166, excess_as_change=True)
result = l1.rpc.splice_init(chan_id, 100000, funds_result['psbt'])
result = l1.rpc.splice_update(chan_id, result['psbt'])
assert(result['commitments_secured'] is False)
result = l1.rpc.splice_update(chan_id, result['psbt'])
assert(result['commitments_secured'] is True)
result = l1.rpc.signpsbt(result['psbt'])
result = l1.rpc.splice_signed(chan_id, result['signed_psbt'])
wait_for(lambda: only_one(l2.rpc.listpeerchannels(l1.info['id'])['channels'])['state'] == 'CHANNELD_AWAITING_SPLICE')
wait_for(lambda: only_one(l1.rpc.listpeerchannels(l2.info['id'])['channels'])['state'] == 'CHANNELD_AWAITING_SPLICE')
pytest: fix flake in splice gossip test. We can in fact see the new channel before this line is called: ``` 2025-03-15T12:31:04.1472196Z @pytest.mark.openchannel('v1') 2025-03-15T12:31:04.1472616Z @pytest.mark.openchannel('v2') 2025-03-15T12:31:04.1473317Z @unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') 2025-03-15T12:31:04.1474271Z def test_splice_gossip(node_factory, bitcoind): 2025-03-15T12:31:04.1475078Z l1, l2, l3 = node_factory.line_graph(3, fundamount=1000000, wait_for_announce=True, opts={'experimental-splicing': None}) 2025-03-15T12:31:04.1475781Z 2025-03-15T12:31:04.1476052Z chan_id = l1.get_channel_id(l2) 2025-03-15T12:31:04.1476460Z pre_splice_scid = first_scid(l1, l2) 2025-03-15T12:31:04.1476844Z 2025-03-15T12:31:04.1477134Z # add extra sats to pay fee 2025-03-15T12:31:04.1477741Z funds_result = l1.rpc.fundpsbt("109000sat", "slow", 166, excess_as_change=True) 2025-03-15T12:31:04.1478345Z 2025-03-15T12:31:04.1478765Z result = l1.rpc.splice_init(chan_id, 100000, funds_result['psbt']) 2025-03-15T12:31:04.1479432Z result = l1.rpc.splice_update(chan_id, result['psbt']) 2025-03-15T12:31:04.1479994Z assert(result['commitments_secured'] is False) 2025-03-15T12:31:04.1480584Z result = l1.rpc.splice_update(chan_id, result['psbt']) 2025-03-15T12:31:04.1481089Z assert(result['commitments_secured'] is True) 2025-03-15T12:31:04.1481386Z result = l1.rpc.signpsbt(result['psbt']) 2025-03-15T12:31:04.1481860Z result = l1.rpc.splice_signed(chan_id, result['signed_psbt']) 2025-03-15T12:31:04.1482403Z 2025-03-15T12:31:04.1485960Z wait_for(lambda: only_one(l2.rpc.listpeerchannels(l1.info['id'])['channels'])['state'] == 'CHANNELD_AWAITING_SPLICE') 2025-03-15T12:31:04.1489978Z wait_for(lambda: only_one(l1.rpc.listpeerchannels(l2.info['id'])['channels'])['state'] == 'CHANNELD_AWAITING_SPLICE') 2025-03-15T12:31:04.1490796Z 2025-03-15T12:31:04.1491223Z bitcoind.generate_block(6, wait_for_mempool=result['txid']) 2025-03-15T12:31:04.1491767Z 2025-03-15T12:31:04.1492213Z # l3 will see channel dying, but still consider it OK for 12 blocks. 2025-03-15T12:31:04.1493174Z l3.daemon.wait_for_log(f'gossipd: channel {pre_splice_scid} closing soon due to the funding outpoint being spent') 2025-03-15T12:31:04.1494422Z assert len(l3.rpc.listchannels(short_channel_id=pre_splice_scid)['channels']) == 2 2025-03-15T12:31:04.1495293Z > assert len(l3.rpc.listchannels(source=l1.info['id'])['channels']) == 1 2025-03-15T12:31:04.1495937Z E AssertionError: assert 2 == 1 2025-03-15T12:31:04.1503185Z E + where 2 = len([{'active': True, 'amount_msat': 1000000000, 'base_fee_millisatoshi': 1, 'channel_flags': 1, ...}, {'active': True, 'amount_msat': 1100000000, 'base_fee_millisatoshi': 1, 'channel_flags': 1, ...}]) ``` Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2025-03-17 09:15:32 +10:30
bitcoind.generate_block(5, wait_for_mempool=result['txid'])
# l3 will see channel dying, but still consider it OK for 12 blocks.
l3.daemon.wait_for_log(f'gossipd: channel {pre_splice_scid} closing soon due to the funding outpoint being spent')
assert len(l3.rpc.listchannels(short_channel_id=pre_splice_scid)['channels']) == 2
assert len(l3.rpc.listchannels(source=l1.info['id'])['channels']) == 1
pytest: fix flake in splice gossip test. We can in fact see the new channel before this line is called: ``` 2025-03-15T12:31:04.1472196Z @pytest.mark.openchannel('v1') 2025-03-15T12:31:04.1472616Z @pytest.mark.openchannel('v2') 2025-03-15T12:31:04.1473317Z @unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') 2025-03-15T12:31:04.1474271Z def test_splice_gossip(node_factory, bitcoind): 2025-03-15T12:31:04.1475078Z l1, l2, l3 = node_factory.line_graph(3, fundamount=1000000, wait_for_announce=True, opts={'experimental-splicing': None}) 2025-03-15T12:31:04.1475781Z 2025-03-15T12:31:04.1476052Z chan_id = l1.get_channel_id(l2) 2025-03-15T12:31:04.1476460Z pre_splice_scid = first_scid(l1, l2) 2025-03-15T12:31:04.1476844Z 2025-03-15T12:31:04.1477134Z # add extra sats to pay fee 2025-03-15T12:31:04.1477741Z funds_result = l1.rpc.fundpsbt("109000sat", "slow", 166, excess_as_change=True) 2025-03-15T12:31:04.1478345Z 2025-03-15T12:31:04.1478765Z result = l1.rpc.splice_init(chan_id, 100000, funds_result['psbt']) 2025-03-15T12:31:04.1479432Z result = l1.rpc.splice_update(chan_id, result['psbt']) 2025-03-15T12:31:04.1479994Z assert(result['commitments_secured'] is False) 2025-03-15T12:31:04.1480584Z result = l1.rpc.splice_update(chan_id, result['psbt']) 2025-03-15T12:31:04.1481089Z assert(result['commitments_secured'] is True) 2025-03-15T12:31:04.1481386Z result = l1.rpc.signpsbt(result['psbt']) 2025-03-15T12:31:04.1481860Z result = l1.rpc.splice_signed(chan_id, result['signed_psbt']) 2025-03-15T12:31:04.1482403Z 2025-03-15T12:31:04.1485960Z wait_for(lambda: only_one(l2.rpc.listpeerchannels(l1.info['id'])['channels'])['state'] == 'CHANNELD_AWAITING_SPLICE') 2025-03-15T12:31:04.1489978Z wait_for(lambda: only_one(l1.rpc.listpeerchannels(l2.info['id'])['channels'])['state'] == 'CHANNELD_AWAITING_SPLICE') 2025-03-15T12:31:04.1490796Z 2025-03-15T12:31:04.1491223Z bitcoind.generate_block(6, wait_for_mempool=result['txid']) 2025-03-15T12:31:04.1491767Z 2025-03-15T12:31:04.1492213Z # l3 will see channel dying, but still consider it OK for 12 blocks. 2025-03-15T12:31:04.1493174Z l3.daemon.wait_for_log(f'gossipd: channel {pre_splice_scid} closing soon due to the funding outpoint being spent') 2025-03-15T12:31:04.1494422Z assert len(l3.rpc.listchannels(short_channel_id=pre_splice_scid)['channels']) == 2 2025-03-15T12:31:04.1495293Z > assert len(l3.rpc.listchannels(source=l1.info['id'])['channels']) == 1 2025-03-15T12:31:04.1495937Z E AssertionError: assert 2 == 1 2025-03-15T12:31:04.1503185Z E + where 2 = len([{'active': True, 'amount_msat': 1000000000, 'base_fee_millisatoshi': 1, 'channel_flags': 1, ...}, {'active': True, 'amount_msat': 1100000000, 'base_fee_millisatoshi': 1, 'channel_flags': 1, ...}]) ``` Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2025-03-17 09:15:32 +10:30
# Final one will allow splice announcement to proceed.
bitcoind.generate_block(1)
wait_for(lambda: only_one(l2.rpc.listpeerchannels(l1.info['id'])['channels'])['state'] == 'CHANNELD_NORMAL')
wait_for(lambda: only_one(l1.rpc.listpeerchannels(l2.info['id'])['channels'])['state'] == 'CHANNELD_NORMAL')
post_splice_scid = first_scid(l1, l2)
assert post_splice_scid != pre_splice_scid
# l3 should see the new channel now.
wait_for(lambda: len(l3.rpc.listchannels(short_channel_id=post_splice_scid)['channels']) == 2)
assert len(l3.rpc.listchannels(short_channel_id=pre_splice_scid)['channels']) == 2
bitcoind.generate_block(7)
# The old channel should fall off l3's perspective
wait_for(lambda: l3.rpc.listchannels(short_channel_id=pre_splice_scid)['channels'] == [])
assert len(l3.rpc.listchannels(short_channel_id=post_splice_scid)['channels']) == 2
# Check that the splice doesn't generate a unilateral close transaction
time.sleep(5)
assert l1.db_query("SELECT count(*) as c FROM channeltxs;")[0]['c'] == 0
# Still looks normal from both sides
assert only_one(l1.rpc.listpeerchannels()['channels'])['short_channel_id'] == post_splice_scid
assert only_one(l1.rpc.listpeerchannels()['channels'])['state'] == 'CHANNELD_NORMAL'
assert only_one(l2.rpc.listpeerchannels(l1.info['id'])['channels'])['short_channel_id'] == post_splice_scid
assert only_one(l2.rpc.listpeerchannels(l1.info['id'])['channels'])['state'] == 'CHANNELD_NORMAL'
# Check for channel announcement failure
assert not l1.daemon.is_in_log("invalid local_channel_announcement")
assert not l2.daemon.is_in_log("invalid local_channel_announcement")
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_splice_listnodes(node_factory, bitcoind):
# Here we do a splice but underfund it purposefully
l1, l2 = node_factory.line_graph(2, fundamount=1000000, wait_for_announce=True, opts={'experimental-splicing': None})
chan_id = l1.get_channel_id(l2)
# add extra sats to pay fee
funds_result = l1.rpc.fundpsbt("109000sat", "slow", 166, excess_as_change=True)
result = l1.rpc.splice_init(chan_id, 100000, funds_result['psbt'])
result = l1.rpc.splice_update(chan_id, result['psbt'])
assert(result['commitments_secured'] is False)
result = l1.rpc.splice_update(chan_id, result['psbt'])
assert(result['commitments_secured'] is True)
result = l1.rpc.signpsbt(result['psbt'])
result = l1.rpc.splice_signed(chan_id, result['signed_psbt'])
l2.daemon.wait_for_log(r'CHANNELD_NORMAL to CHANNELD_AWAITING_SPLICE')
l1.daemon.wait_for_log(r'CHANNELD_NORMAL to CHANNELD_AWAITING_SPLICE')
assert len(l1.rpc.listnodes()['nodes']) == 2
assert len(l2.rpc.listnodes()['nodes']) == 2
bitcoind.generate_block(6, wait_for_mempool=1)
l2.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL')
l1.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL')
bitcoind.generate_block(7)
wait_for(lambda: len(l1.rpc.listnodes()['nodes']) == 2)
wait_for(lambda: len(l2.rpc.listnodes()['nodes']) == 2)
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_splice_out(node_factory, bitcoind):
l1, l2 = node_factory.line_graph(2, fundamount=1000000, wait_for_announce=True, opts={'experimental-splicing': None})
chan_id = l1.get_channel_id(l2)
funds_result = l1.rpc.addpsbtoutput(100000)
# Pay with fee by subjtracting 5000 from channel balance
result = l1.rpc.splice_init(chan_id, -105000, funds_result['psbt'])
result = l1.rpc.splice_update(chan_id, result['psbt'])
assert(result['commitments_secured'] is False)
result = l1.rpc.splice_update(chan_id, result['psbt'])
assert(result['commitments_secured'] is True)
result = l1.rpc.splice_signed(chan_id, result['psbt'])
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)
mempool = bitcoind.rpc.getrawmempool(True)
assert result['txid'] in list(mempool.keys())
bitcoind.generate_block(6, wait_for_mempool=1)
l2.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL')
l1.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL')
inv = l2.rpc.invoice(10**2, '3', 'no_3')
l1.rpc.pay(inv['bolt11'])
# Check that the splice doesn't generate a unilateral close transaction
time.sleep(5)
assert l1.db_query("SELECT count(*) as c FROM channeltxs;")[0]['c'] == 0
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_invalid_splice(node_factory, bitcoind):
# Here we do a splice but underfund it purposefully
l1, l2 = node_factory.line_graph(2, fundamount=1000000, wait_for_announce=True, opts={'experimental-splicing': None,
'may_reconnect': True,
'allow_warning': True})
chan_id = l1.get_channel_id(l2)
# We claim to add 100000 but in fact add nothing
result = l1.rpc.splice_init(chan_id, 100000)
with pytest.raises(RpcError) as rpc_error:
result = l1.rpc.splice_update(chan_id, result['psbt'])
assert(result['commitments_secured'] is False)
result = l1.rpc.splice_update(chan_id, result['psbt'])
assert(result['commitments_secured'] is True)
assert rpc_error.value.error["code"] == 357
assert rpc_error.value.error["message"] == "You provided 1000000000msat but committed to 1100000000msat."
# The splicing inflight should not have been left pending in the DB
assert l1.db_query("SELECT count(*) as c FROM channel_funding_inflights;")[0]['c'] == 0
l1.daemon.wait_for_log(r'Restarting channeld after tx_abort on CHANNELD_NORMAL channel')
assert l1.db_query("SELECT count(*) as c FROM channel_funding_inflights;")[0]['c'] == 0
# Now we do a real splice to confirm everything works after restart
funds_result = l1.rpc.fundpsbt("109000sat", "slow", 166, excess_as_change=True)
result = l1.rpc.splice_init(chan_id, 100000, funds_result['psbt'])
result = l1.rpc.splice_update(chan_id, result['psbt'])
assert(result['commitments_secured'] is False)
result = l1.rpc.splice_update(chan_id, result['psbt'])
assert(result['commitments_secured'] is True)
result = l1.rpc.signpsbt(result['psbt'])
result = l1.rpc.splice_signed(chan_id, result['signed_psbt'])
wait_for(lambda: len(list(bitcoind.rpc.getrawmempool(True).keys())) == 1)
mempool = bitcoind.rpc.getrawmempool(True)
assert result['txid'] in list(mempool.keys())
bitcoind.generate_block(6, wait_for_mempool=1)
l2.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL')
l1.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL')
inv = l2.rpc.invoice(10**2, '3', 'no_3')
l1.rpc.pay(inv['bolt11'])
# Check that the splice doesn't generate a unilateral close transaction
time.sleep(5)
assert l1.db_query("SELECT count(*) as c FROM channeltxs;")[0]['c'] == 0
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_commit_crash_splice(node_factory, bitcoind):
# Here we do a normal splice out but force a restart after commiting.
l1, l2 = node_factory.line_graph(2, fundamount=1000000, wait_for_announce=True, opts={'experimental-splicing': None,
'may_reconnect': True})
chan_id = l1.get_channel_id(l2)
result = l1.rpc.splice_init(chan_id, -105000, l1.rpc.addpsbtoutput(100000)['psbt'])
result = l1.rpc.splice_update(chan_id, result['psbt'])
assert(result['commitments_secured'] is False)
result = l1.rpc.splice_update(chan_id, result['psbt'])
assert(result['commitments_secured'] is True)
l1.daemon.wait_for_log(r"Splice initiator: we commit")
l1.restart()
# The splicing inflight should have been left pending in the DB
assert l1.db_query("SELECT count(*) as c FROM channel_funding_inflights;")[0]['c'] == 1
l1.daemon.wait_for_log(r'peer_out WIRE_CHANNEL_REESTABLISH')
l1.daemon.wait_for_log(r'Got reestablish commit=1 revoke=0 inflights: 1, active splices: 1')
l1.daemon.wait_for_log(r'Splice resume check with local_next_funding: sent, remote_next_funding: received, inflights: 1')
l1.daemon.wait_for_log(r'Splice negotation, will not send commit, not recv commit, send signature, recv signature as initiator')
assert l1.db_query("SELECT count(*) as c FROM channel_funding_inflights;")[0]['c'] == 1
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)
bitcoind.generate_block(6, wait_for_mempool=1)
l2.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL')
l1.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL')
time.sleep(5)
assert l1.db_query("SELECT count(*) as c FROM channel_funding_inflights;")[0]['c'] == 0
inv = l2.rpc.invoice(10**2, '3', 'no_3')
l1.rpc.pay(inv['bolt11'])
# Check that the splice doesn't generate a unilateral close transaction
time.sleep(5)
assert l1.db_query("SELECT count(*) as c FROM channeltxs;")[0]['c'] == 0
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_splice_stuck_htlc(node_factory, bitcoind, executor):
l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True, opts={'experimental-splicing': None})
l3.rpc.dev_ignore_htlcs(id=l2.info['id'], ignore=True)
inv = l3.rpc.invoice(10000000, '1', 'no_1')
executor.submit(l1.rpc.pay, inv['bolt11'])
l3.daemon.wait_for_log('their htlc 0 dev_ignore_htlcs')
# Now we should have a stuck invoice between l1 -> l2
chan_id = l1.get_channel_id(l2)
# add extra sats to pay fee
funds_result = l1.rpc.fundpsbt("109000sat", "slow", 166, excess_as_change=True)
result = l1.rpc.splice_init(chan_id, 100000, funds_result['psbt'])
result = l1.rpc.splice_update(chan_id, result['psbt'])
assert(result['commitments_secured'] is False)
result = l1.rpc.splice_update(chan_id, result['psbt'])
assert(result['commitments_secured'] is True)
result = l1.rpc.signpsbt(result['psbt'])
result = l1.rpc.splice_signed(chan_id, result['signed_psbt'])
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)
mempool = bitcoind.rpc.getrawmempool(True)
assert result['txid'] in list(mempool.keys())
bitcoind.generate_block(1, wait_for_mempool=1)
# Don't have l2, l3 reject channel_announcement as too far in future.
sync_blockheight(bitcoind, [l1, l2, l3])
bitcoind.generate_block(5)
l2.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL')
l1.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL')
# Check that the splice doesn't generate a unilateral close transaction
time.sleep(5)
assert l1.db_query("SELECT count(*) as c FROM channeltxs;")[0]['c'] == 0
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_route_by_old_scid(node_factory, bitcoind):
opts = {'experimental-splicing': None, 'may_reconnect': True}
# l1 sometimes talks about pre-splice channels. l2 (being part of the splice) immediately forgets
# the old scid and uses the new one, then complains when l1 talks about it. Which is fine, but
# breaks CI.
l1opts = opts.copy()
l1opts['allow_warning'] = True
l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True, opts=[l1opts, opts, opts])
# Get pre-splice route.
inv = l3.rpc.invoice(10000000, 'test_route_by_old_scid', 'test_route_by_old_scid')
inv2 = l3.rpc.invoice(10000000, 'test_route_by_old_scid2', 'test_route_by_old_scid2')
pytest: fix flake in test_route_by_old_scid ``` 2025-08-14T11:45:41.7353946Z # Now l1 tries to send using old scid: should work 2025-08-14T11:45:41.7354652Z l1.rpc.sendpay(route, inv['payment_hash'], payment_secret=inv['payment_secret']) 2025-08-14T11:45:41.7355321Z > l1.rpc.waitsendpay(inv['payment_hash']) 2025-08-14T11:45:41.7355644Z 2025-08-14T11:45:41.7355791Z tests/test_splicing.py:528: ... 2025-08-14T11:45:41.7383073Z E pyln.client.lightning.RpcError: RPC call failed: method: waitsendpay, payload: {'payment_hash': '7b74fa9f6a889a16ebf89b8a9468302100f6ad50a771bbab2a16de58dcb1a9a4'}, error: {'code': 203, 'message': 'failed: WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS (reply from remote)', 'data': {'created_index': 1, 'id': 1, 'payment_hash': '7b74fa9f6a889a16ebf89b8a9468302100f6ad50a771bbab2a16de58dcb1a9a4', 'groupid': 1, 'destination': '035d2b1192dfba134e10e540875d366ebc8bc353d5aa766b80c090b39c3a5d885d', 'amount_msat': 10000000, 'amount_sent_msat': 10000101, 'created_at': 1755171392, 'status': 'pending', 'erring_index': 2, 'failcode': 16399, 'failcodename': 'WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS', 'erring_node': '035d2b1192dfba134e10e540875d366ebc8bc353d5aa766b80c090b39c3a5d885d', 'erring_channel': '103x2x0', 'erring_direction': 0, 'raw_message': '400f000000000098968000000072'}} ``` Caused by: ``` 2025-08-14T11:45:41.9275961Z lightningd-3 2025-08-14T11:36:32.700Z DEBUG 022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59-chan#1: Expiry cltv too soon 118 < 114 + 5 ``` Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2025-08-15 15:20:32 +09:30
route = l1.rpc.getroute(l3.info['id'], 10000000, 1, cltv=16)['route']
# Do a splice
funds_result = l2.rpc.fundpsbt("109000sat", "slow", 166, excess_as_change=True)
chan_id = l2.get_channel_id(l3)
result = l2.rpc.splice_init(chan_id, 100000, funds_result['psbt'])
result = l2.rpc.splice_update(chan_id, result['psbt'])
assert(result['commitments_secured'] is False)
result = l2.rpc.splice_update(chan_id, result['psbt'])
assert(result['commitments_secured'] is True)
result = l2.rpc.signpsbt(result['psbt'])
result = l2.rpc.splice_signed(chan_id, result['signed_psbt'])
wait_for(lambda: only_one(l2.rpc.listpeerchannels(l3.info['id'])['channels'])['state'] == 'CHANNELD_AWAITING_SPLICE')
bitcoind.generate_block(6, wait_for_mempool=1)
wait_for(lambda: only_one(l2.rpc.listpeerchannels(l3.info['id'])['channels'])['state'] == 'CHANNELD_NORMAL')
# Now l1 tries to send using old scid: should work
l1.rpc.sendpay(route, inv['payment_hash'], payment_secret=inv['payment_secret'])
l1.rpc.waitsendpay(inv['payment_hash'])
# Let's splice again, so the original scid is two behind the times.
l3.fundwallet(200000)
funds_result = l3.rpc.fundpsbt("109000sat", "slow", 166, excess_as_change=True)
chan_id = l3.get_channel_id(l2)
result = l3.rpc.splice_init(chan_id, 100000, funds_result['psbt'])
result = l3.rpc.splice_update(chan_id, result['psbt'])
assert(result['commitments_secured'] is False)
result = l3.rpc.splice_update(chan_id, result['psbt'])
assert(result['commitments_secured'] is True)
result = l3.rpc.signpsbt(result['psbt'])
result = l3.rpc.splice_signed(chan_id, result['signed_psbt'])
wait_for(lambda: only_one(l2.rpc.listpeerchannels(l3.info['id'])['channels'])['state'] == 'CHANNELD_AWAITING_SPLICE')
bitcoind.generate_block(6, wait_for_mempool=1)
wait_for(lambda: only_one(l2.rpc.listpeerchannels(l3.info['id'])['channels'])['state'] == 'CHANNELD_NORMAL')
# Now restart l2, make sure it remembers the original!
l2.restart()
pytest: fix reconnect flake in test_route_by_old_scid We restart l2, then try to connect to l1. But l1 will be trying to reconnect (and it can, since it was initially given l2's address), so it can race us and we end up disconnecting because of simultaneous connect: ``` 2026-01-13T04:06:10.7490260Z > l2.rpc.connect(l1.info['id'], 'localhost', l1.port) 2026-01-13T04:06:10.7490667Z 2026-01-13T04:06:10.7490828Z tests/test_splicing.py:554: ... 2026-01-13T04:06:10.7525780Z > raise RpcError(method, payload, resp['error']) 2026-01-13T04:06:10.7527464Z E pyln.client.lightning.RpcError: RPC call failed: method: connect, payload: {'id': '0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518', 'host': 'localhost', 'port': 46813}, error: {'code': 402, 'message': 'disconnected during connection'} 2026-01-13T04:06:10.7528832Z ... 2026-01-13T04:06:10.9411680Z lightningd-2 2026-01-13T03:59:59.463Z DEBUG 0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518-connectd: Connect OUT 2026-01-13T04:06:10.9412178Z lightningd-2 2026-01-13T03:59:59.463Z DEBUG 0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518-connectd: peer_out WIRE_INIT 2026-01-13T04:06:10.9412551Z lightningd-2 2026-01-13T03:59:59.463Z DEBUG 0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518-connectd: Connect IN 2026-01-13T04:06:10.9412947Z lightningd-2 2026-01-13T03:59:59.464Z DEBUG 0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518-connectd: peer_out WIRE_INIT 2026-01-13T04:06:10.9413342Z lightningd-2 2026-01-13T03:59:59.464Z DEBUG 0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518-connectd: peer_in WIRE_INIT 2026-01-13T04:06:10.9413624Z lightningd-2 2026-01-13T03:59:59.487Z TRACE lightningd: Calling peer_connected hook of plugin chanbackup 2026-01-13T04:06:10.9414057Z lightningd-2 2026-01-13T03:59:59.534Z DEBUG 0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518-connectd: Handed peer, entering loop 2026-01-13T04:06:10.9414451Z lightningd-2 2026-01-13T03:59:59.534Z DEBUG 0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518-connectd: peer_in WIRE_INIT 2026-01-13T04:06:10.9414851Z lightningd-2 2026-01-13T03:59:59.574Z DEBUG 0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518-lightningd: peer reconnected 2026-01-13T04:06:10.9415250Z lightningd-2 2026-01-13T03:59:59.609Z DEBUG 0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518-lightningd: peer_disconnected 2026-01-13T04:06:10.9415527Z lightningd-2 2026-01-13T03:59:59.685Z TRACE lightningd: Calling peer_connected hook of plugin chanbackup 2026-01-13T04:06:10.9415957Z lightningd-2 2026-01-13T03:59:59.687Z DEBUG 0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518-connectd: Handed peer, entering loop 2026-01-13T04:06:10.9416364Z lightningd-2 2026-01-13T03:59:59.757Z DEBUG 0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518-lightningd: peer_disconnected 2026-01-13T04:06:10.9417067Z lightningd-2 2026-01-13T03:59:59.830Z DEBUG 0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518-connectd: Will try reconnect in 4 seconds ``` Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2026-01-13 14:53:26 +10:30
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
l2.rpc.connect(l3.info['id'], 'localhost', l3.port)
wait_for(lambda: only_one(l1.rpc.listpeers()['peers'])['connected'] is True)
l1.rpc.sendpay(route, inv2['payment_hash'], payment_secret=inv2['payment_secret'])
l1.rpc.waitsendpay(inv2['payment_hash'])
pytest: test for splicing while channel is not announced yet. ``` DEBUG lightningd: Got depth change 2->3 for e9e31956f77c3844ee2e6e4607dbfebdee95a9aa549668a7a429b8246a6a29de **BROKEN** lightningd: FATAL SIGNAL 6 (version v25.09-20-g003ba4a) **BROKEN** lightningd: backtrace: common/daemon.c:41 (send_backtrace) 0x619bef20e274 **BROKEN** lightningd: backtrace: common/daemon.c:78 (crashdump) 0x619bef20e408 **BROKEN** lightningd: backtrace: ./signal/../sysdeps/unix/sysv/linux/x86_64/libc_sigaction.c:0 ((null)) 0x7a1ccf24532f **BROKEN** lightningd: backtrace: ./nptl/pthread_kill.c:44 (__pthread_kill_implementation) 0x7a1ccf29eb2c **BROKEN** lightningd: backtrace: ./nptl/pthread_kill.c:78 (__pthread_kill_internal) 0x7a1ccf29eb2c **BROKEN** lightningd: backtrace: ./nptl/pthread_kill.c:89 (__GI___pthread_kill) 0x7a1ccf29eb2c **BROKEN** lightningd: backtrace: ../sysdeps/posix/raise.c:26 (__GI_raise) 0x7a1ccf24527d **BROKEN** lightningd: backtrace: ./stdlib/abort.c:79 (__GI_abort) 0x7a1ccf2288fe **BROKEN** lightningd: backtrace: ./assert/assert.c:96 (__assert_fail_base) 0x7a1ccf22881a **BROKEN** lightningd: backtrace: ./assert/assert.c:105 (__assert_fail) 0x7a1ccf23b516 **BROKEN** lightningd: backtrace: lightningd/peer_control.c:2202 (funding_depth_cb) 0x619bef1ac497 **BROKEN** lightningd: backtrace: lightningd/watch.c:223 (txw_fire) 0x619bef1cfcbf **BROKEN** lightningd: backtrace: lightningd/watch.c:292 (watch_topology_changed) 0x619bef1cffa4 **BROKEN** lightningd: backtrace: lightningd/chaintopology.c:829 (updates_complete) 0x619bef144a8c **BROKEN** lightningd: backtrace: lightningd/chaintopology.c:1047 (get_new_block) 0x619bef14561e ``` Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2025-10-23 10:08:10 +10:30
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
pytest: test for splicing while channel is not announced yet. ``` DEBUG lightningd: Got depth change 2->3 for e9e31956f77c3844ee2e6e4607dbfebdee95a9aa549668a7a429b8246a6a29de **BROKEN** lightningd: FATAL SIGNAL 6 (version v25.09-20-g003ba4a) **BROKEN** lightningd: backtrace: common/daemon.c:41 (send_backtrace) 0x619bef20e274 **BROKEN** lightningd: backtrace: common/daemon.c:78 (crashdump) 0x619bef20e408 **BROKEN** lightningd: backtrace: ./signal/../sysdeps/unix/sysv/linux/x86_64/libc_sigaction.c:0 ((null)) 0x7a1ccf24532f **BROKEN** lightningd: backtrace: ./nptl/pthread_kill.c:44 (__pthread_kill_implementation) 0x7a1ccf29eb2c **BROKEN** lightningd: backtrace: ./nptl/pthread_kill.c:78 (__pthread_kill_internal) 0x7a1ccf29eb2c **BROKEN** lightningd: backtrace: ./nptl/pthread_kill.c:89 (__GI___pthread_kill) 0x7a1ccf29eb2c **BROKEN** lightningd: backtrace: ../sysdeps/posix/raise.c:26 (__GI_raise) 0x7a1ccf24527d **BROKEN** lightningd: backtrace: ./stdlib/abort.c:79 (__GI_abort) 0x7a1ccf2288fe **BROKEN** lightningd: backtrace: ./assert/assert.c:96 (__assert_fail_base) 0x7a1ccf22881a **BROKEN** lightningd: backtrace: ./assert/assert.c:105 (__assert_fail) 0x7a1ccf23b516 **BROKEN** lightningd: backtrace: lightningd/peer_control.c:2202 (funding_depth_cb) 0x619bef1ac497 **BROKEN** lightningd: backtrace: lightningd/watch.c:223 (txw_fire) 0x619bef1cfcbf **BROKEN** lightningd: backtrace: lightningd/watch.c:292 (watch_topology_changed) 0x619bef1cffa4 **BROKEN** lightningd: backtrace: lightningd/chaintopology.c:829 (updates_complete) 0x619bef144a8c **BROKEN** lightningd: backtrace: lightningd/chaintopology.c:1047 (get_new_block) 0x619bef14561e ``` Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2025-10-23 10:08:10 +10:30
def test_splice_unannounced(node_factory, bitcoind):
l1, l2 = node_factory.line_graph(2, fundamount=1000000, wait_for_announce=False, opts={'experimental-splicing': None})
chan_id = l1.get_channel_id(l2)
# add extra sats to pay fee
funds_result = l1.rpc.fundpsbt("109000sat", "slow", 166, excess_as_change=True)
result = l1.rpc.splice_init(chan_id, 100000, funds_result['psbt'])
result = l1.rpc.splice_update(chan_id, result['psbt'])
assert(result['commitments_secured'] is False)
result = l1.rpc.splice_update(chan_id, result['psbt'])
assert(result['commitments_secured'] is True)
result = l1.rpc.signpsbt(result['psbt'])
result = l1.rpc.splice_signed(chan_id, result['signed_psbt'])
l2.daemon.wait_for_log(r'CHANNELD_NORMAL to CHANNELD_AWAITING_SPLICE')
l1.daemon.wait_for_log(r'CHANNELD_NORMAL to CHANNELD_AWAITING_SPLICE')
bitcoind.generate_block(1, wait_for_mempool=1)
l2.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL')
l1.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL')
bitcoind.generate_block(1)
sync_blockheight(bitcoind, [l1, l2])