OXYGENHUB
# 67bfa06a894f spectra / src / cli / admin.rs
3.8 KiB · Rust · last changed The command line from another machine: every command through the API (#23, completes #21) 1 day ago source blame history raw
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.
10
pub async fn verify(ctx: &Ctx, json: bool) -> Result<i32> {
11
    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).
13
    let v: serde_json::Value = match ctx.remote()? {
14
        Some(_) => {
15
            let home = super::identity::owner_home(ctx, None);
16
            let c = ctx.owner_client(&home).await?.context("no server")?;
17
            super::call(&c, "GET", "/api/v1/admin/verify", None).await?
18
        }
19
        None => serde_json::to_value(Ledger::open_read_only(&ctx.db()?).await?.verify(&mut Principals::default()).await?)?,
20
    };
21
    if let Some(s) = spin {
22
        s.clear();
23
    }
24
    let findings = v["findings"].as_array().cloned().unwrap_or_default();
25
    let clean = findings.is_empty();
26
    if json {
27
        println!("{v}");
28
        return Ok(if clean { 0 } else { 1 });
29
    }
30
    if clean {
31
        println!("{} {}", ui::done("Verified"), ui::subject("the ledger"));
32
    } else {
33
        println!(
34
            "{} {} {}",
35
            ui::err(ui::glyph("✘", "x")),
36
            ui::err("The ledger does not verify:"),
37
            ui::err(format!("{} finding(s)", findings.len()))
38
        );
39
    }
40
    println!("{}", ui::kv("events", v["events"].as_u64().unwrap_or(0)));
41
    let last = v["last_checkpoint"].as_u64().map(|s| format!("last at #{s}")).unwrap_or_else(|| "none yet".into());
42
    println!("{}", ui::kv("signed", format!("{} checkpoint(s){}{last}", v["checkpoints"].as_u64().unwrap_or(0), ui::sep())));
43
    let uncovered = v["uncovered"].as_u64().unwrap_or(0);
44
    if uncovered > 0 {
45
        println!("{}", ui::kv("pending", ui::dim(format!("{uncovered} event(s) since, chained and awaiting the next checkpoint"))));
46
    }
47
    println!("{}", ui::kv("head", ui::hash(v["head"].as_str().unwrap_or(""))));
48
    for f in &findings {
49
        // As the problem is named in code: hash_mismatch is HashMismatch.
50
        let problem = match &f["problem"] {
51
            serde_json::Value::String(p) => p
52
                .split('_')
53
                .map(|w| {
54
                    let mut c = w.chars();
55
                    c.next().map(|f| f.to_uppercase().chain(c).collect::<String>()).unwrap_or_default()
56
                })
57
                .collect(),
58
            other => other.to_string(),
59
        };
60
        println!(
61
            "  {} {} {}",
62
            ui::id(format!("#{}", f["seq"].as_u64().unwrap_or(0))),
63
            ui::err(problem),
64
            f["detail"].as_str().unwrap_or("")
65
        );
66
    }
67
    Ok(if clean { 0 } else { 1 })
68
}
69
70
/// `spectra admin check`: do the projections equal a rebuild?
71
pub async fn check(ctx: &Ctx) -> Result<i32> {
72
    ctx.local_only("spectra admin check")?;
73
    let ledger = ctx.writer().await?;
74
    let differ = ledger.check().await?;
75
    if differ.is_empty() {
76
        println!("{} {}", ui::done("Checked"), ui::subject("every projection matches the ledger"));
77
        Ok(0)
78
    } else {
79
        println!("{} projections differ from the ledger: {}", ui::caution(), ui::err(differ.join(", ")));
80
        println!("  {} {}", ui::note(), ui::dim("rebuild them with spectra admin rebuild"));
81
        Ok(1)
82
    }
83
}
84
85
/// `spectra admin rebuild`: replay the ledger into every projection.
86
pub async fn rebuild(ctx: &Ctx) -> Result<i32> {
87
    ctx.local_only("spectra admin rebuild")?;
88
    let ledger = ctx.writer().await?;
89
    let spin = ui::spinner("replaying the ledger");
90
    ledger.rebuild().await?;
91
    spin.done(&format!("{} {}", ui::done("Rebuilt"), ui::subject(format!("projections from {} events", ledger.head().await.0))));
92
    Ok(0)
93
}