balq 0.4.0

BAL query: local archive of contract storage history, verified against block headers.
use super::Ctx;
use crate::util::emit;
use anyhow::Result;
use bal_source::{BalProbe, JsonRpcSource, BAL_HASH_FIELD, BAL_METHOD};
use serde_json::json;

fn probe_json(p: &BalProbe) -> serde_json::Value {
    match p {
        BalProbe::Verified {
            block,
            accounts,
            hash,
        } => json!({ "block": block, "status": "verified", "accounts": accounts, "hash": hash }),
        BalProbe::NoHashInHeader { block, accounts } => {
            json!({ "block": block, "status": "no_hash_in_header", "accounts": accounts })
        }
        BalProbe::Mismatch {
            block,
            computed,
            expected,
        } => {
            json!({ "block": block, "status": "mismatch", "computed": computed, "expected": expected })
        }
        BalProbe::Missing(b) => json!({ "block": b, "status": "missing" }),
        BalProbe::Error(e) => json!({ "status": "error", "message": e }),
    }
}

fn probe_line(p: &BalProbe) -> String {
    match p {
        BalProbe::Verified {
            block,
            accounts,
            hash,
        } => format!(
            "block {block}: VERIFIED — {accounts} accounts, keccak(rlp(bal)) == header ({hash})"
        ),
        BalProbe::NoHashInHeader { block, accounts } => format!(
            "block {block}: served ({accounts} accounts) but header has no BAL hash — cannot verify"
        ),
        BalProbe::Mismatch {
            block,
            computed,
            expected,
        } => format!(
            "block {block}: HASH MISMATCH — computed {computed}, header {expected}. Codec/spec drift; do not build on this."
        ),
        BalProbe::Missing(b) => format!("block {b}: {BAL_METHOD} returned null"),
        BalProbe::Error(e) => format!("error: {e}"),
    }
}

pub async fn run(ctx: &Ctx, rpc: Option<String>, age: u64) -> Result<()> {
    let rpc = ctx.cfg.rpc(rpc)?;
    let src = JsonRpcSource::new(&rpc);
    let r = src.probe(age).await?;
    let hash_field = r.head_fields.iter().any(|f| f == BAL_HASH_FIELD);

    if ctx.json {
        emit(&json!({
            "rpc": rpc,
            "client": r.client_version,
            "chainId": r.chain_id,
            "head": r.head,
            "headerHasBalHash": hash_field,
            "balMethod": BAL_METHOD,
            "q1Head": probe_json(&r.head_probe),
            "q2Old": probe_json(&r.old_probe),
            "q2Block1": probe_json(&r.earliest_probe),
            "proofWindow": match &r.proof_window {
                Ok(w) => json!(w),
                Err(e) => json!({ "error": e }),
            },
        }));
        return Ok(());
    }

    println!(
        "client:        {}",
        r.client_version.as_deref().unwrap_or("?")
    );
    println!(
        "chain id:      {}",
        r.chain_id.map(|c| c.to_string()).unwrap_or("?".into())
    );
    println!("head:          {}", r.head);
    println!(
        "header field:  {}",
        if hash_field {
            format!("`{BAL_HASH_FIELD}` present")
        } else {
            format!(
                "`{BAL_HASH_FIELD}` ABSENT — fields: {}",
                r.head_fields.join(", ")
            )
        }
    );
    println!("method:        {BAL_METHOD}");
    println!();
    println!("{:<16}{}", "Q1 head", probe_line(&r.head_probe));
    println!("{:<16}{}", "Q2 old", probe_line(&r.old_probe));
    println!("{:<16}{}", "Q2 block 1", probe_line(&r.earliest_probe));
    match &r.proof_window {
        Ok(0) => {
            println!("eth_getProof    window 0 — proofs only at head.");
            println!("                Only `sync --prove` (the optional eth_getProof shortcut) is affected;");
            println!("                `index` and `backfill` read earlier values from older blocks and never need proofs.");
        }
        Ok(w) => println!("eth_getProof    window {w} blocks — pass `sync --proof-window {w}`"),
        Err(e) => println!("eth_getProof    NOT served — no bootstrap at all: {e}"),
    }
    Ok(())
}