Skip to main content

Crate bal_archive

Crate bal_archive 

Source
Expand description

§bal-archive

Local, verified history of contract storage. Accumulates every change from EIP-7928 BALs into an embedded redb file, backfills older blocks, handles reorgs, and answers “what was in slot X at block N” with one ordered seek. Knows blocks and slots; knows nothing about Solidity — see bal-layout for names.

use alloy_primitives::{address, B256, U256};
use bal_archive::{Archive, NotAvailable};
use bal_source::JsonRpcSource;

let archive = Archive::open("./balq.redb")?;
let proxy = address!("35825972e2ca90851b14576C531F13dA0B5d53ce");
archive.watch(proxy, 114_563)?;               // must be above the current head

// Forward: fetch → verify keccak(rlp(bal)) against the header → apply.
let node = JsonRpcSource::new("http://localhost:8545");
let report = archive.sync(&node, None).await?;
println!("applied {} blocks", report.blocks_applied);

// Backward: older blocks' BALs down to the contract's creation, each block
// chained by parent_hash to the one above. No proofs, no archive node.
let back = archive.backfill(&node, proxy, bal_archive::BackfillOpts::default()).await?;
println!("history now starts at {} ({:?})", back.to, back.stopped);

let slot = B256::from(U256::from(0).to_be_bytes::<32>());
match archive.storage_at(proxy, slot, 114_591) {
    Ok(v) => println!("{} set at block {} ({:?})", v.value, v.set_at, v.provenance),
    Err(NotAvailable::BeforeStart { start, .. }) => println!("history starts at {start}"),
    Err(e) => println!("no value: {e}"),     // never a silent zero
}

Every stored word carries its Provenance (Bal, Proof, or opt-in Unverified/Imported). Every miss is a typed NotAvailable. All methods take &self: share the archive in an Arc and read while sync runs.

sync(&node, Some(&state)) additionally proves the earlier value of newly seen slots with eth_getProof while the node’s state window allows — an optional shortcut for what backfill reads from blocks.

Key layout, backfill and creation rules, reorg handling and the trust model are documented in the repository (docs/DECISIONS.md, docs/SECURITY-AUDIT.md).

bal-archive: accumulate storage changes from verified BALs, serve versioned reads, backfill older blocks, handle reorgs. Knows blocks and slots; knows nothing about Solidity.

Every value in the store carries its Provenance. Every miss is a typed NotAvailable. There is no code path that returns a zero for “unknown”.

All methods take &self: redb serialises writers itself, so an Archive can be shared (e.g. in an Arc) and read while Archive::sync is running. Archive::watch and the sync loop coordinate through a small gate so that a watch added mid-sync is never silently skipped.

Structs§

Archive
The store. One file, one process, any number of readers alongside the syncing writer.
ArchiveConfig
Tunables fixed at Archive::open_with.
ArchiveStats
What Archive::stats reports.
BackfillOpts
What to walk back to.
BackfillReport
What one Archive::backfill call did.
HistoryEntry
One recorded change, as returned by Archive::history.
StorageValue
A stored word with where it came from and when it was set.
SyncReport
What one Archive::sync pass did.

Enums§

ArchiveError
Failures of the archive itself (storage, source, verification). Reads use NotAvailable instead: a missing value is an answer, not a failure.
BackfillStop
Why a backfill call returned.
BootState
Bootstrap bookkeeping per (addr, slot).
NotAvailable
Why a read has no answer. Promise #3 lives here: a caller always learns which boundary it hit, and never receives a zero in place of “unknown”.
Provenance
Where a stored value came from. Stored as the first byte of every value so provenance can never drift from the data.

Constants§

OLDEST_UPGRADABLE
Oldest on-disk version this build upgrades in place.
REORG_HORIZON_FALLBACK
Block hashes retained behind the head when the source has no finalized tag, and the deepest reorg find_fork will walk. Deeper reorgs are refused as ArchiveError::ReorgBeyondHorizon rather than guessed or walked one RPC call at a time forever.
SCHEMA_VERSION
Key/value layout version written into meta:schema_version. Bump when any byte layout in this module changes.
TOUCHED_CAP
Bound on SyncReport::touched; beyond it the list stops growing.

Type Aliases§

Result
Result of archive operations.