splice: Turn on multi-channel, dynamic wallet, smart fees
What changed, and why it matters
This commit removes artificial limits in Core Lightning's experimental splicing feature. It now allows splicing multiple channels at once, using wallet funds dynamically, and calculating fees automatically. These are feature unlocks, not a fix for a known security bug. The change is large in scope and removes safety guards, so it increases the attack surface of a complex feature, but there is no direct evidence it introduces an exploitable vulnerability.
Treat as a high-risk feature commit rather than a security patch. Review the splice.c code paths for dynamic wallet and fee handling, run the newly-enabled tests, and monitor for any follow-up fixes or incident reports. No immediate emergency action is warranted based solely on this diff.
Security signals we found
Removal of multiple input-validation guards in a funds-moving code path
Enabling previously-failing integration tests by removing xfail markers
Change affects splicing, which manipulates live channel funding UTXOs and on-chain wallet inputs
No explicit security bug fix or vulnerability disclosure language in commit message
Evidence from the diff
The patch deletes validation rules in plugins/spender/splice.c that previously rejected: (1) dynamic fees paid from on-chain wallet inputs, (2) dynamic wallet funding amounts, and (3) multi-channel splices. It also removes @pytest.mark.xfail(strict=True) from many two-channel splice tests, enabling them. The commit is a feature-enablement change for the splice script subsystem. No CVE, advisory, or vendor security framing is present in the commit or supplied references.
Changed components
plugins/spender/splice.ctests/test_splice.pyCore Lightning splice script RPC/featureInspect captured patch +0 / −56
diff --git a/plugins/spender/splice.c b/plugins/spender/splice.c
index 8ff30651..1bbe2545 100644
--- a/plugins/spender/splice.c
+++ b/plugins/spender/splice.c
@@ -2031,29 +2031,8 @@ validate_splice_cmd(struct splice_cmd *splice_cmd)
{
struct splice_script_result *action;
int paying_fee_count = 0;
- int channels = 0;
for (size_t i = 0; i < tal_count(splice_cmd->actions); i++) {
action = splice_cmd->actions[i];
- /* Taking fee from onchain wallet requires recursive looping
- * since adding more funds adds more input bytes. We don't
- * support it for now. */
- if (action->pays_fee && action->onchain_wallet
- && action->out_ppm)
- return command_fail(splice_cmd->cmd,
- JSONRPC2_INVALID_PARAMS,
- "Don't support dynamic fee being"
- " added to onchain wallet");
- if (action->onchain_wallet && action->out_ppm)
- return command_fail(splice_cmd->cmd,
- JSONRPC2_INVALID_PARAMS,
- "Don't support dynamic wallet"
- " funding amounts for now");
- if (action->pays_fee && action->onchain_wallet
- && !amount_sat_is_zero(action->out_sat))
- return command_fail(splice_cmd->cmd,
- JSONRPC2_INVALID_PARAMS,
- "Don't support wallet funding"
- " being used for fee");
if (action->pays_fee) {
if (paying_fee_count)
return command_fail(splice_cmd->cmd,
@@ -2067,14 +2046,6 @@ validate_splice_cmd(struct splice_cmd *splice_cmd)
JSONRPC2_INVALID_PARAMS,
"Dynamic bitcoin address amounts"
" not supported for now");
- if (action->channel_id) {
- if (channels)
- return command_fail(splice_cmd->cmd,
- JSONRPC2_INVALID_PARAMS,
- "Multi-channel splice not"
- "supported for now");
- channels++;
- }
if (action->bitcoin_address)
return command_fail(splice_cmd->cmd,
JSONRPC2_INVALID_PARAMS,
diff --git a/tests/test_splice.py b/tests/test_splice.py
index ad2e8854..8ae072f9 100644
--- a/tests/test_splice.py
+++ b/tests/test_splice.py
@@ -198,7 +198,6 @@ def test_script_splice_in(node_factory, bitcoind, chainparams):
assert not account_info['account_closed']
-@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')
@@ -234,7 +233,6 @@ def test_script_two_chan_splice_in(node_factory, bitcoind):
l2.rpc.pay(inv['bolt11'])
-@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')
@@ -273,7 +271,6 @@ def test_script_two_chan_splice_out(node_factory, bitcoind):
l2.rpc.pay(inv['bolt11'])
-@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')
@@ -393,7 +390,6 @@ def execute_script(node_factory, bitcoind, script, expected_balances=None, fee_m
verify_chans(nodes, bitcoind, result['txid'], chanids, int(fee), expected_balances, fee_multiplier)
-@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')
@@ -402,7 +398,6 @@ def test_script_two_chan_splice_b(node_factory, bitcoind):
[500000 - 100000, 500000 - 100000])
-@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')
@@ -411,7 +406,6 @@ def test_script_two_chan_splice_c(node_factory, bitcoind):
[500000 + 100000, 500000 - 100000])
-@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')
@@ -420,7 +414,6 @@ def test_script_two_chan_splice_d(node_factory, bitcoind):
[500000 + 100000, 500000 + 100000])
-@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')
@@ -429,7 +422,6 @@ def test_script_two_chan_splice_e(node_factory, bitcoind):
[500000 - 100000, 500000 - 100000])
-@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')
@@ -438,7 +430,6 @@ def test_script_two_chan_splice_f(node_factory, bitcoind):
[500000 - 200000, 500000 + 100000])
-@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')
@@ -447,7 +438,6 @@ def test_script_two_chan_splice_g(node_factory, bitcoind):
[500000 - 200000, 500000 + 100000])
-@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')
@@ -456,7 +446,6 @@ def test_script_two_chan_splice_h(node_factory, bitcoind):
[500000 + 100000, 500000 + 100000])
-@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')
@@ -465,7 +454,6 @@ def test_script_two_chan_splice_ii(node_factory, bitcoind):
[500000 + 100000, 500000 - 100000], [0, -1])
-@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')
@@ -474,7 +462,6 @@ def test_script_two_chan_splice_j(node_factory, bitcoind):
[500000 + 100000, 500000 - 100000], [-1, 0])
-@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')
@@ -483,7 +470,6 @@ def test_script_two_chan_splice_k(node_factory, bitcoind):
[500000 - 10000, 500000 + 1000])
-@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')
@@ -492,7 +478,6 @@ def test_script_two_chan_splice_l(node_factory, bitcoind):
[500000 + 50000, 500000 + 50000], [-0.5, -0.5])
-@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')
@@ -501,7 +486,6 @@ def test_script_two_chan_splice_m(node_factory, bitcoind):
[500000 + 100000, 500000 + 100000])
-@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')
@@ -510,7 +494,6 @@ def test_script_two_chan_splice_n(node_factory, bitcoind):
[500000 // 2, 500000 + 100000])
-@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')
@@ -519,7 +502,6 @@ def test_script_two_chan_splice_oo(node_factory, bitcoind):
[500000 // 2, 500000 + 100000])
-@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')
@@ -528,7 +510,6 @@ def test_script_two_chan_splice_p(node_factory, bitcoind):
[500000 // 2, 500000 + 100000], [-1, 0])
-@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')
@@ -537,7 +518,6 @@ def test_script_two_chan_splice_q(node_factory, bitcoind):
[500000 - 50000, 500000 + 100000], [-1, 0])
-@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')
@@ -546,7 +526,6 @@ def test_script_two_chan_splice_r(node_factory, bitcoind):
[500000 - 100000, 500000 + 100000])
-@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')
@@ -555,7 +534,6 @@ def test_script_two_chan_splice_s(node_factory, bitcoind):
[500000 - 50000, 500000 + 100000], [-1, 0])
-@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')
@@ -564,7 +542,6 @@ def test_script_two_chan_splice_t(node_factory, bitcoind):
[500000 - 50000, 500000 + 100000], [-1, 0])
-@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')
@@ -573,7 +550,6 @@ def test_script_two_chan_splice_u(node_factory, bitcoind):
[500000 - 50000, 500000 + 100000])
-@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')
@@ -582,7 +558,6 @@ def test_script_two_chan_splice_v(node_factory, bitcoind):
[500000 - 100000, 500000 + 100000])
-@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')
@@ -591,7 +566,6 @@ def test_script_two_chan_splice_x(node_factory, bitcoind):
[500000 + 50000, 500000 - 100000], [-0.5, 0])
-@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')
@@ -600,7 +574,6 @@ def test_script_two_chan_splice_y(node_factory, bitcoind):
[500000 + 100000, 500000 + 100000])
-@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')
Why this scored 26/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.