hygiene: track Cargo.lock, drop dead config + error variant, fix doc drift

- .gitignore no longer excludes Cargo.lock; track it (standard for a service crate)
- drop the unused GRINLWS_SCAN_BATCH_BLOCKS config field (the scanner pages by a
  fixed PAGE_MAX, never read this) and its .env.example line
- remove the unused Error::NotImplemented variant and its match arm
- replace the crate-level #![allow(dead_code)] with a targeted allow on
  mark_unspent (the single-commit un-spend counterpart to mark_spent; the reorg
  path un-spends in bulk via rollback_to)
- doc drift: ready() returns 502 when the node is down (not 503); main.rs status
  reflects the functional, in-production service
This commit is contained in:
jwinterm
2026-07-11 12:22:20 -04:00
parent c2625318d2
commit 5e9de16130
9 changed files with 3056 additions and 26 deletions
+1 -2
View File
@@ -16,9 +16,8 @@ GRIN_NODE_URL=http://127.0.0.1:3413/v2/foreign
# Optional basic-auth secret for the node Foreign API (empty = none).
GRIN_NODE_API_SECRET=
# Background scanner tuning.
# Background scanner tuning: how often it polls for new blocks.
GRINLWS_SCAN_POLL_SECS=15
GRINLWS_SCAN_BATCH_BLOCKS=100
# Restore-depth bound: how many days behind the tip a newly registered account
# may start scanning from. 0 = unbounded.
-1
View File
@@ -1,4 +1,3 @@
/target
.env
*.log
Cargo.lock
Generated
+3044
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -235,7 +235,7 @@ pub async fn health() -> Json<HealthResponse> {
})
}
/// Readiness — reaches the grin node (503 when the node is down).
/// Readiness — reaches the grin node (502 when the node is down).
pub async fn ready(State(state): State<Arc<AppState>>) -> Result<Json<HealthResponse>> {
state.node.health_check().await?;
Ok(Json(HealthResponse {
-4
View File
@@ -25,9 +25,6 @@ pub struct Config {
/// How often the background scanner polls for new blocks.
pub scan_poll_secs: u64,
/// How many blocks the scanner processes per batch (bounds memory / a single
/// transaction's size).
pub scan_batch_blocks: u64,
/// How far behind the tip a newly-registered account may start scanning from
/// (restore-depth bound). `0` = unbounded.
pub restore_max_depth_days: u32,
@@ -55,7 +52,6 @@ impl Config {
),
node_foreign_api_secret: Secret::new(env_or("GRIN_NODE_API_SECRET", "")),
scan_poll_secs: parse_or("GRINLWS_SCAN_POLL_SECS", 15),
scan_batch_blocks: parse_or("GRINLWS_SCAN_BATCH_BLOCKS", 100),
restore_max_depth_days: parse_or("GRINLWS_RESTORE_MAX_DEPTH_DAYS", 0) as u32,
admin_key: Secret::new(env_or("GRINLWS_ADMIN_KEY", "")),
}
+4 -7
View File
@@ -18,12 +18,6 @@
//!
//! SECURITY: only view-only data is ever stored. No spend key, no private key.
// Several write helpers (insert_output, mark_spent, set_cursor, rollback_to) are
// exercised only by the scanner, which is a deferred milestone in this scaffold.
// They are real and tested-shaped; allow them to be unused until the scanner
// loop is wired up.
#![allow(dead_code)]
use sqlx::any::AnyPoolOptions;
use sqlx::{AnyPool, Row};
@@ -197,7 +191,10 @@ pub async fn mark_spent(pool: &AnyPool, commit: &str, spent_height: i64) -> Resu
}
/// Un-mark a spend (a reorg brought a previously-spent output back into the
/// unspent set). Self-heals an over-eager spend reconcile.
/// unspent set). Self-heals an over-eager spend reconcile. Retained as the
/// single-commit counterpart to `mark_spent`; the reorg path currently un-spends
/// in bulk via `rollback_to`, so this is not yet called.
#[allow(dead_code)]
pub async fn mark_unspent(pool: &AnyPool, commit: &str) -> Result<()> {
sqlx::query("UPDATE outputs SET spent = 0, spent_height = NULL WHERE \"commit\" = $1")
.bind(commit)
-5
View File
@@ -23,10 +23,6 @@ pub enum Error {
#[error("database error")]
Db(#[from] sqlx::Error),
/// A route that is deliberately stubbed in this scaffold.
#[error("{0} not implemented (scaffold)")]
NotImplemented(&'static str),
}
impl IntoResponse for Error {
@@ -41,7 +37,6 @@ impl IntoResponse for Error {
tracing::error!(error = %e, "database error");
StatusCode::INTERNAL_SERVER_ERROR
}
Error::NotImplemented(_) => StatusCode::NOT_IMPLEMENTED,
};
let message = match &self {
Error::Db(_) => "internal error".to_string(),
+6 -5
View File
@@ -29,10 +29,11 @@
//! nothing; grin-lws holds the `rewind_hash` for scanning just as monero-lws
//! holds the view key. NEVER a spend key.
//!
//! 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.
//! STATUS: FUNCTIONAL and running in production. The view-only rewind
//! (`grin::rewind_output`), the PMMR-index node reads, and the background scanner
//! (discover / spend-reconcile / reorg / backfill) are implemented + tested, and
//! the service has been validated against a live grin node with a funded wallet.
//! 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.
@@ -75,7 +76,7 @@ async fn main() {
.await
.expect("failed to build database pool");
// In production, run `sqlx migrate run` (or embed `sqlx::migrate!()`) against
// DATABASE_URL before serving. Left explicit here so the scaffold starts
// DATABASE_URL before serving. Left explicit here so the service starts
// without a live DB.
let node = GrinNode::new(&config).expect("failed to build grin node client");
-1
View File
@@ -357,7 +357,6 @@ mod integration {
node_foreign_api_url: url,
node_foreign_api_secret: Secret::new(String::new()),
scan_poll_secs: 1,
scan_batch_blocks: 100,
restore_max_depth_days: 0,
admin_key: Secret::new(String::new()),
}