Skip to content
Open
Changes from all commits
Commits
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
91 changes: 91 additions & 0 deletions solutions/LP-0017.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Solution: LP-0017 — Whistleblower

**Submitted by:** Tranquil-Flow

## Summary

Whistleblower is a Logos Basecamp app that lets a user pick a document, upload it to Logos Storage, broadcast the `(CID, metadata)` envelope over Logos Delivery, and (optionally) anchor the CID on a LEZ registry. A separate permissionless CLI tool batch-anchors accumulated CIDs from the Delivery topic without coordination with the publisher. The on-chain registry uses a **PDA-per-CID** layout — every anchored CID lives in its own program-derived account, so anchor cost is O(1), capacity is unbounded, and idempotency falls out of the default-state check.

## Repository

- **Repo:** <https://github.com/Tranquil-Flow/lp-0017-whistleblower>

## Approach

The system has four components built from a shared trait surface:

1. **Logos Basecamp app** (`ui/`) — Qt6/QML plugin packaged as a portable `.lgx`. Drives upload → Storage, broadcast → Delivery, optional anchor → LEZ via FFI.
2. **Reusable indexing module** (`document-indexing`, Qt-free Rust) — exposes `Publisher`, `StorageClient`, `DeliveryClient`, `RegistryClient` traits. Mock adapter for tests, LEZ adapter for production.
3. **Permissionless batch CLI** (`batch/whistleblower-batch`) — subscribes to the Delivery topic, dedupe via sled-backed ledger, anchors in bounded batches.
4. **LEZ registry program** (`methods/guest/`) — PDA-per-CID storage, idempotent anchors, queryable without a transaction.

### Key design decisions

1. **PDA-per-CID registry storage** — not a single root PDA with a `HashMap<CID, CidRecord>`. We considered the single-PDA approach and rejected it: a single account's data field is capped at ~100 KiB, which bounds registry capacity by design. PDA-per-CID gives O(1) anchor cost regardless of registry size, unbounded capacity, and idempotency-by-default-state-check (no read-modify-write race). Tradeoff: no on-chain enumeration — acceptable because the spec only requires queryability by CID.
2. **Raw `nssa_core` guest** (not `spel-framework`). We tried `spel-framework` first; its `nssa_core/host` feature pulls `bonsai-sdk` into the `riscv32im` build, which fails to cross-compile on macOS arm64 (`ring` Apple Metal). IDL is hand-written; `spel inspect` still consumes it. Filed upstream as a portability bug — see `BUGS_FILED.md`.
3. **LEZ program path** (not zone SDK). The zone SDK currently requires a single designated consensus inscriber — decentralised sequencers haven't shipped. LEZ program is permissionless from day one, which the censorship-resistance brief demands.
4. **Adapter-based indexing module.** The Qt-free Rust core is `dyn`-safe at three trait boundaries (`StorageClient`, `DeliveryClient`, `RegistryClient`). The Basecamp UI plugin and the batch CLI consume it identically, so the batch path can be re-targeted (e.g. a headless Rust adapter against `logos_host`) without touching publishing logic. Tradeoff: real Storage / Delivery integration lives in the UI plugin (uses in-process `LogosAPIClient`); a future headless Rust adapter is sketched in `adapters/logos/README.md`.
5. **Wallet-free upload + broadcast.** Only on-chain anchoring needs a wallet. The publish path satisfies the spec's "without identifying the uploader" requirement.

### Why the Logos stack

The brief is censorship-resistant document publication. Storage gives content-addressed retrieval; Delivery gives broadcast without a central operator; LEZ gives a permissionless on-chain registry anyone can append to. Built on a centralized stack, the publisher's identity leaks through whichever single operator hosts upload + broadcast + indexing — the very property we're avoiding. Logos lets each component live in a different trust domain.

## Success Criteria Checklist

Mirrored from the LP-0017 spec.

- [x] **Functionality 1 — Upload to Logos Storage.** `ui/src/WhistleblowerBackend.cpp::uploadToStorage` via `LogosAPIClient::invokeRemoteMethodAsync("storage_module", "uploadUrl", ...)`.
- [x] **Functionality 2 — Broadcast envelope to Logos Delivery topic.** `ui/src/WhistleblowerBackend.cpp::broadcastEnvelope` to `/lp0017-whistleblower/1/cids/json`.
- [x] **Functionality 3 — Optional on-chain anchor.** `Publisher::anchor_published` (Rust) and `whistleblower_anchor_one` (C FFI) — wired to QML's "Anchor" button.
- [x] **Functionality 4 — Permissionless batch anchor CLI.** `batch/` produces `whistleblower-batch`. Sled-backed dedupe ledger, batch window, idempotent re-anchoring.
- [x] **Functionality 4 idempotency — Re-submit registered CID = no-op.** `process_entry` in `methods/guest/src/bin/whistleblower_registry.rs` uses `AccountPostState::new_claimed_if_default`.
- [x] **Functionality 5 — Registry stores `(CID, metadata_hash, anchor_timestamp)` per doc.** `AnchorEntry` borsh-encoded into one PDA per CID.
- [x] **Functionality 5 — Registry queryable by CID.** `whistleblower_query_by_cid` FFI + `LezRegistryClient::query_by_cid_hash`.
- [x] **Functionality 5 — ≥10 CIDs per batch tx.** 50-CID batch confirmed in `lez_adapter_anchor_50_cids_in_one_tx` (5× headroom).
- [x] **Functionality 6 — Reusable indexing module.** `document-indexing` crate, no Qt deps, three `dyn`-safe trait boundaries.
- [x] **Usability — Basecamp app GUI.** `ui/` Qt6/QML plugin packaged as `dist/whistleblower-plugin.lgx`.
- [x] **Usability — LEZ program IDL via SPEL.** `whistleblower-registry.idl.json` consumed by `spel inspect`.
- [x] **Reliability — Storage retry with backoff.** `Publisher` wraps every adapter call in `with_retry` (5 retries, exponential).
- [x] **Reliability — Delivery dedup.** `DurableDedupeStore` (sled) in `batch::run_batch_loop`.
- [x] **Reliability — Batch resumes from last anchored.** Sled ledger + registry idempotency = safe re-runs from any point.
- [x] **Performance — CU benchmarks.** `BENCHMARKS.md` under `risc0_dev_mode = false`: 50-CID batch ~120ms zkVM executor (~2.5ms/CID amortized), single-CID ~6–12ms.
- [x] **Supportability — Deployed on LEZ devnet.** Per Logos Discord (2026-05-11), local sequencer = LEZ devnet; program exercised under `risc0_dev_mode = false`.
- [x] **Supportability — E2E tests in CI.** `.github/workflows/ci.yml` runs the full workspace + ignored live-LEZ tests against a localnet sequencer.
- [x] **Supportability — README + reproducible E2E demo + narrated video.** `README.md`; `scripts/demo.sh` against `lgs localnet start`; narrated demo at <https://youtu.be/lMu25io5K-k>.
- [x] **Submission — GitHub issues filed for upstream problems.** `BUGS_FILED.md` lists every issue raised + link.

## FURPS Self-Assessment

### Functionality

The submission delivers the full publish flow (upload → broadcast → optional anchor) inside a real Basecamp instance, plus the permissionless batch CLI for third-party anchoring. The on-chain registry is queryable by CID hash without submitting a transaction. The 50-CID batch confirms a single transaction handles 5× the spec's ≥10-per-batch requirement. Out of scope: tag-based search across the registry (no on-chain enumeration by design — single-CID lookup only), and a headless real-Delivery adapter for the batch CLI (only the in-process Basecamp path uses real Delivery; the batch CLI uses a mock Delivery client because real Delivery currently requires Qt remote objects against `logos_host`).

### Usability

End users install the `.lgx` via `lgs basecamp install` or via Basecamp's "Install plugin" → file picker. Onboarding is two steps: pick a document, hit "Publish". Anchoring is an opt-in second click. The reusable indexing module (`document-indexing`) is documented via rustdoc on `Publisher` + the three adapter traits; integrators implement one adapter trait per Logos service they want to back the publisher with. The batch CLI is a single binary with three flags (`--topic`, `--batch-size`, `--dedupe-store-path`).

### Reliability

`Publisher` wraps every adapter call in `with_retry` (5 retries, exponential backoff, surfaces the final error). The batch CLI's dedupe ledger is sled-backed so it survives process restarts; combined with the registry's default-state idempotency, the batch is safe to re-run from any point — re-submitting an already-anchored CID is a no-op at the program level. Failed anchors leave the registry unchanged because the LEZ program rejects transactions atomically before any PDA write. The known limitation is that real-Delivery in the headless batch CLI is mock-only; the UI plugin owns the real Storage / Delivery integration (in-process `LogosAPIClient`).

### Performance

Captured under `risc0_dev_mode = false` and `RISC0_DEV_MODE=0` on the local LEZ devnet (per Logos Discord 2026-05-11, the local sequencer is the LEZ devnet — no separate remote endpoint exists). 50-CID batch: ~120 ms total zkVM executor time, ~2.5 ms/CID amortized. Single-CID anchor: 6–12 ms. Anchor cost is constant per CID regardless of registry size — the PDA-per-CID layout means anchoring CID N+1 is independent of CIDs 1..N. Wall-clock latency is dominated by the 15-second block production interval, not by program compute cost. Full numbers in `BENCHMARKS.md`. Note: anchor transactions use the LEZ Public path which bypasses host-side proof generation; the prover does fire under `RISC0_DEV_MODE=0` for the faucet / wallet-bootstrap path shown in Scene 1 of the demo video.

### Supportability

CI is green on `main`: 24 fast tests + 4 FFI unit tests + 3 ignored live-LEZ adapter tests + 2 ignored live-LEZ FFI smokes. The CI workflow exercises the full workspace including the risc0 zkVM guest, the FFI cdylib, and the LEZ adapter against a localnet sequencer — not just the pure-Rust shared types. Deployment is documented in `DEPLOYMENT.md` with the exact `lgs basecamp install` + `lgs basecamp launch` commands used to verify the plugin in a real Basecamp instance on 2026-05-09. The codebase is split into eight crates plus the UI plugin so future maintainers can swap individual layers (registry program, indexing traits, batch CLI, UI) independently.

## Supporting Materials

- **Narrated demo video:** <https://youtu.be/lMu25io5K-k>
- **CU benchmarks:** <https://github.com/Tranquil-Flow/lp-0017-whistleblower/blob/main/BENCHMARKS.md>
- **Deployment instructions:** <https://github.com/Tranquil-Flow/lp-0017-whistleblower/blob/main/DEPLOYMENT.md>
- **Demo script (terminal walkthrough):** <https://github.com/Tranquil-Flow/lp-0017-whistleblower/blob/main/DEMO.md>
- **Upstream bug reports filed during this build:** <https://github.com/Tranquil-Flow/lp-0017-whistleblower/blob/main/BUGS_FILED.md>
- **Hand-written SPEL-compatible IDL:** <https://github.com/Tranquil-Flow/lp-0017-whistleblower/blob/main/whistleblower-registry.idl.json>

## Terms & Conditions

By submitting this solution, I confirm that I have read and agree to the [Terms & Conditions](../TERMS.md).
Loading