mirror of
https://github.com/Such-Software/grin-lws.git
synced 2026-10-17 07:31:34 +02:00
Prepare for public release: generic wording, MIT LICENSE
Make the scaffold standalone and publish-ready: - Rewrite code + README references to the originating backend as a generic "wallet backend" so the service reads as a reusable, run-anywhere LWS. - Add the MIT LICENSE file (matches Cargo.toml) and a short provenance note. - Add repository/readme/keywords/categories metadata to Cargo.toml.
This commit is contained in:
@@ -5,6 +5,11 @@ edition = "2021"
|
|||||||
rust-version = "1.75"
|
rust-version = "1.75"
|
||||||
description = "Grin light-wallet-server: register a view credential, scan the chain in the background, store recognized outputs in a DB, and serve balance/outputs/broadcast/height."
|
description = "Grin light-wallet-server: register a view credential, scan the chain in the background, store recognized outputs in a DB, and serve balance/outputs/broadcast/height."
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
repository = "https://github.com/Such-Software/grin-lws"
|
||||||
|
readme = "README.md"
|
||||||
|
keywords = ["grin", "light-wallet-server", "lws", "mimblewimble", "wallet"]
|
||||||
|
categories = ["cryptography::cryptocurrencies"]
|
||||||
|
# crates.io publish is gated until the rangeproof-rewind crypto lands (scaffold).
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Such Software
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -5,9 +5,9 @@ view credential, run a **background scanner** over the Grin chain, **store**
|
|||||||
recognized outputs (with recovered derivation paths) in a **database**, and
|
recognized outputs (with recovered derivation paths) in a **database**, and
|
||||||
serve balance / unspent-outputs / broadcast / height fast from that store.
|
serve balance / unspent-outputs / broadcast / height fast from that store.
|
||||||
|
|
||||||
It is the Grin analogue of monero-lws, and lets any backend proxy to it exactly
|
It is the Grin analogue of monero-lws: a wallet backend proxies to it exactly the
|
||||||
the way `smirk-backend-core`'s `xmr_wow.rs` proxies to the Monero LWS via
|
way a backend proxies to the Monero LWS — forward a view credential, serve the
|
||||||
`infra/lws/client.rs`.
|
stored results back to the wallet.
|
||||||
|
|
||||||
**Status: SCAFFOLD.** The HTTP surface, DTOs, config, database layer, and the
|
**Status: SCAFFOLD.** The HTTP surface, DTOs, config, database layer, and the
|
||||||
scanner loop *structure* are real and build. The two chain-crypto seams — block
|
scanner loop *structure* are real and build. The two chain-crypto seams — block
|
||||||
@@ -20,12 +20,12 @@ milestone (see [Build order](#build-order)).
|
|||||||
|
|
||||||
## Why this exists (and why it needs a DB)
|
## Why this exists (and why it needs a DB)
|
||||||
|
|
||||||
`smirk-backend-core`'s current `POST /wallet/grin/scan` is an **on-demand,
|
A common wallet-backend approach is an **on-demand, stateless** grin scan: each
|
||||||
stateless proxy**: each request forwards a `rewind_hash` to grin-wallet's
|
request forwards a `rewind_hash` to grin-wallet's `scan_rewind_hash`, which
|
||||||
`scan_rewind_hash`, which rescans the chain and returns matches, **storing
|
rescans the chain and returns matches, **storing nothing**. That works for
|
||||||
nothing**. That works for Grin's small UTXO set, but it is *not* a
|
Grin's small UTXO set, but it is *not* a light-wallet-server — every read
|
||||||
light-wallet-server — every read re-pays the scan cost, and it has no output
|
re-pays the scan cost, and it has no output store to recover derivation paths
|
||||||
store to recover derivation paths from.
|
from.
|
||||||
|
|
||||||
A real LWS — like monero-lws — keeps a **per-account output store** maintained by
|
A real LWS — like monero-lws — keeps a **per-account output store** maintained by
|
||||||
a **background scanner**, so client reads are O(rows) not O(chain), and it can
|
a **background scanner**, so client reads are O(rows) not O(chain), and it can
|
||||||
@@ -145,33 +145,32 @@ chain_cursor(id=1 PK, height, block_hash) -- reorg-safe resume
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## How `smirk-backend-core` proxies to it
|
## How a wallet backend proxies to it
|
||||||
|
|
||||||
Identical wiring to the XMR/WOW LWS (`infra/lws/client.rs`):
|
Identical wiring to a Monero LWS client — forward the view credential, serve the
|
||||||
|
stored results:
|
||||||
|
|
||||||
```
|
```
|
||||||
wallet ──JWT──▶ smirk-backend-core ──private──▶ grin-lws ──▶ grin node
|
wallet ──JWT──▶ wallet backend ──private──▶ grin-lws ──▶ grin node
|
||||||
/api/v1/wallet/grin/scan /get_unspent_outs (Foreign API v2)
|
/wallet/grin/scan /get_unspent_outs (Foreign API v2)
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Add `GrinLwsConfig { lws_url, admin_url?, admin_key? }` in `config.rs`, next
|
1. Add a grin-lws client config (`lws_url`, optional `admin_url` + `admin_key`)
|
||||||
to `LwsConfig`.
|
alongside your existing Monero-LWS config.
|
||||||
2. Add a `GrinLwsClient` in `infra/` mirroring `infra/lws/client.rs` (shared
|
2. Add an HTTP client mirroring your Monero-LWS client (shared `reqwest::Client`,
|
||||||
`reqwest::Client`, timeouts, size-capped hostile-response reads, `Secret`-
|
timeouts, size-capped hostile-response reads, a `Secret`-wrapped admin key).
|
||||||
wrapped admin key).
|
3. Behind a feature flag, have your grin scan endpoint forward the `rewind_hash`
|
||||||
3. Behind a `features.chains.grin_lws` flag, `POST /wallet/grin/scan` forwards
|
to grin-lws `/get_unspent_outs` (returning outputs **with paths**) instead of
|
||||||
the `rewind_hash` to grin-lws `/get_unspent_outs` (returning outputs **with
|
driving grin-wallet `scan_rewind_hash` on-demand.
|
||||||
paths**) instead of driving grin-wallet `scan_rewind_hash` on-demand.
|
|
||||||
4. Register at balance-fetch time by forwarding `rewind_hash` to grin-lws
|
4. Register at balance-fetch time by forwarding `rewind_hash` to grin-lws
|
||||||
`/register` (idempotent), exactly as `fetchLwsBalance` awaits `registerLws`
|
`/register` (idempotent), exactly as you await LWS registration for Monero.
|
||||||
for XMR/WOW.
|
|
||||||
|
|
||||||
**Non-custodial guarantee preserved end to end:** the backend forwards only the
|
**Non-custodial guarantee preserved end to end:** the backend forwards only the
|
||||||
`rewind_hash` and stores nothing; grin-lws holds the `rewind_hash` for scanning
|
`rewind_hash` and stores nothing; grin-lws holds the `rewind_hash` for scanning
|
||||||
just as monero-lws holds the view key. No spend key ever leaves the wallet.
|
just as monero-lws holds the view key. No spend key ever leaves the wallet.
|
||||||
|
|
||||||
When grin-lws ships, the client can **drop** its `grin_identify_output` helper —
|
Once grin-lws is wired in, the wallet client can **drop** any client-side
|
||||||
`/get_unspent_outs` returns paths directly.
|
output-identify helper — `/get_unspent_outs` returns paths directly.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -228,3 +227,12 @@ Run as a private service alongside a grin node and its database. Bind to
|
|||||||
loopback / a private network; expose it only to the proxying backend. Add a
|
loopback / a private network; expose it only to the proxying backend. Add a
|
||||||
readiness probe on `/ready`. Everything is env-configured — no code changes to
|
readiness probe on `/ready`. Everything is env-configured — no code changes to
|
||||||
deploy.
|
deploy.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## License & provenance
|
||||||
|
|
||||||
|
MIT — see [`LICENSE`](LICENSE).
|
||||||
|
|
||||||
|
grin-lws was extracted from the [Smirk](https://smirk.cash) wallet's backend and
|
||||||
|
generalized into a standalone service that any wallet backend can run.
|
||||||
|
|||||||
+6
-5
@@ -1,10 +1,11 @@
|
|||||||
//! HTTP surface — mirrors the monero-lws user API so smirk-backend-core can
|
//! HTTP surface — mirrors the monero-lws user API so a wallet backend can proxy
|
||||||
//! proxy to it exactly as it proxies the XMR/WOW LWS (see `infra/lws/client.rs`).
|
//! to it exactly as it proxies a Monero LWS.
|
||||||
//!
|
//!
|
||||||
//! AUTH: the user API is unauthenticated at THIS layer — grin-lws binds to
|
//! AUTH: the user API is unauthenticated at THIS layer — grin-lws binds to
|
||||||
//! loopback / a private network and trusts smirk-backend-core (which does the
|
//! loopback / a private network and trusts the proxying wallet backend (which
|
||||||
//! JWT auth) as its only caller. The admin API (`/list_accounts`, `/rescan`) is
|
//! does the JWT auth) as its only caller. The admin API (`/list_accounts`,
|
||||||
//! gated by a shared bearer (`GRINLWS_ADMIN_KEY`) and disabled when unset.
|
//! `/rescan`) is gated by a shared bearer (`GRINLWS_ADMIN_KEY`) and disabled
|
||||||
|
//! when unset.
|
||||||
//!
|
//!
|
||||||
//! The `rewind_hash` in every user request is a VIEW credential (public-key
|
//! The `rewind_hash` in every user request is a VIEW credential (public-key
|
||||||
//! derived — it cannot spend). Its request DTOs omit `Debug` so it is never
|
//! derived — it cannot spend). Its request DTOs omit `Debug` so it is never
|
||||||
|
|||||||
+2
-3
@@ -1,8 +1,7 @@
|
|||||||
//! Error type mapped to HTTP responses.
|
//! Error type mapped to HTTP responses.
|
||||||
//!
|
//!
|
||||||
//! Hostile-upstream hygiene (carried over from smirk-backend-core's LWS/grin
|
//! Hostile-upstream hygiene: a node/DB error NEVER interpolates an untrusted
|
||||||
//! clients): a node/DB error NEVER interpolates an untrusted upstream response
|
//! upstream response body into the client-facing message.
|
||||||
//! body into the client-facing message.
|
|
||||||
|
|
||||||
use axum::{http::StatusCode, response::IntoResponse, Json};
|
use axum::{http::StatusCode, response::IntoResponse, Json};
|
||||||
|
|
||||||
|
|||||||
+2
-3
@@ -4,9 +4,8 @@
|
|||||||
//! block outputs (commit + rangeproof), and relay a finalized transaction. This
|
//! block outputs (commit + rangeproof), and relay a finalized transaction. This
|
||||||
//! is the ONLY component that reaches the chain; everything else reads the DB.
|
//! is the ONLY component that reaches the chain; everything else reads the DB.
|
||||||
//!
|
//!
|
||||||
//! Hostile-upstream hygiene (carried from smirk-backend-core's node client):
|
//! Hostile-upstream hygiene: per-request + connect timeouts, a streaming
|
||||||
//! per-request + connect timeouts, a streaming body-size cap, and errors that
|
//! body-size cap, and errors that never interpolate an untrusted node body.
|
||||||
//! never interpolate an untrusted node body.
|
|
||||||
//!
|
//!
|
||||||
//! REWIND (the hard, deferred part): recognizing which of a block's outputs
|
//! REWIND (the hard, deferred part): recognizing which of a block's outputs
|
||||||
//! belong to a registered account means rewinding each output's Bulletproof
|
//! belong to a registered account means rewinding each output's Bulletproof
|
||||||
|
|||||||
+17
-18
@@ -10,25 +10,24 @@
|
|||||||
//!
|
//!
|
||||||
//! WHY A DB
|
//! WHY A DB
|
||||||
//! --------
|
//! --------
|
||||||
//! smirk-backend-core's current `POST /wallet/grin/scan` is an ON-DEMAND,
|
//! A typical wallet backend exposes an ON-DEMAND, STATELESS grin scan: each
|
||||||
//! STATELESS proxy: each request forwards a `rewind_hash` to grin-wallet's
|
//! request forwards a `rewind_hash` to grin-wallet's `scan_rewind_hash`, which
|
||||||
//! `scan_rewind_hash`, which rescans and returns matches, storing NOTHING. That
|
//! rescans and returns matches, storing NOTHING. That is fine for grin's small
|
||||||
//! is fine for grin's small UTXO set but it is NOT a light-wallet-server. A real
|
//! UTXO set but it is NOT a light-wallet-server. A real LWS — like monero-lws —
|
||||||
//! LWS — like monero-lws — keeps a per-account output store maintained by a
|
//! keeps a per-account output store maintained by a background scanner, so reads
|
||||||
//! background scanner, so reads are O(rows) not O(chain). Hence this service
|
//! are O(rows) not O(chain). Hence this service needs a database (the 3 tables in
|
||||||
//! needs a database (the 3 tables in `migrations/`).
|
//! `migrations/`).
|
||||||
//!
|
//!
|
||||||
//! HOW smirk-backend-core PROXIES TO IT
|
//! HOW A WALLET BACKEND PROXIES TO IT
|
||||||
//! ------------------------------------
|
//! ----------------------------------
|
||||||
//! Identical wiring to the XMR/WOW LWS (`infra/lws/client.rs`): the backend adds
|
//! Identical wiring to a Monero LWS client: the backend adds a grin-lws client
|
||||||
//! a `GrinLwsConfig { lws_url, admin_url?, admin_key? }` and a `GrinLwsClient`;
|
//! (`lws_url`, optional `admin_url` + `admin_key`); behind a feature flag, its
|
||||||
//! when the `grin_lws` feature is on, `POST /wallet/grin/scan` forwards the
|
//! grin scan endpoint forwards the `rewind_hash` to this service's
|
||||||
//! `rewind_hash` to this service's `/get_unspent_outs` (and registers via
|
//! `/get_unspent_outs` (and registers via `/register` at balance-fetch time,
|
||||||
//! `/register` at balance-fetch time, idempotently, exactly as `fetchLwsBalance`
|
//! idempotently, exactly as it awaits LWS registration for Monero). Non-custodial
|
||||||
//! awaits `registerLws` for XMR/WOW). Non-custodial guarantee preserved: the
|
//! guarantee preserved: the backend forwards only the `rewind_hash` and stores
|
||||||
//! backend forwards only the `rewind_hash` and stores nothing; grin-lws holds
|
//! nothing; grin-lws holds the `rewind_hash` for scanning just as monero-lws
|
||||||
//! the `rewind_hash` for scanning just as monero-lws holds the view key. NEVER a
|
//! holds the view key. NEVER a spend key.
|
||||||
//! spend key.
|
|
||||||
//!
|
//!
|
||||||
//! STATUS: SCAFFOLD. Routes, DTOs, config, DB layer, and the scanner loop
|
//! STATUS: SCAFFOLD. Routes, DTOs, config, DB layer, and the scanner loop
|
||||||
//! structure are real; the two chain-crypto seams — block parsing
|
//! structure are real; the two chain-crypto seams — block parsing
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
//! A tiny redacting wrapper so secrets (DB URL, node auth, admin key) never leak
|
//! A tiny redacting wrapper so secrets (DB URL, node auth, admin key) never leak
|
||||||
//! into `Debug` output or tracing spans. Mirrors smirk-backend-core's `Secret`.
|
//! into `Debug` output or tracing spans.
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Secret(String);
|
pub struct Secret(String);
|
||||||
|
|||||||
Reference in New Issue
Block a user