Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
7c4e1ef
feat(ffor): verify committed voucher books
coreyphillips Sep 20, 2026
40973f3
feat(ffor): park receiver vouchers through channel recovery
coreyphillips Sep 20, 2026
364d8f9
feat(ffor): share validated variant d protocol primitives
coreyphillips Sep 21, 2026
9ce29d1
feat(ffor): persist authenticated receiver setup and recovery
coreyphillips Sep 21, 2026
2725353
feat(ffor): own receiver quiescence and revalidate voucher evidence
coreyphillips Sep 21, 2026
25d2c0d
feat(ffor): verify witness provisioning and acknowledgement correlation
coreyphillips Sep 21, 2026
11e6ffe
feat(ffor): retain activation evidence and fence channel mutations
coreyphillips Sep 21, 2026
8da701b
feat(ffor): persist receiver activation and reconnect recovery
coreyphillips Sep 21, 2026
1d6ed49
feat(ffor): authenticate witness fetch pages and encrypted records
coreyphillips Sep 21, 2026
f33c5f7
feat(ffor): verify witness plaintext terms and preimages
coreyphillips Sep 21, 2026
f9e879b
feat(ffor): persist cooperative voucher drain and close recovery
coreyphillips Sep 21, 2026
49e4f36
feat(ffor): decrypt authenticated witness records
coreyphillips Sep 21, 2026
8478895
feat(ffor): expose read-only receiver recovery contexts
coreyphillips Sep 21, 2026
dec0df9
Add durable FFOR pre-init admission and synchronous setup
coreyphillips Sep 21, 2026
a20b34f
feat(ffor): drive receiver activation and cooperative close
coreyphillips Sep 21, 2026
7ed8161
feat(ffor): register immutable witnesses before provisioning
coreyphillips Sep 21, 2026
0def887
feat(ffor): protect recovered receipts with the original monitor
coreyphillips Sep 21, 2026
40d105c
feat(ffor): validate exact retained receiver request intent
coreyphillips Sep 21, 2026
016778d
feat(ffor): persist native witness acknowledgement authority
coreyphillips Sep 21, 2026
a00512b
feat(ffor): retain and gate exact receiver invoices
coreyphillips Sep 21, 2026
6f91649
feat(ffor): journal cooperative voucher outcomes
coreyphillips Sep 21, 2026
996f5b0
feat(ffor): reuse a channel after a terminal receiver epoch
coreyphillips Sep 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/workflows/ffor-protocol.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: FFOR protocol primitives

on:
pull_request:
paths:
- 'lightning-ffor/**'
- 'Cargo.toml'
- 'lightning/Cargo.toml'
- '.github/workflows/ffor-protocol.yml'
workflow_dispatch:

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- run: cargo fmt -p lightning-ffor -- --check
- run: cargo test -p lightning-ffor
- run: cargo check -p lightning-ffor --lib --no-default-features
- run: cargo clippy -p lightning-ffor --all-targets -- -D warnings

msrv:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@1.63.0
- run: lightning-ffor/ci/check-msrv.sh

fuzz:
runs-on: ubuntu-latest
timeout-minutes: 15
defaults:
run:
working-directory: lightning-ffor
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
- run: cargo install cargo-fuzz --version 0.13.2 --locked
- run: python3 fuzz/seed_corpus.py
- run: cargo fuzz run wire -- -max_len=131072 -max_total_time=30
- run: cargo fuzz run witness -- -max_len=196609 -max_total_time=30
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ resolver = "2"
# as in `ci/ci-tests.sh`.
members = [
"lightning",
"lightning-ffor",
"lightning-types",
"lightning-block-sync",
"lightning-invoice",
Expand Down
638 changes: 638 additions & 0 deletions FFOR.md

Large diffs are not rendered by default.

45 changes: 44 additions & 1 deletion lightning-background-processor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ use lightning::events::EventHandler;
use lightning::events::EventsProvider;
use lightning::events::ReplayEvent;
use lightning::events::{Event, PathFailure};
use lightning::io;
use lightning::util::ser::Writeable;

use lightning::ln::channelmanager::AChannelManager;
Expand Down Expand Up @@ -1080,14 +1081,23 @@ where
log_trace!(logger, "Persisting ChannelManager...");

let fut = async {
let persistence_token = channel_manager.get_cm().capture_ffor_persistence();
kv_store
.write(
CHANNEL_MANAGER_PERSISTENCE_PRIMARY_NAMESPACE,
CHANNEL_MANAGER_PERSISTENCE_SECONDARY_NAMESPACE,
CHANNEL_MANAGER_PERSISTENCE_KEY,
channel_manager.get_cm().encode(),
)
.await
.await?;
channel_manager.get_cm().ffor_persistence_completed(persistence_token).map_err(
|_| {
io::Error::new(
io::ErrorKind::InvalidData,
"FFOR persistence token belongs to another manager",
)
},
)
};
// TODO: Once our MSRV is 1.68 we should be able to drop the Box
let mut fut = Box::pin(fut);
Expand Down Expand Up @@ -1309,6 +1319,7 @@ where
// After we exit, ensure we persist the ChannelManager one final time - this avoids
// some races where users quit while channel updates were in-flight, with
// ChannelMonitor update(s) persisted without a corresponding ChannelManager update.
let persistence_token = channel_manager.get_cm().capture_ffor_persistence();
kv_store
.write(
CHANNEL_MANAGER_PERSISTENCE_PRIMARY_NAMESPACE,
Expand All @@ -1317,6 +1328,12 @@ where
channel_manager.get_cm().encode(),
)
.await?;
channel_manager.get_cm().ffor_persistence_completed(persistence_token).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidData,
"FFOR persistence token belongs to another manager",
)
})?;
if let Some(ref scorer) = scorer {
kv_store
.write(
Expand Down Expand Up @@ -1636,12 +1653,22 @@ impl BackgroundProcessor {
}
if channel_manager.get_cm().get_and_clear_needs_persistence() {
log_trace!(logger, "Persisting ChannelManager...");
let persistence_token = channel_manager.get_cm().capture_ffor_persistence();
(kv_store.write(
CHANNEL_MANAGER_PERSISTENCE_PRIMARY_NAMESPACE,
CHANNEL_MANAGER_PERSISTENCE_SECONDARY_NAMESPACE,
CHANNEL_MANAGER_PERSISTENCE_KEY,
channel_manager.get_cm().encode(),
))?;
channel_manager
.get_cm()
.ffor_persistence_completed(persistence_token)
.map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidData,
"FFOR persistence token belongs to another manager",
)
})?;
log_trace!(logger, "Done persisting ChannelManager.");
}

Expand Down Expand Up @@ -1744,12 +1771,21 @@ impl BackgroundProcessor {
// After we exit, ensure we persist the ChannelManager one final time - this avoids
// some races where users quit while channel updates were in-flight, with
// ChannelMonitor update(s) persisted without a corresponding ChannelManager update.
let persistence_token = channel_manager.get_cm().capture_ffor_persistence();
kv_store.write(
CHANNEL_MANAGER_PERSISTENCE_PRIMARY_NAMESPACE,
CHANNEL_MANAGER_PERSISTENCE_SECONDARY_NAMESPACE,
CHANNEL_MANAGER_PERSISTENCE_KEY,
channel_manager.get_cm().encode(),
)?;
channel_manager.get_cm().ffor_persistence_completed(persistence_token).map_err(
|_| {
io::Error::new(
io::ErrorKind::InvalidData,
"FFOR persistence token belongs to another manager",
)
},
)?;
if let Some(ref scorer) = scorer {
kv_store.write(
SCORER_PERSISTENCE_PRIMARY_NAMESPACE,
Expand Down Expand Up @@ -1878,6 +1914,8 @@ mod tests {
use std::time::Duration;
use std::{env, fs};

mod ffor;

const EVENT_DEADLINE: Duration =
Duration::from_millis(5 * (FRESHNESS_TIMER.as_millis() as u64));

Expand Down Expand Up @@ -2064,6 +2102,7 @@ mod tests {
}

struct Persister {
manager_write_gate: Option<ffor::SyncWriteGate>,
graph_error: Option<(std::io::ErrorKind, &'static str)>,
graph_persistence_notifier: Option<SyncSender<()>>,
manager_error: Option<(std::io::ErrorKind, &'static str)>,
Expand All @@ -2075,6 +2114,7 @@ mod tests {
fn new(data_dir: PathBuf) -> Self {
let kv_store = FilesystemStore::new(data_dir);
Self {
manager_write_gate: None,
graph_error: None,
graph_persistence_notifier: None,
manager_error: None,
Expand Down Expand Up @@ -2118,6 +2158,9 @@ mod tests {
&& secondary_namespace == CHANNEL_MANAGER_PERSISTENCE_SECONDARY_NAMESPACE
&& key == CHANNEL_MANAGER_PERSISTENCE_KEY
{
if let Some(gate) = &self.manager_write_gate {
gate.wait()?;
}
if let Some((error, message)) = self.manager_error {
return Err(std::io::Error::new(error, message).into());
}
Expand Down
Loading