Skip to content

feat: add FFOR request persistence and witness recovery - #118

Draft
coreyphillips wants to merge 20 commits into
mainfrom
feat/ffor-protocol
Draft

coreyphillips wants to merge 20 commits into
mainfrom
feat/ffor-protocol

Conversation

@coreyphillips

@coreyphillips coreyphillips commented Sep 20, 2026

Copy link
Copy Markdown
Collaborator

FFOR receiving needs Node to share the native channel engine, authenticate peer messages and preserve witness recovery material across failures. This draft pins the 0.2.5 engine and companion crates to 016778d264edf6c3f7201199cfdc9162fdc29562 from rust-lightning #4, forwards signing through WalletKeysManager, and adds a private lifecycle adapter, bounded transport, encrypted witness recovery store, concrete witness owner and durable receive-request store. Direct dependency pins also apply to downstream Rust consumers.

Tracked in #117. Mobile drafts: Android #1316 and iOS #766.

The private adapter pairs the concrete native manager's authenticated generation with the transport connection token. Accept, ActivateAck, Abort and CloseAck dispatch synchronously before subsequent ordinary HTLC frames. Native errors request disconnection without mailbox fallback. Preparation, advancement, retry lookup, cancellation and close intent delegate to the native lifecycle driver. When native advancement requires commitment proof, the adapter reads the actual ChainMonitor and drops its guard before returning the opaque snapshot and original generation to native authority. It retains no independent lifecycle state and does not interpret Active progress as invoice readiness.

The optional transport preserves the existing LSPS reader, outbox, features and peer callbacks. PeerManager supplies authenticated peer identities. Disconnect or replacement clears queued work. Combined count and byte limits bound each peer and the whole transport. Enqueue checks the original token and capacity atomically; exact backpressure retries deduplicate. The custom drain retains LSPS ordering. Debug output contains only message type and length because lifecycle payloads can contain payment preimages. Witness provisioning and signed fetch requests require shared typed values; raw request bytes cannot bypass trusted setup or mailbox-key validation. Fetch responses remain uncorrelated encrypted input until an owner checks the request, connection and manifest. No inbound witness service is exposed.

The private store creates one encryption key per epoch and separate fetch keys and mailboxes per selected witness. Its non-test binding requires an opaque native recovery context and retained activation acknowledgement. Wallet-seed-derived keys protect secret records and receipt books in separate authenticated-encryption domains. Retries preserve exact keys, ciphertext and signed manifests. Readable bytes after a failed sync do not establish durability: failed writes and restored records require successful byte-identical confirmation. One serialized owner covers both namespaces; concurrent independent owners and rollback detection are unsupported.

New schema 3 records reserve all receipt capacity before material is returned. Creation persists immutable secrets with a pending allocation, the fixed empty receipt book, then the same secrets marked reserved. Interrupted creation resumes with the original material and keeps the full allocation charged. A completed reservation that disappears or becomes corrupt cannot be recreated. Legacy schema 1 and 2 records support historical key operations and acknowledgements but cannot silently acquire a new receipt allocation or provisioning authority. The store keeps each selected witness's first exactly correlated acknowledgement in fixed slots and preserves it across retries.

Receipt retention verifies the selected witness, exact manifest, native AEAD, voucher terms and preimage before saving the signed encrypted core. Unsigned guardian attachments are excluded. Identical cores deduplicate; valid conflicting evidence cannot overwrite the first record. Receipt loads recheck authenticity and durability. Empty slots and successful fetches never imply that a voucher is unpaid. Protected key operations sign fresh fetch requests and decrypt records without exporting private keys. Neither transport nor storage credits a payment or grants current channel authority.

The private witness owner first persists the sidecar and registers its exact immutable witness selection with native authority. Existing native registration requires load-only recovery. A sealed pair binds the actual native witness generation to its transport token. Native stages exact request/manifest correlation and marks an attempt sent only after the bounded typed enqueue succeeds. Queued requests are never resent after drain; explicit timeout retries use a fresh ID. Node stages replacements in a candidate table, preserving the predecessor if the native 64-attempt/tombstone limit refuses admission. Provision and fetch correlation share a separate 64-request and 8 MiB encoded-payload bound without eviction.

ACK handling validates the actual witness, original connection and exact sent request, confirms the protected sidecar write, then asks native to retain its own promise. Pending native persistence keeps correlation intact. Correlated refusal retires the attempt without a promise. Disconnect between the writes retains the first sidecar promise and requires a fresh native attempt. Each layer preserves its own first sufficient promise, which can legitimately have a different request ID after a crash. Recovery joins exact epoch, manifest and witness with adequate retention, then captures a fresh Active context so the latest native barrier follows the metadata reads. Restored managers require another successful write. These progress values report persistence only and grant no invoice or payment authority.

Historical fetches bind each response to the actual witness, original connection, signed request, retained manifest and increasing page cursor. Each encrypted record becomes durable before cursor advancement. Checked pages survive disconnect and uncertain writes. Candidate-only authentication failures and valid conflicting cores are rejected while later valid evidence is retained; after those writes complete, a new traversal can start with a fresh request and nonce. Local corruption and uncertain writes keep the page intact. Completed or empty traversals confer no unpaid status or payment credit. Each authenticated page now retains all valid cores with one fixed-book write; exact retries add no write. This removes repeated full-book authentication and writes per candidate while preserving exact uncertain-write recovery. Large-book recovery performance still needs measurement before production scheduling.

The private receipt-recovery caller joins exact native witness registration to confirmed encrypted evidence, captures a snapshot from the actual ChainMonitor, drops its guard and imports through native authority. Native checks the original funding output, current counter and retained voucher under channel locks. Live and archive-only receipts use the stock monitor path. PendingMonitor and MonitorPersisted report preimage protection only; neither creates receive credit, settlement status or invoice readiness. Uncertain storage cannot reach the monitor, and normal monitor events and durable restore determine completion.

The private receive-request store retains exact client-ID and description bytes, positive fixed amount, selected channel/S and every preparation parameter before native allocation. Its local lookup ID depends only on actual chain/node identity and client ID, so changed retry arguments conflict. A separate authenticated-encryption domain protects each request. The exclusive owner requires successful exact writes and re-confirms restored bytes, preserves failed candidates and predecessors, and never reconstructs missing application intent from native selectors. Native preparation rejoins the exact stored parameters on a genuine S connection before binding an opaque selector. A separate historical recovery call validates every original native parameter without a peer connection, completes a lost selector-binding write against the same protected intent, and grants no advancement or invoice authority. Unbound absence does not allocate anything, and missing native history for a bound record refuses replacement. The initial one-slot schema reserves 4 KiB per record and at most 64 records/256 KiB, including unbound intents, without eviction. It has no invoice, readiness or completion ledger yet.

The production builder leaves FFOR disabled and does not construct the private adapter, witness owner or request store. Native setup, activation, cooperative close and immutable witness registration are present, along with private provisioning and fetch orchestration. Runtime scheduling, authoritative payment outcomes, repeated epochs, settlement interoperability and invoice readiness remain incomplete. There is no feature advertisement. Bindings remain at 0.7.0-rc.66 and both mobile production providers remain unavailable.

Validation

  • All 235 Node library tests pass, including protected witness storage, the concrete provisioning/fetch owner and transport/adapter checks. Receipt fault injection covers all three initialization writes, pending allocation across restart, visible failed updates, missing/corrupt reservations, exact deduplication, valid witness equivocation, quota refusal, legacy handling and concurrent acknowledgement/receipt writes. Four pinned Beignet encrypted records and an independently reproduced equivocation fixture exercise native decryption and evidence preservation.
  • Genuine NodeBuilder fixture tests restore a funded, signed native Active manager and stock monitor from a public seed. They cover sidecar and manager persistence boundaries, stale persistence tokens, immutable registration after restart, missing-sidecar refusal, exact queued retries, fresh timeout identity, authenticated witness correlation, historical recovery after channel removal, bidirectional combined quotas, byte reservation, pagination, backpressure, disconnects and storage failures. Rejected candidates followed by valid records preserve the valid evidence, and witness retrieval emits no payment credit. Two additional tests verify one write for a complete page, deduplication without extra writes, and exact whole-page recovery after failures before or after visibility. Test-only ring encryption uses the retained public epoch key without exposing private sidecar material.
  • Five additional native ACK integration tests cover independent first promises after a real Node restore or disconnect, preservation of the original pending request when native staging reaches capacity, correlated refusal, disconnect after the protected write, and a newer native promise appearing during protected storage reads. Genuine manager writes define completion; a completed old barrier cannot authorize a newly observed ACK.
  • Eighteen request-store tests cover exact retries, native intent mismatch, disconnected lost-response binding without native mutation, uncertain binding recovery and restart, genuine connection binding, missing application/native history, ambiguous binding writes and restart, identity substitution, malformed records, property checks and fixed capacity. Five receipt-recovery tests use the real Node persister and durable restore, covering live and archive-only imports, exact idempotence and storage failures before or after restart without receive credit. No test manufactures a monitor-completion signal.
  • Transport and adapter checks cover stale and foreign generations, disconnect races, shared quotas, synchronous lifecycle error propagation, exact backpressure retries, malformed framing, typed witness messages, payload redaction and LSPS coexistence. Monitor guards release before native calls; generation checks remain inside native and transport authority.
  • Formatting and whitespace checks pass. All-target Clippy completes with existing repository warnings; no FFOR or custom-message-handler source produces a diagnostic. This is not a zero-warning build.
  • An external Rust consumer resolves one native runtime lineage and passes cargo check --lib --locked --offline.
  • The pinned engine passes 156 focused FFOR tests, native no_std and documentation builds. Nine new native ACK tests cover sent correlation, first-promise immutability, generation loss, persistence, fixed capacity and legacy/corruption refusal. Exact-intent tests cover every original field, absence and mismatch, pending barriers, disconnected restart, terminal and archive-only history; all 21 setup-driver tests pass. Receipt-import integration checks cover both funders, delayed and failed monitor writes, restart, queued versus committed failures, historical recovery, original funding after splice and zero ordinary payment credit. Witness registration checks include both funders, durability, reordered exact retries, compact bounded archives, key/mailbox reuse rejection and historical retention. Lifecycle coverage includes both funders, delayed monitor completion, exact activation release, lost acknowledgement recovery, expiry, signed aborts, cooperative drain, close replay and restored Closed proof. The earlier close checkpoint also passed 10 stock reconnect, 21 quiescence and 16 splice tests. The shared protocol passed 95 tests, five doctests, Rust 1.63 std/no_std checks, strict Clippy and seeded fuzz validation.

These are component checks, not end-to-end offline-payment evidence. The Node test build also includes a small existing wallet-test compatibility fix for address parsing and borrowed scripts. No binding release is created. Production scheduling and authoritative settlement outcomes, native epoch reuse, invoice creation, settlement interoperability, process-crash tests and mobile lifecycle validation remain before enabling Receive Offline. This PR remains a draft.

@coreyphillips coreyphillips changed the title feat: add FFOR protocol primitives feat: validate signed FFOR Variant D messages Sep 20, 2026
@coreyphillips coreyphillips changed the title feat: validate signed FFOR Variant D messages feat: prepare Node for shared FFOR protocol validation Sep 21, 2026
@coreyphillips coreyphillips changed the title feat: prepare Node for shared FFOR protocol validation feat: connect native FFOR support and node signing Sep 21, 2026
@coreyphillips coreyphillips changed the title feat: connect native FFOR support and node signing feat: add bounded FFOR receive transport and native 0.2.5 support Sep 21, 2026
@coreyphillips coreyphillips changed the title feat: add bounded FFOR receive transport and native 0.2.5 support feat: add FFOR receive transport and protected witness storage Sep 21, 2026
@coreyphillips coreyphillips changed the title feat: add FFOR receive transport and protected witness storage feat: add FFOR transport and protected witness recovery Sep 21, 2026
@coreyphillips coreyphillips changed the title feat: add FFOR transport and protected witness recovery feat: add native FFOR setup and protected witness recovery Sep 21, 2026
@coreyphillips coreyphillips changed the title feat: add native FFOR setup and protected witness recovery feat: add native FFOR lifecycle and witness recovery storage Sep 21, 2026
@coreyphillips coreyphillips changed the title feat: add native FFOR lifecycle and witness recovery storage feat: add FFOR lifecycle and durable witness recovery Sep 21, 2026
@coreyphillips coreyphillips changed the title feat: add FFOR lifecycle and durable witness recovery feat: add FFOR request persistence and witness recovery Sep 21, 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