OXYGENHUB

Coverage / src/cli/admin.rs 92.1%

70 of 76 lines run, 9 of 20 functions · run #31 on main · run missed

1
//! `spectra admin`: the ledger's own checks (spec §9.5, §19).
2
3
use super::{ui, Ctx};
4
use anyhow::{Context, Result};
5
use spectra_core::ledger::Ledger;
6
use spectra_core::principals::Principals;
7
8
/// `spectra admin verify`: recompute the chain, check every checkpoint and
9
/// proof; exit 1 when anything is wrong.
1022×
pub async fn verify(ctx: &Ctx, json: bool) -> Result<i32> {
1122×
    let spin = if json { None } else { Some(ui::spinner("verifying the ledger")) };
12
    // On a client the server verifies its own ledger (spec §8.6).
1322×
    let v: serde_json::Value = match ctx.remote()? {
14
        Some(_) => {
151×
            let home = super::identity::owner_home(ctx, None);
161×
            let c = ctx.owner_client(&home).await?.context("no server")?;
171×
            super::call(&c, "GET", "/api/v1/admin/verify", None).await?
18
        }
1921×
        None => serde_json::to_value(Ledger::open_read_only(&ctx.db()?).await?.verify(&mut Principals::default()).await?)?,
20
    };
2121×
    if let Some(s) = spin {
2210×
        s.clear();
2311×
    }
2421×
    let findings = v["findings"].as_array().cloned().unwrap_or_default();
2521×
    let clean = findings.is_empty();
2621×
    if json {
2711×
        println!("{v}");
2811×
        return Ok(if clean { 0 } else { 1 });
2910×
    }
3010×
    if clean {
319×
        println!("{} {}", ui::done("Verified"), ui::subject("the ledger"));
329×
    } else {
331×
        println!(
341×
            "{} {} {}",
351×
            ui::err(ui::glyph("✘", "x")),
361×
            ui::err("The ledger does not verify:"),
371×
            ui::err(format!("{} finding(s)", findings.len()))
381×
        );
391×
    }
4010×
    println!("{}", ui::kv("events", v["events"].as_u64().unwrap_or(0)));
4110×
    let last = v["last_checkpoint"].as_u64().map(|s| format!("last at #{s}")).unwrap_or_else(|| "none yet".into());
4210×
    println!("{}", ui::kv("signed", format!("{} checkpoint(s){}{last}", v["checkpoints"].as_u64().unwrap_or(0), ui::sep())));
4310×
    let uncovered = v["uncovered"].as_u64().unwrap_or(0);
4410×
    if uncovered > 0 {
450×
        println!("{}", ui::kv("pending", ui::dim(format!("{uncovered} event(s) since, chained and awaiting the next checkpoint"))));
4610×
    }
4710×
    println!("{}", ui::kv("head", ui::hash(v["head"].as_str().unwrap_or(""))));
4810×
    for f in &findings {
49
        // As the problem is named in code: hash_mismatch is HashMismatch.
501×
        let problem = match &f["problem"] {
511×
            serde_json::Value::String(p) => p
521×
                .split('_')
532×
                .map(|w| {
542×
                    let mut c = w.chars();
552×
                    c.next().map(|f| f.to_uppercase().chain(c).collect::<String>()).unwrap_or_default()
562×
                })
571×
                .collect(),
580×
            other => other.to_string(),
59
        };
601×
        println!(
61
            "  {} {} {}",
621×
            ui::id(format!("#{}", f["seq"].as_u64().unwrap_or(0))),
631×
            ui::err(problem),
641×
            f["detail"].as_str().unwrap_or("")
65
        );
66
    }
6710×
    Ok(if clean { 0 } else { 1 })
6822×
}
69
70
/// `spectra admin check`: do the projections equal a rebuild?
716×
pub async fn check(ctx: &Ctx) -> Result<i32> {
726×
    ctx.local_only("spectra admin check")?;
735×
    let ledger = ctx.writer().await?;
745×
    let differ = ledger.check().await?;
755×
    if differ.is_empty() {
765×
        println!("{} {}", ui::done("Checked"), ui::subject("every projection matches the ledger"));
775×
        Ok(0)
78
    } else {
790×
        println!("{} projections differ from the ledger: {}", ui::caution(), ui::err(differ.join(", ")));
800×
        println!("  {} {}", ui::note(), ui::dim("rebuild them with spectra admin rebuild"));
810×
        Ok(1)
82
    }
836×
}
84
85
/// `spectra admin rebuild`: replay the ledger into every projection.
863×
pub async fn rebuild(ctx: &Ctx) -> Result<i32> {
873×
    ctx.local_only("spectra admin rebuild")?;
883×
    let ledger = ctx.writer().await?;
893×
    let spin = ui::spinner("replaying the ledger");
903×
    ledger.rebuild().await?;
913×
    spin.done(&format!("{} {}", ui::done("Rebuilt"), ui::subject(format!("projections from {} events", ledger.head().await.0))));
923×
    Ok(0)
933×
}