splice-script: Round channel balances down
What changed, and why it matters
This commit fixes a rounding bug in Core Lightning's splice commands. Previously, when reading a channel balance expressed in milli-satoshis (msat), the code rounded to the nearest whole satoshi. Now it always rounds down. The change removes two test markers that expected the old behavior to fail, suggesting the bug was causing real splice-in/splice-out failures when balances had fractional satoshis.
Treat as a functional bug fix rather than a security vulnerability. Review whether the old rounding behavior could have caused incorrect splice amounts, failed transactions, or inconsistent channel states. If automated splice operations are used, verify behavior with balances containing fractional satoshis after upgrading.
Security signals we found
Behavioral change in monetary parsing: rounding mode changed from nearest to down
Changelog labels the change as a fix for user-facing splice commands
Test expectations changed: xfail markers removed, indicating previously failing real behavior
No explicit security framing by the vendor
Evidence from the diff
The patch adds json_to_msat_to_sat_round_down() in plugins/spender/splice.c and uses it when parsing available_msat in stfu_channels_get_result(). The old helper json_to_msat_to_sat() used amount_msat_to_sat(), which rounds to the nearest satoshi. The new helper uses amount_msat_to_sat_round_down(), which truncates fractional millisatoshis. Two pytest xfail markers in tests/test_splice.py are removed, indicating the affected tests (test_script_splice_in and test_script_splice_msat) now pass. The changelog explicitly calls this a fix for splicein, spliceout, and dev-splice commands where channel balances included partial sats.
Changed components
plugins/spender/splice.ctests/test_splice.pysplicein RPC commandspliceout RPC commanddev-splice RPC commandInspect captured patch +13 / −3
diff --git a/plugins/spender/splice.c b/plugins/spender/splice.c
index 7d0bab26..ad25f386 100644
--- a/plugins/spender/splice.c
+++ b/plugins/spender/splice.c
@@ -604,6 +604,18 @@ static bool json_to_msat_to_sat(const char *buffer, const jsmntok_t *tok,
return amount_msat_to_sat(sat, msat);
}
+static bool json_to_msat_to_sat_round_down(const char *buffer,
+ const jsmntok_t *tok,
+ struct amount_sat *sat)
+{
+ struct amount_msat msat;
+
+ if (!json_to_msat(buffer, tok, &msat))
+ return false;
+ *sat = amount_msat_to_sat_round_down(msat);
+ return true;
+}
+
static struct splice_script_result *make_wallet(struct splice_cmd *splice_cmd)
{
struct splice_script_result *action;
@@ -1971,7 +1983,7 @@ static struct command_result *stfu_channels_get_result(struct command *cmd,
err = json_scan(tmpctx, buf, jchannel,
"{channel_id?:%,available_msat?:%}",
JSON_SCAN(json_to_channel_id, &channel_id),
- JSON_SCAN(json_to_msat_to_sat, &sat));
+ JSON_SCAN(json_to_msat_to_sat_round_down, &sat));
if (err)
errx(1, "Bad stfu_channels.channels %zu: %s",
i, err);
diff --git a/tests/test_splice.py b/tests/test_splice.py
index b98cd02e..39ee8293 100644
--- a/tests/test_splice.py
+++ b/tests/test_splice.py
@@ -196,7 +196,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')
@@ -301,7 +300,6 @@ def test_script_splice_msat(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')
Why this scored 32/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.