channeld: report the fundee's full post-splice balance to hsmd in msat - #9591
Open
Amperstrand wants to merge 3 commits into
Open
Amperstrand wants to merge 3 commits into
Amperstrand wants to merge 3 commits into
Conversation
Amperstrand
force-pushed
the
splice-fundee-msat
branch
from
September 30, 2026 12:52
5d98433 to
1b7f880
Compare
relative_splice_balance_fundee() wrapped the satoshi-denominated opener_relative/accepter_relative splicing fields in amount_msat() unchanged, but the hsmd_setup_channel push_value field it feeds is an amount_msat: the fundee's post-splice balance reached hsmd under-reported by 1000x. A stock hsmd never notices (it signs whatever it is asked to), but any hsmd/signer implementation that validates the balances reported to it reads the honest post-splice commitment as an overpayment and refuses to sign it, wedging fundee-side splices. Convert with amount_sat_to_msat() instead, matching every other consumer of these fields (amount_msat_add_sat_s64), and fail the peer on a negative contribution, which is never valid here. Add channeld/test/run-splice_fundee_msat.c, which pins byte-exact through the real static function that an X-sat contribution is reported as X*1000 msat (never X msat), and that negative and overflowing contributions fail the peer. Fixes: 4649bcc Changelog-Fixed: channeld: splicing was reporting the fundee's post-splice balance to hsmd in satoshis instead of millisatoshis. Signed-off-by: Amperstrand <amperstrand@users.noreply.github.com>
hsmd_setup_channel's push_value is the fundee's balance at the start of a channel era: at channel open that is the pushed amount, and for a splice it is the fundee's pre-splice balance plus its funding contribution. Reporting a splice-role-selected contribution alone is wrong twice over: - The contribution was selected by SPLICE role, but the fundee is defined by CHANNEL role: the side that did not open the channel. The splice initiator is not necessarily the channel opener, so whenever the channel fundee is reporting -- whether it initiated the splice or accepted an opener-initiated one -- the splice roles are inverted with respect to the channel roles and the channel opener's contribution was reported instead of the fundee's. - A fundee that already held a balance at splice time was under-reported by exactly that balance. Neither defect is caught by the existing tests: stock hsmd signs unconditionally, so no test observes what hsmd was told. Select the contribution by channel opener role and add the fundee's pre-splice balance from the channel view (view[].owed[], an upper bound of the fundee's first post-splice commitment output, since pending HTLCs only reduce it). A negative contribution is a splice-out and subtracts; only an out-of-range result fails the peer. Extend the unit test with the channel/splice role matrix and the pre-splice balance cases, and add test_splice_fundee_with_balance, which routes 500k sat to the fundee before splicing in on top, so the reported balance is exercised on top of a pre-splice balance. Fixes: 4649bcc Changelog-Fixed: splicing: hsmd is now given the fundee's full post-splice balance, not just its funding contribution. Signed-off-by: Amperstrand <amperstrand@users.noreply.github.com>
Amperstrand
force-pushed
the
splice-fundee-msat
branch
from
September 30, 2026 14:43
1b7f880 to
10361ee
Compare
2 of 4 tasks
hsmd_setup_channel's push_value is the fundee's balance at the start of the new channel era, and a fundee may hold HTLCs in flight when the splice is set up. The settled owed[] alone under-reports the fundee in one resolution direction: a fundee-owned HTLC that fails back returns its escrow to the fundee, raising its output above the settled at-setup balance. A signer that validates the reported balance then reads the honest post-splice commitment as an overpayment and refuses it, wedging the splice. Add the HTLCs pending at splice setup and attributable to the fundee (bucketed by owner side, the same bucketing check_balances uses for its pending_htlcs; callers run after check_balances so the set is final) to the total. The other side's pending HTLCs never count. A negative total -- settled + pending + contribution below zero -- remains a genuine over-draw and fails the peer; nothing is wrapped or clamped. Rails: owed + fundee-owned pending + contribution lands exactly (both role shapes, other side's HTLCs excluded; RED against the settled-only total: 400M vs 500M msat), and a cushioned true-negative total refuses. Changelog-Fixed: splicing: the fundee balance reported to hsmd now includes the fundee's pending HTLCs, not just its settled balance. Signed-off-by: Amperstrand <amperstrand@users.noreply.github.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
relative_splice_balance_fundee() computes the push_value handed to hsmd via hsmd_setup_channel when a splice creates a new channel era. It is wrong three ways:
opener_relative/accepter_relativesplicing fields inamount_msat()unchanged, so the value reached hsmd 1000x under-reported.None of this is caught by the existing tests: stock hsmd signs unconditionally, so no test observes what hsmd was told.
Impact: stock hsmd is unaffected (it signs whatever it is asked to); any hsmd/signer implementation that validates the balances reported to it reads the honest post-splice commitment as an overpayment and refuses to sign it, wedging fundee-side splices. Found while running a validating signer implementation against experimental splicing.
Fix: three commits, each independently test-covered. Units conversion (
amount_sat_to_msat, matching every other consumer of these fields, which feedamount_msat_add_sat_s64); then selection by channel opener role plus the fundee's pre-splice settled balance from the channel view (view[].owed[]); then the fundee-owned HTLCs pending at splice setup (bucketed by owner side, the same bucketingcheck_balancesuses; the other side's HTLCs never count). A negative contribution is a splice-out and subtracts; a negative total or an overflowing add is a genuine over-draw and fails the peer. Nothing is ever wrapped or clamped.Tests:
channeld/test/run-splice_fundee_msat.cpins byte-exact through the real static function: the channel/splice role matrix, X-sat -> X*1000 msat (never X msat: the wrap fingerprint), pre-splice balance, splice-out and pending-HTLC cases (fundee-owned HTLCs included, other side's excluded), and refusal of out-of-range and cushioned true-negative totals. It follows thehsmd/test/run-bad-request-close.cpattern of including the daemon.c(with its main renamed) to unit-test statics; channeld had no unit-test home that could otherwise reach this function.test_splice_fundee_with_balanceexercises a splice on top of a fundee that already holds 500k sat; the rest oftests/test_splicing.py(25 tests) passes unchanged with stock hsmd.