Skip to content

feat: GetChannelAttestations RPC + commitment-bundle events (RFC staging)#3

Draft
rsafier wants to merge 6 commits into
mainfrom
rfc/get-channel-attestations
Draft

feat: GetChannelAttestations RPC + commitment-bundle events (RFC staging)#3
rsafier wants to merge 6 commits into
mainfrom
rfc/get-channel-attestations

Conversation

@rsafier
Copy link
Copy Markdown
Owner

@rsafier rsafier commented Apr 20, 2026

Status: review-staging draft on the rsafier fork. Stacks on three prior RFC branches (proto schema + EventKind filter + event emission) and sits on top of rsafier/ldk-node#1, which in turn depends on rsafier/rust-lightning#1.

Stack (view in order)

# Branch What it adds
1 rfc/channel-commitment-proto proto schema: `ChannelPending` / `ChannelReady` / `ChannelClosed` / `ChannelCommitmentUpdated` / `ChannelCommitmentBundle` / `AttestationHtlc` + `EventKind` filter on `SubscribeEvents`. No behavior change.
2 rfc/subscribe-events-filter server honors `EventKind only`. Default excludes commitment events (opt-in). Unit tests included.
3 rfc/channel-commitment-emission emit lifecycle events on the broadcast channel (previously only logged). Emit `ChannelCommitmentUpdated` on pending/ready — crypto bundle initially stubbed.
4 rfc/get-channel-attestations (this PR) `GetChannelAttestations` unary RPC backed by `Node::export_channel_attestation` from the paired ldk-node PR. Full crypto bundle populated. New `ldk-server-cli get-channel-attestations` subcommand.

What this top commit adds specifically

  • `GetChannelAttestations` RPC (unary, request = optional channel_ids filter, response = list of `ChannelCommitmentUpdated`).
  • Server handler calls `node.list_channel_attestations()` or `node.export_channel_attestation(cid)` for the filtered case. Logs per-channel export failures instead of silently dropping them.
  • `ldk-server-cli get-channel-attestations [--channel-id]` subcommand for operational use.
  • Hex-string serde on byte fields (`holder_commitment_tx`, `counterparty_signature`, both funding pubkeys, payment_hash) so the JSON output is readable.
  • Cargo workspace pins transitive `rust-lightning` deps to our fork via `[patch.'https://github.com/lightningdevkit/rust-lightning']` (required because patches only apply at workspace root).

Proof

lightning-attestor regtest harness builds `ldk-server` from this branch in Docker, drives six scenarios, and its Go extractor passes independent BIP-143 counterparty-sig verification on every one. This is the first stack where LDK parity with LND/CLN attestation is achievable.

rsafier added 6 commits April 19, 2026 20:44
Extends events.proto with:
 - ChannelPending / ChannelReady / ChannelClosed lifecycle events (tags 8-10)
   covering the scope of lightningdevkit#134.
 - ChannelCommitmentUpdated (tag 11) carrying a ChannelCommitmentBundle:
   unsigned holder commit tx, counterparty ECDSA signature, both funding
   pubkeys, balances, commit fee, pending HTLCs, and channel_type marker.
 - AttestationHtlc record for per-HTLC fields on the bundle.

Extends api.proto SubscribeEventsRequest with a repeated EventKind `only`
filter and a new EventKind enum (PAYMENT / CHANNEL_LIFECYCLE /
CHANNEL_COMMITMENT). Default behavior excludes CHANNEL_COMMITMENT because
its rate is much higher than the other streams; attestors opt in.

Proto bindings regenerated via RUSTFLAGS="--cfg genproto" cargo build.
Bumps SubscribeEventsRequest from a unit struct to one with a field, so
the two in-repo call sites use ::default() instead of `{}`.

No emission or filter plumbing in this change — purely the schema.

Motivates the attestation bundle via the RFC:
  "include commitment-bundle data on channel-state events (extends lightningdevkit#134)"
Decode the SubscribeEventsRequest body at stream start and apply its
`only` filter to outgoing events:

  - Empty `only` (back-compat default): emit every kind EXCEPT
    EVENT_KIND_CHANNEL_COMMITMENT. Commitment events fire per
    commitment-state update and are opt-in so existing subscribers see
    no rate change.
  - Non-empty `only`: strict allow-list, including CHANNEL_COMMITMENT
    when explicitly requested.

Unknown / future enum values in `only` are ignored rather than treated
as permissive; unknown event variants (proto evolution) default to
allowed so a newer server doesn't silently drop events for clients
running an older filter.

Unit tests cover: default exclusion, commitment-only subscribe, mixed
kinds, and unknown enum values.
Publish ChannelPending / ChannelReady / ChannelClosed on the
SubscribeEvents broadcast channel (previously these were only logged).
This is the lightningdevkit#134 lifecycle-event scope.

On ChannelPending and ChannelReady, additionally publish a
ChannelCommitmentUpdated carrying the per-channel attestation bundle
identified in the RFC. The event is gated behind the opt-in
EVENT_KIND_CHANNEL_COMMITMENT subscription filter introduced in the
preceding commit, so default subscribers see no rate change.

The bundle's cryptographic fields — unsigned holder commit tx, the
counterparty's ECDSA signature, and the two funding pubkeys — are left
empty for now with explicit TODOs. They are populated by the ldk-node
API proposed in the sibling RFC:

    "expose channel attestation data"
    (Node::export_channel_attestation -> ChannelAttestation)

Until that lands, the event carries the identity / capacity / balance
fields derivable from ChannelDetails:
 - channel_id, counterparty_node_id, funding_txid/outnum
 - capacity_sats (channel_value_sats)
 - holder_balance_msat / counterparty_balance_msat
   (using outbound_capacity_msat / inbound_capacity_msat as a proxy
    until the commitment-derived balances are exposed)

A consumer can detect the pre-bundle stub by checking that
holder_commitment_tx is empty.

Per-commitment emission (every state update, not just on lifecycle
transitions) also waits on the ldk-node RFC — it requires an event on
Persist<ChannelMonitor> updates that ldk-node does not currently
expose. The proto schema is ready for it.
Exposes `Node::export_channel_attestation` (from the paired ldk-node fork)
over gRPC for external regulatory-attestation tooling.

Changes:

 - proto: new GetChannelAttestationsRequest / Response under api.proto
   with a ChannelCommitmentUpdated payload per channel, plus the
   corresponding RPC on the LightningNode service.
 - ldk-server: handler that calls node.list_channel_attestations() (or
   filters to specific channel_ids) and serializes each attestation
   bundle with the full crypto payload — unsigned commit tx, DER
   counterparty signature, both funding pubkeys distinguishably, balances,
   pending HTLCs, and commitment number.
 - ldk-server-client: get_channel_attestations() helper + new path const.
 - ldk-server-cli: `ldk-server-cli get-channel-attestations [--channel-id]`
   subcommand, used by the `lightning-attestor` fixture-generation script.

Cargo wiring:
 - ldk-server Cargo.toml and e2e-tests Cargo.toml point at the fork
   `rsafier/ldk-node` branch `rfc/export-channel-attestation`.
 - Workspace Cargo.toml [patch."https://github.com/lightningdevkit/rust-lightning"]
   redirects transitive deps to `rsafier/rust-lightning` branch
   `rfc/channel-keys-id-accessor` (required because the fork's ldk-node
   references the upstream URL; Cargo patches only apply at the workspace
   root).

Stacks on top of rfc/channel-commitment-emission: the event-stream path
continues to ship the stub bundle until a follow-up commit replaces it
with the real export_channel_attestation payload.
Adds `serialize_bytes_hex` helper and applies it to the ChannelCommitmentBundle
and AttestationHtlc byte fields so `ldk-server-cli get-channel-attestations`
emits a readable hex string instead of a JSON array of u8 integers.

Keeps the downstream attestor Go extractor trivial: it just reads the hex
string straight into the ChannelProof artifact without further decoding.
…ations RPC

Instead of filter_map(.ok()) silently dropping failed exports, log the
error via log::warn when iterating all channels, and return an InternalServerError
with the error detail when a specific channel_id was requested.
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