Skip to content

Commit e27e41f

Browse files
committed
fix(node): implement reconciliation sweep as durability backstop (#218)
1 parent ad7c2b2 commit e27e41f

5 files changed

Lines changed: 360 additions & 8 deletions

File tree

‎Cargo.lock‎

Lines changed: 6 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎crates/gitlawb-node/src/db/mod.rs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
use std::time::Duration;
2+
13
use anyhow::{Context, Result};
24
use chrono::{DateTime, Utc};
35
use serde::{Deserialize, Serialize};
46
use sqlx::{postgres::PgPoolOptions, PgPool, Row};
5-
use std::time::Duration;
67
use tracing::info;
78
use uuid::Uuid;
89

‎crates/gitlawb-node/src/main.rs‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ mod operator;
1616
mod p2p;
1717
mod pinata;
1818
mod rate_limit;
19+
mod reconciliation;
1920
mod server;
2021
mod state;
2122
mod sync;
@@ -498,6 +499,19 @@ async fn main() -> Result<()> {
498499
info!("auto-sync worker started");
499500
}
500501

502+
// Periodic reconciliation sweep: re-derives pin/seal sets and fills gaps
503+
// so a dropped replication job never means data loss.
504+
{
505+
let db = state.db.clone();
506+
let config = Arc::clone(&state.config);
507+
let http_client = Arc::clone(&state.http_client);
508+
let node_keypair = Arc::clone(&state.node_keypair);
509+
let node_did = state.node_did.clone();
510+
let shutdown_rx = state.subscribe_shutdown();
511+
reconciliation::spawn(db, config, http_client, node_keypair, node_did, shutdown_rx);
512+
info!("reconciliation sweep worker started");
513+
}
514+
501515
// On-chain operator setup: verify stake + spawn heartbeat loop
502516
if !state.config.contract_node_staking.is_empty()
503517
&& !state.config.operator_private_key.is_empty()

‎crates/gitlawb-node/src/metrics.rs‎

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
//! `gitlawb_pack_size_bytes`
1616
//! * a single `gitlawb_info{version, did}` gauge = 1, for joins/dashboards
1717
//! * currently-connected peer count — `gitlawb_peers_connected`
18+
//! * reconciliation sweep gaps found and filled —
19+
//! `gitlawb_reconciliation_gaps_found_total` /
20+
//! `gitlawb_reconciliation_gaps_filled_total`
1821
//!
1922
//! All metrics live in a single process-wide registry initialized by
2023
//! [`init`]. Increment helpers (`record_push`, `record_auth_failure`, ...)
@@ -33,8 +36,8 @@
3336
use std::sync::OnceLock;
3437

3538
use prometheus::{
36-
Encoder, Histogram, HistogramOpts, IntCounterVec, IntGauge, IntGaugeVec, Opts, Registry,
37-
TextEncoder,
39+
Encoder, Histogram, HistogramOpts, IntCounter, IntCounterVec, IntGauge, IntGaugeVec, Opts,
40+
Registry, TextEncoder,
3841
};
3942

4043
/// The single, process-wide metrics registry. Initialized by [`init`].
@@ -51,6 +54,8 @@ static SYNC_PROCESSED: OnceLock<IntCounterVec> = OnceLock::new();
5154
static WEBHOOK_DELIVERIES: OnceLock<IntCounterVec> = OnceLock::new();
5255
static PACK_SIZE: OnceLock<Histogram> = OnceLock::new();
5356
static PEERS_CONNECTED: OnceLock<IntGauge> = OnceLock::new();
57+
static RECONCILIATION_GAPS_FOUND: OnceLock<IntCounter> = OnceLock::new();
58+
static RECONCILIATION_GAPS_FILLED: OnceLock<IntCounter> = OnceLock::new();
5459

5560
/// One-time initializer. Builds the registry, registers every metric,
5661
/// and sets the constant `gitlawb_info` gauge. Idempotent — calling
@@ -197,6 +202,30 @@ pub fn init(version: &str, node_did: &str) {
197202
.set(peers_connected)
198203
.expect("set PEERS_CONNECTED once");
199204

205+
let gaps_found = IntCounter::with_opts(Opts::new(
206+
"gitlawb_reconciliation_gaps_found_total",
207+
"Total reconciliation sweep gaps detected (objects that should be pinned but are not)",
208+
))
209+
.expect("gitlawb_reconciliation_gaps_found_total definition");
210+
registry
211+
.register(Box::new(gaps_found.clone()))
212+
.expect("register gitlawb_reconciliation_gaps_found_total");
213+
RECONCILIATION_GAPS_FOUND
214+
.set(gaps_found)
215+
.expect("set RECONCILIATION_GAPS_FOUND once");
216+
217+
let gaps_filled = IntCounter::with_opts(Opts::new(
218+
"gitlawb_reconciliation_gaps_filled_total",
219+
"Total reconciliation sweep gaps successfully filled (objects pinned by the sweep)",
220+
))
221+
.expect("gitlawb_reconciliation_gaps_filled_total definition");
222+
registry
223+
.register(Box::new(gaps_filled.clone()))
224+
.expect("register gitlawb_reconciliation_gaps_filled_total");
225+
RECONCILIATION_GAPS_FILLED
226+
.set(gaps_filled)
227+
.expect("set RECONCILIATION_GAPS_FILLED once");
228+
200229
REGISTRY
201230
.set(registry)
202231
.expect("set REGISTRY once (init must be called exactly once)");
@@ -264,6 +293,20 @@ pub fn set_peers_connected(count: i64) {
264293
}
265294
}
266295

296+
/// Record reconciliation sweep gaps found (objects that should be pinned but are not).
297+
pub fn record_reconciliation_gaps_found(count: u64) {
298+
if let Some(c) = RECONCILIATION_GAPS_FOUND.get() {
299+
c.inc_by(count);
300+
}
301+
}
302+
303+
/// Record reconciliation sweep gaps filled (objects successfully pinned by the sweep).
304+
pub fn record_reconciliation_gaps_filled(count: u64) {
305+
if let Some(c) = RECONCILIATION_GAPS_FILLED.get() {
306+
c.inc_by(count);
307+
}
308+
}
309+
267310
/// Encode the registry as the standard Prometheus text exposition format.
268311
/// Returns an error if `init` was never called.
269312
pub fn encode() -> Result<String, prometheus::Error> {

0 commit comments

Comments
 (0)