Skip to content

deps: consolidate the dependency stack, cln-* 0.7 and tonic 0.14 - #744

Closed
cdecker wants to merge 10 commits into
mainfrom
2026m09-dependency-minimize
Closed

cdecker wants to merge 10 commits into
mainfrom
2026m09-dependency-minimize

Conversation

@cdecker

@cdecker cdecker commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

Dependency consolidation for the client libraries, paired with the corresponding Greenlight
MR — the two must land together, since gl-plugin-internal implements cln-grpc's generated
service trait and both sides have to be on the same tonic.

Lock: 544 → 514 entries, 56 → 34 crates at more than one version, 68 → 38
redundant copies.

cln-* 0.7 and tonic 0.14, in lockstep

cln-grpc 0.7 is built against tonic 0.14, and WrappedNodeServer implements its generated
Node trait using our own tonic's Request/Response/Status. Two tonic majors in one
tree therefore means the trait we implement is not the trait the server expects, so the two
bumps cannot be staged — the cln-* half alone fails with 57 errors.

Carries prost 0.12 → 0.14 and the transitive hyper 0.14 → 1.0 / http 0.2 → 1.0 /
tower 0.4 → 0.5. tonic-build no longer emits prost code (codegen moves to
tonic-prost-build, runtime gains tonic-prost), tonic::body::BoxBody is private,
http-body 1.0 replaces poll_data/poll_trailers with poll_frame, and hyper 1.0 no longer
implements its IO traits for tokio types.

The Node trait also gained 26 methods in cln 0.7; unary calls delegate to the inner server
and the new subscriptions follow the existing ones in being unimplemented pending a public
NotificationStream.

A production bug this surfaced

generate_self_signed_device_cert set IsCa::ExplicitNoCa, and since rcgen 0.13
serialize_request rejects any basicConstraints in a CSR with UnsupportedInCsr. Every
caller that turns those params into a CSR was failing — both device registration paths in
scheduler.rs and the new-device pairing flow
, not only the test that caught it. NoCa
omits the extension and keeps the CSR valid; the certificate is only a vehicle for the params
and key pair here, and is discarded once the CSR is built.

Dependency hygiene

  • picky: gl-client declared three crates from one family at mutually incompatible
    versions (picky 6, which internally uses picky-asn1-x509 0.6, plus direct 0.15 and
    picky-asn1-der 0.4), putting picky-asn1 in the tree three times. That skew was also why
    verify_pairing_data serialised the CSR key to DER and parsed it back — it was crossing
    between two copies of the same types. With matching versions the key is just borrowed.
    Selecting only picky's x509 feature additionally drops http 0.2.
  • Unused deps removed: async-stream, sha256, uuid (gl-client), env_logger
    (gl-plugin), each confirmed by grep. futures was the one not to remove — real, but only
    under #[cfg(test)], so it moved to dev-dependencies.
  • Wildcard "*" specs replaced with workspace dependencies. Cargo.lock unchanged.
  • Python codegen toolchain bumped to grpcio-tools 1.81.1 with declared bounds moved up to
    match the regenerated stubs, keeping the deliberate-bump discipline the pins documented.

One behaviour change worth review

AuthService previously signed only the first body frame, because http-body 0.4's data()
returns one chunk. It now collects the whole body, which is what the signature was meant to
cover.

Rebase note

Rebased onto current main, which had gained the splice support in the meantime. Two
consequences worth calling out, both caught by rebuilding afterwards rather than by the
rebase itself:

  • sha256 was flagged unused and removed; the new splice code calls sha256::digest for
    real, so it is kept. (async-stream and uuid remain genuinely unused.)
  • ListpeerchannelsRequest gained server-side filters in cln 0.7, so the struct literal in
    old_splice_state needed the two new fields. Both left unset, preserving the existing
    list-then-filter behaviour.

Testing

cargo check --all --all-targets clean; 270 lib tests green.

cdecker and others added 10 commits September 9, 2026 16:40
Move the workspace off a set of long-stale majors: cln-grpc, cln-rpc and
cln-plugin 0.4 -> 0.6, env_logger 0.10 -> 0.11, base64 0.21 -> 0.22,
bech32 0.9 -> 0.11, reqwest 0.11 -> 0.12, ring 0.16 -> 0.17,
rustls-pemfile 1 -> 2, rcgen 0.10 -> 0.13 and secp256k1 0.26 -> 0.29,
with the call-site changes each of those majors requires. gl-plugin now
takes cln-plugin from the workspace rather than pinning ^0.1 separately.

`cargo check --workspace` is clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YZoAzNVajT8Udj7XYrurTa
Move PROTOC_TOOLCHAIN and TESTPROTOC_TOOLCHAIN from grpcio-tools 1.78.0
to 1.81.1 and regenerate, keeping the deliberate-bump discipline the
pins already documented.

Generated protobuf code asserts `runtime >= gencode` at import time and
the grpc stubs refuse to load below the grpcio-tools that produced them,
so the declared bounds move up with the stubs: protobuf >= 6.33.5 and
grpcio >= 1.81.1. gl-client-py gained an explicit grpcio dependency; its
stubs have always imported grpc at runtime but only declared it in the
dev group, leaving it to arrive transitively via pyln-grpc-proto.

gl-testing's test.proto stubs were still 4.25.1 gencode because the
`protoc` target used the ambient venv instead of the pinned toolchain,
and its -I. paths only resolved when run from libs/gl-testing rather
than the repo root it is included into. Point it at the same isolated
toolchain as testgrpc so its output no longer depends on who ran it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YZoAzNVajT8Udj7XYrurTa
The exact pins on 24.2 held the test harness two years behind the
released pyln packages and, being the tightest constraint in the tree,
dragged everything resolving alongside them back with it. Relax to
floors at the current release, 26.6.6.

Every pyln.testing.fixtures and pyln.testing.utils symbol gltesting
imports still exists in 26.6.6. Note this also pulls pytest 7 -> 9 and
flask 2 -> 3 for anything sharing the resolution.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YZoAzNVajT8Udj7XYrurTa
cln-grpc 0.7 is built against tonic 0.14, and WrappedNodeServer
implements its generated `Node` trait using our own tonic's Request,
Response and Status types. Two tonic majors in one tree therefore means
the trait we implement is not the trait the server expects, so the cln-*
bump and the tonic bump cannot be staged separately -- attempting the
cln-* half alone fails with 57 errors. Move both, plus prost 0.12 -> 0.14
and the transitive hyper 0.14 -> 1.0 / http 0.2 -> 1.0 / tower 0.4 -> 0.5
that tonic 0.14 brings with it.

Mechanical parts:

- tonic's "tls" feature is now backend-specific; use "tls-ring".
- tonic-build no longer emits prost code. Codegen moves to
  tonic-prost-build and the runtime gains tonic-prost; `compile` is
  `compile_protos`.
- `tonic::body::BoxBody` is private, and `tonic::transport::Body` is
  gone; both are `tonic::body::Body`. The custom tower services
  (AuthService, SignatureContextService, RpcWaitService) are typed on
  http::Request/Response over that body.
- http-body 1.0 replaces poll_data/poll_trailers with poll_frame, so
  StashBody is reimplemented and the body-buffering loop in
  SignatureContextService walks frames while keeping its size check.
- hyper 1.0 no longer implements its IO traits for tokio types, so the
  hsmproxy connector wraps its UnixStream in hyper_util's TokioIo.
- `Status::to_http` is `into_http`.

API drift in cln 0.7 itself:

- The Node trait gained 26 methods (splices, bkpr-report, xkeysend and
  a batch of notification subscriptions). Unary calls delegate to the
  inner server; the new subscriptions follow the existing ones in being
  unimplemented pending a public NotificationStream.
- SendpayRoute made every field optional and added the newer
  `*_out`/`short_channel_id_dir` hop form; we still describe the hop
  the legacy way.
- FeeratesPerkw dropped delayed_to_us and htlc_resolution, gained
  splice, and floor is no longer optional.

One behaviour change worth noting: AuthService signed only the first
body frame, since http-body 0.4's `data()` returns one chunk. It now
collects the whole body, which is what the signature was meant to cover.

`cargo check --workspace --all-targets` is clean and the tree resolves a
single tonic 0.14.6 and prost 0.14.4.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YZoAzNVajT8Udj7XYrurTa
`generate_self_signed_device_cert` set `IsCa::ExplicitNoCa`, which emits
a basicConstraints extension. Since rcgen 0.13 `serialize_request`
rejects any basicConstraints in a CSR and returns `UnsupportedInCsr`, so
every caller that turns these params into a CSR was failing: both device
registration paths in scheduler.rs and the new-device pairing flow, not
only the test that caught it. `NoCa` omits the extension and keeps the
CSR valid; the certificate itself is only a vehicle for the params and
key pair here, and is discarded once the CSR is built.

Also stop asserting on bech32's error wording in the lnurl test. It
checked for "invalid length", which was 0.9's message; 0.11 reports a
checksum failure for that input. Asserting on our own wrapper text keeps
the test meaningful across bech32 upgrades.

Both regressions came in with the dependency modernization, which was
verified with cargo check but not cargo test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YZoAzNVajT8Udj7XYrurTa
gl-client only uses picky's Pem and Csr types, but the default feature
set also enables jose and http_trait_impl, and the latter pulls http 0.2
into a tree that is otherwise entirely on http 1. Selecting just the
x509 feature removes that duplicate along with the unused jose stack.

picky 7 would also work but is still a release candidate, which is not
somewhere to take an X.509 parser for a deduplication.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YZoAzNVajT8Udj7XYrurTa
gl-client pulled three crates from the same family at mutually
incompatible versions: picky 6 (which internally uses picky-asn1-x509
0.6), plus direct picky-asn1-x509 0.15 and picky-asn1-der 0.4. That put
picky-asn1 in the tree three times, picky-asn1-der twice and
picky-asn1-x509 twice.

The version skew also explains the shape of verify_pairing_data: it
serialised the CSR's public key to DER and parsed it back with
picky-asn1-der purely to cross from picky's copy of the types to ours.
picky's PublicKey is a thin wrapper over SubjectPublicKeyInfo and
implements AsRef for it, so with matching versions the key can just be
borrowed and the round-trip disappears along with the picky-asn1-der
dependency.

cargo tree -d now reports no picky duplicates at all.

Also drop a `mut` that became redundant when AuthService moved to
collecting the whole body.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YZoAzNVajT8Udj7XYrurTa
cargo-udeps flags async-stream and uuid as unused in gl-client and
env_logger as unused in gl-plugin; grepping each confirms it.

futures is a different case: it is genuinely used, but only from
`#[cfg(test)]` code, so it moves to dev-dependencies rather than being
removed. That also gives gl-client a dev-dependencies section, which it
did not have before.

sha256 was also flagged, and was genuinely unused when this was first
written -- its only hits were calls to a local `crypto_utils::hkdf_sha256`
rather than the crate. It is kept, because the splice work that has since
landed on main calls `sha256::digest` for real.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YZoAzNVajT8Udj7XYrurTa
gl-client-py and gl-signerproxy declared hex, log and once_cell as `"*"`,
which accepts any future breaking major. Lift those three into
[workspace.dependencies] alongside the anyhow entry that was already
there, and point every member at them.

Beyond removing the wildcards this collapses a spread of spellings for
the same crates -- hex appeared as "*", "0.4", "0.4.3" and log as "*",
"0.4", "^0.4" -- so each now has one place to bump.

Cargo.lock is unchanged; the resolved versions already matched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YZoAzNVajT8Udj7XYrurTa
`ListpeerchannelsRequest` gained server-side `channel_id` and
`short_channel_id` filters in cln 0.7, so the struct literal in
`old_splice_state` no longer compiles. Both are left unset: the call
lists every channel and filters by channel_id in the following
`find`, and changing the query shape is not something a dependency
bump should decide.

Surfaced by rebasing the dependency work onto main, where the splice
support had landed in the meantime.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YZoAzNVajT8Udj7XYrurTa
@cdecker cdecker closed this Sep 9, 2026
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.

1 participant