From f2d59af0eca5b0075b34463f622e41b7210b3193 Mon Sep 17 00:00:00 2001 From: jwinterm Date: Thu, 9 Jul 2026 13:35:50 -0400 Subject: [PATCH] docs: mark grin-lws FUNCTIONAL (Phases 1-5 done); refresh build order The rewind crypto, PMMR-index node reads, and the scanner are implemented + tested (unit + end-to-end). Update README status, the build-order checklist (rewind/reads/scanner/reorg done; live-node smoke + backend proxy remain), the main.rs module status, and the startup log. --- README.md | 61 ++++++++++++++++++++++++++++------------------------- src/main.rs | 10 ++++----- 2 files changed, 37 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index bc27ad8..3116043 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,14 @@ It is the Grin analogue of monero-lws: a wallet backend proxies to it exactly th way a backend proxies to the Monero LWS — forward a view credential, serve the stored results back to the wallet. -**Status: SCAFFOLD.** The HTTP surface, DTOs, config, database layer, and the -scanner loop *structure* are real and build. The two chain-crypto seams — block -parsing and rangeproof rewind — are stubbed; wiring them up is the main remaining -milestone (see [Build order](#build-order)). +**Status: FUNCTIONAL.** The view-only rangeproof rewind, the PMMR-index node +reads, and the background scanner (discover / spend-reconcile / reorg / backfill) +are all implemented and tested. The rewind is cross-validated against grin's own +reference `ProofBuilder` and passed an adversarial money-safety review; an +end-to-end test drives the real scanner against a mock grin node (modeled on the +Foreign API v2) over both SQLite and Postgres. Remaining before production: a +smoke test against a live grin node with a funded wallet. See +[Build order](#build-order). [monero-lws]: https://github.com/vtnerd/monero-lws @@ -179,27 +183,26 @@ output-identify helper — `/get_unspent_outs` returns paths directly. Ship in stages; the deployed on-demand `/wallet/grin/scan` keeps serving balance throughout, so nothing here blocks the client refactor. -1. **Scaffold (this repo).** ✅ Routes, DTOs, config, DB layer, scanner loop - structure, migrations, health/readiness. User-API reads already work against - the DB; the store is just empty until the scanner fills it. -2. **Grin node reads.** Implement `grin::GrinNode::get_block` and - `get_tip_height` against the Foreign API v2, and `push_transaction` for - `/submit_raw_tx`. After this, `/height` and broadcast are live. -3. **Rangeproof rewind (the core milestone).** Implement `grin::rewind_output`: - compute the rewind nonce from `rewind_hash` + commitment, rewind the - Bulletproof, and parse the proof message to recover `(value, key_id, - n_child)` — the server-side analog of `grin_recover_output`. Add the - `secp256k1` / bulletproof crypto deps (commented in `Cargo.toml`). -4. **Scanner fill + spend detection.** Wire the `tick` loop: page blocks, rewind - per account, `insert_output` on matches, `mark_spent` on input commitments, - advance `chain_cursor`. -5. **Reorg handling + backfill.** Detect `prev_hash` mismatch, `rollback_to` the - fork, reseek. Backfill from each account's `start_height`. -6. **Backend proxy.** Land `GrinLwsConfig` + `GrinLwsClient` behind the - `grin_lws` feature flag; parity-check against the on-demand path; then retire - the embedded grin-wallet coupling. - -Steps 3–5 are the bulk of the work and are why this is its own project. +1. **Scaffold.** ✅ Routes, DTOs, config, DB layer, migrations, health/readiness. +2. **Rangeproof rewind (the core milestone).** ✅ `grin::rewind_output`: compute + the rewind nonce from `rewind_hash` + commitment, rewind the Bulletproof, and + parse the proof message to recover `(value, key_id, n_child)` — VIEW-ONLY, the + server-side analog of `grin_recover_output`. Cross-validated against grin's + reference `ProofBuilder`; adversarial money-safety review passed. (v3 outputs + only — legacy needs the master secret, same as grin-wallet's view-only scan.) +3. **Grin node reads.** ✅ `get_unspent_outputs` (PMMR-index, with proofs), + `get_outputs` (spend-by-absence), `get_pmmr_indices`, `get_header`, `get_tip`, + `push_transaction` — all against the Foreign API v2, defensively parsed. +4. **Scanner.** ✅ The `tick` loop: page the UTXO set from the cursor, rewind per + caught-up account, `insert_output` on matches, spend-reconcile by absence, + advance the cursor + account scan heights, coinbase-maturity/lock read path. +5. **Reorg + backfill.** ✅ Detect via header-hash mismatch at the checkpoint, + bounded `rollback_to`, reseek; backfill a new account from its birthday index. +6. **Live-node smoke test.** ⏳ Point at a real grin node with a funded wallet and + confirm balance/outputs match grin-wallet's `scan_rewind_hash`. +7. **Backend proxy.** ⏳ Land a `GrinLwsClient` in a wallet backend behind a + feature flag; parity-check against the on-demand path; retire the embedded + grin-wallet coupling. --- @@ -216,10 +219,10 @@ cargo run # binds GRINLWS_BIND (default 127.0.0.1:3480) curl localhost:3480/health ``` -In the scaffold, `/height`, `/submit_raw_tx`, and `/ready` require a reachable -grin node (they return `502` otherwise); `/register`, `/get_balance`, and -`/get_unspent_outs` work against the DB but return empty until the scanner is -implemented; `/health` always works. +`/height`, `/submit_raw_tx`, and `/ready` require a reachable grin node (they +return `502` otherwise). `/register`, `/get_balance`, and `/get_unspent_outs` +read from the DB store, which the background scanner fills once it has a grin +node to page the output set from. `/health` always works. ## Deployment diff --git a/src/main.rs b/src/main.rs index fa6634e..10ea068 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,10 +29,10 @@ //! nothing; grin-lws holds the `rewind_hash` for scanning just as monero-lws //! holds the view key. NEVER a spend key. //! -//! STATUS: SCAFFOLD. Routes, DTOs, config, DB layer, and the scanner loop -//! structure are real; the two chain-crypto seams — block parsing -//! (`grin::GrinNode::get_block`) and rangeproof rewind (`grin::rewind_output`) — -//! are stubbed. See README.md for the build-order plan. +//! STATUS: FUNCTIONAL. The view-only rewind (`grin::rewind_output`), the +//! PMMR-index node reads, and the background scanner (discover / spend-reconcile +//! / reorg / backfill) are implemented + tested. Remaining before production: a +//! smoke test against a live grin node. See README.md. //! //! PUBLIC-CLEAN: all config is env-based with generic loopback defaults. No //! hostnames, operator IPs, secrets, or deploy specifics anywhere in this repo. @@ -94,7 +94,7 @@ async fn main() { .expect("failed to bind grin-lws listener"); tracing::info!( %bind_addr, - "grin-lws listening (SCAFFOLD — scanner block-parse + rewind stubbed)" + "grin-lws listening" ); axum::serve(listener, api::router(state)) .await