Skip to content

feat(cln): add expose_private_channels config option#1758

Merged
thesimplekid merged 4 commits intocashubtc:mainfrom
4xvgal:feat/cln-expose-private-channels
Mar 25, 2026
Merged

feat(cln): add expose_private_channels config option#1758
thesimplekid merged 4 commits intocashubtc:mainfrom
4xvgal:feat/cln-expose-private-channels

Conversation

@4xvgal
Copy link
Contributor

@4xvgal 4xvgal commented Mar 22, 2026

Summary

  • Add expose_private_channels config option to cdk-cln backend (default: false)
  • When enabled, passes exposeprivatechannels: true to CLN's invoice RPC so private channels become route hint candidates in bolt11 invoices
  • Uses call_raw to bypass cln-rpc's typed API limitation (Option<Vec<ShortChannelId>> cannot represent boolean true)
  • Includes integration test that verifies private channels are included as route hint candidates

Context

CLN's default behavior for route hint selection is non-deterministic — it depends on internal heuristics around inbound capacity, channel count, and other factors. In practice, this means mint nodes with private channel inbound liquidity may intermittently fail to receive payments, because CLN omits the private channel route hints that payers need for routing. This option lets mint operators explicitly include private channels as route hint candidates.

See also: ElementsProject/lightning#7601 for the upstream cln-rpc typing limitation.

Closes #1757

CHANGED

  • Cln::new() now accepts expose_private_channels: bool parameter

ADDED

  • expose_private_channels field in [cln] config section
  • CDK_MINTD_CLN_EXPOSE_PRIVATE_CHANNELS environment variable
  • ClnClient::open_private_channel() for integration tests
  • create_cln_backend_with_options() helper for integration tests
  • Integration test test_expose_private_channels

Test plan

  • cargo test -p cdk-cln passes
  • cargo clippy -p cdk-cln -- -D warnings passes
  • cargo fmt passes
  • Regtest integration test (test_expose_private_channels): opens private channel between CLN-1 and CLN-2, generates 10 invoices with expose_private_channels=true, verifies at least one invoice includes private channel route hint (6/10 in local test)
  • CI integration tests pass (existing CLN tests should not break since default is false)

🤖 Generated with Claude Code

@github-project-automation github-project-automation bot moved this to Backlog in CDK Mar 22, 2026
@4xvgal 4xvgal force-pushed the feat/cln-expose-private-channels branch 2 times, most recently from f792367 to eb3cb38 Compare March 22, 2026 13:47
…tion

Add configuration to pass `exposeprivatechannels: true` to CLN's invoice
RPC, allowing private channel route hints to be included in bolt11 invoices.

Since cln-rpc types this field as `Option<Vec<ShortChannelId>>` which cannot
represent a boolean, `call_raw` is used to bypass the typed API when the
option is enabled.

Includes integration test that opens a private channel between CLN-1 and
CLN-2, then verifies that invoices include the private channel route hint
when the option is enabled.

Closes cashubtc#1757

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@4xvgal 4xvgal force-pushed the feat/cln-expose-private-channels branch from 0b83b0d to 7554685 Compare March 22, 2026 14:48
@4xvgal
Copy link
Contributor Author

4xvgal commented Mar 22, 2026

This PR uses call_raw to bypass a cln-rpc typing limitation where exposeprivatechannels is typed as Option<Vec> and cannot represent boolean true.
I've opened an upstream issue to address the root cause: ElementsProject/lightning#8964. If upstream adds UnionField support for this field, this PR can be simplified to
use the typed call_typed API instead of call_raw.

@4xvgal 4xvgal marked this pull request as ready for review March 22, 2026 14:59
@4xvgal
Copy link
Contributor Author

4xvgal commented Mar 22, 2026

The current test generates 10 invoices and asserts that at least one includes a private channel route hint. In local testing this passed at 6/10, but since CLN selects route hints from 3 candidates (LND-1, LND-2, CLN-2) non-deterministically, there's a ~1.7% chance ((2/3)^10) that all 10 miss the private channel.

Would it be better to loop until the first private channel hint is found, with an upper bound of 100 attempts?

The probability of 100 consecutive misses is (2/3)^100 ≈ 2.5 × 10⁻¹⁸, making it effectively impossible to flake. The test would still exit immediately on first success, so typical runtime stays at 1-3 iterations.

Copy link
Collaborator

@thesimplekid thesimplekid left a comment

Choose a reason for hiding this comment

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

Would it be better to loop until the first private channel hint is found, with an upper bound of 100 attempts?

Yeah I think this makes sense if you saw it fail 4/10 times locally would like to reduce flakiness where we can if we know there is a potential source of it, since we already have some issues with our ci just because its so heavy.

Comment on lines +596 to +612
deschashonly: None,
exposeprivatechannels: None,
};

// cln-rpc types exposeprivatechannels as Option<Vec<ShortChannelId>>
// which cannot represent boolean true. Use call_raw to bypass this
// limitation when expose_private_channels is enabled.
let invoice_response: InvoiceResponse = if self.expose_private_channels {
let mut params = serde_json::to_value(&request).map_err(Error::from)?;
params["exposeprivatechannels"] = serde_json::Value::Bool(true);
cln_client
.call_raw("invoice", &params)
.await
.map_err(Error::from)?
} else {
cln_client.call_typed(&request).await.map_err(Error::from)?
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

Would another option be on the start up of the mint list the cln channels and then pass in the channel ides on the exposeprivatechannels field of the request? I'm not necessarily requesting we do that instead this is probably simpler just trying to understand the api.

Copy link
Contributor Author

@4xvgal 4xvgal Mar 24, 2026

Choose a reason for hiding this comment

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

That would be cleaner from a cdk<-> cln API perspective. however, fetching short cheannel ids only at start up wouldn't detect channels opened after launch.

The root cause is that cln-rpc doesn't expose the needed fields in its tyepd API yet. Once the upstream issue is resolved, we won't need the startup-based approach or the raw JSON workaround in this PR.

For now, I think it makes sense to go with json-raw approach and switch over when the typed API catches up.

@4xvgal
Copy link
Contributor Author

4xvgal commented Mar 24, 2026

Would it be better to loop until the first private channel hint is found, with an upper bound of 100 attempts?

Yeah I think this makes sense if you saw it fail 4/10 times locally would like to reduce flakiness where we can if we know there is a potential source of it, since we already have some issues with our ci just because its so heavy.

Updated. The test now iterates up to 100 times, It breaks on the first match.

@thesimplekid thesimplekid added this to the 0.16.0 milestone Mar 24, 2026
@thesimplekid thesimplekid merged commit c17becd into cashubtc:main Mar 25, 2026
169 of 172 checks passed
@github-project-automation github-project-automation bot moved this from Backlog to Done in CDK Mar 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

cdk-cln: Add exopose_private_channels configuration option for invoice creation

2 participants