Skip to content

openingd: bound funding_satoshis by total bitcoin supply - #9368

Open
morehouse wants to merge 1 commit into
ElementsProject:masterfrom
morehouse:bounds_check_funding_satoshis
Open

openingd: bound funding_satoshis by total bitcoin supply#9368
morehouse wants to merge 1 commit into
ElementsProject:masterfrom
morehouse:bounds_check_funding_satoshis

Conversation

@morehouse

@morehouse morehouse commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

funding_satoshis values above the total bitcoin supply were not rejected during open_channel/accept_channel negotiation and would later cause libwally to fail and openingd to crash during commitment transaction construction.

Reject such funding_satoshis values immediately so that the negotiation terminates gracefully.

The new test covers the v1 funding flow by manually constructing and sending open_channel messages with excessive funding_satoshis values and then completing the funding flow through funding_created and funding_signed.

The dualopend sites are also fixed for consistency, though it's not clear whether the crash can even be reached via the v2 flow, and writing a test for the v2 flow would be significantly more complex than the v1 test.

Note that this PR only sanitizes funding_satoshis values received from a peer -- it may still be possible the node operator to choose a funding_satoshis value above the total bitcoin supply and cause openingd to crash.

Fixes: #9225

@morehouse
morehouse force-pushed the bounds_check_funding_satoshis branch from dc74660 to 0fb3f26 Compare July 31, 2026 02:02
Andezion
Andezion previously approved these changes Aug 3, 2026

@Andezion Andezion left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The core fix is correct - it closes the actual remote-crash vector (issue #9225) cleanly, and the refactor into max_channel_funding() is a genuine improvement over the five duplicated blocks it replaces!

Comment thread openingd/openingd.c
* - otherwise:
* - MUST set `funding_satoshis` to less than 2^24 satoshi.
*/
if (!feature_negotiated(state->our_features,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is leaving setup_channel_funder() and the lightningd-side checks unpatched a deliberate scope decision (only fixing remotely-triggerable crashes)? The PR description explains the dualopend v2-flow uncertainty but doesn't mention these two sites

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I haven't tested what happens if the node operator chooses an absurdly high funding amount, so I'm not sure whether that gets rejected gracefully or by crashing openingd. When fuzzing with smite we generally only detect bugs that can be triggered by an outside peer/attacker.

I've updated the PR description to mention this gap in the PR.

Comment thread openingd/common.h
bool anchors_negotiated(struct feature_set *our_features,
const u8 *their_features);

struct amount_sat max_channel_funding(const struct feature_set *our_features,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be worth moving max_channel_funding() into bitcoin/chainparams.c (or a shared header) so lightningd could reuse it too? Or is the module boundary (openingd vs lightningd) considered a hard line here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally wouldn't insist on keeping it in openingd. The main reason I put it there is because I was focused on fixing the bug that is triggerable by an outside peer.

IMO it makes sense to merge this bug fix first. If folks care enough about the UX when an operator chooses a funding amount more than 21M, this helper function could be moved to a central location and applied to the funder side in a follow-up PR.

Comment thread tests/test_connection.py


@pytest.mark.openchannel('v1')
def test_open_channel_funding_above_max_supply(node_factory, bitcoind):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the new test also send a case with push_msat alone pushed above max_supply (with funding_satoshis at/near the cap) to directly cover the second crash variant from the issue thread, even though it's mathematically implied by the push_msat <= funding_satoshis invariant? (Not required)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, and also added a case that exercises the third variant where both outputs stay under max_supply.

funding_satoshis values above the total bitcoin supply were not rejected
during open_channel/accept_channel negotiation and would later cause
libwally to fail and openingd to crash during commitment transaction
construction.

Reject such funding_satoshis values immediately so that the negotiation
terminates gracefully.

Fixes: ElementsProject#9225
Changelog-Fixed: `openingd` no longer crashes when a peer opens a channel with a `funding_satoshis` value greater than the total bitcoin supply.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

openingd crashes on assertion wally_err == WALLY_OK when peer sends open_channel with funding_satoshis > WALLY_SATOSHI_MAX

2 participants