feat: add FFOR request persistence and witness recovery - #118
Draft
coreyphillips wants to merge 20 commits into
Draft
coreyphillips wants to merge 20 commits into
coreyphillips wants to merge 20 commits into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
016778d264edf6c3f7201199cfdc9162fdc29562from 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
cargo check --lib --locked --offline.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.