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.
This commit is contained in:
jwinterm
2026-07-09 13:35:50 -04:00
parent d839b75a76
commit f2d59af0ec
2 changed files with 37 additions and 34 deletions
+32 -29
View File
@@ -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
+5 -5
View File
@@ -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