Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,24 +366,33 @@ cargo test -p ethlambda-blockchain --test forkchoice_spectests -- --test-threads
finalized boundary, signatures are pruned (`prune_old_block_signatures`) while
headers and bodies are kept forever. `get_signed_block` returns `None` for a
pruned finalized block
- States are stored as parent-linked diffs (`StateDiffs`, never pruned) plus
full-state snapshots (`States`) written only at 1024-slot anchors (and the
bootstrap). Neither is ever pruned. `get_state` returns an anchor snapshot or
reconstructs by walking diffs back to the nearest anchor; results are memoized
in an in-memory LRU (`STATE_CACHE_CAPACITY`) so recent reads stay hot
- `LiveChain` table provides fast `(slot||root) → parent_root` index for fork choice
- Storage uses trait-based API: `StorageBackend` → `StorageReadView` (reads) + `StorageWriteBatch` (atomic writes)

### Storage Tables (10)
### Storage Tables (7)

These are the variants of the `Table` enum (`crates/storage/src/api/tables.rs`).

| Table | Key → Value | Purpose |
|-------|-------------|---------|
| `BlockHeaders` | H256 → BlockHeader | Block headers by root |
| `BlockBodies` | H256 → BlockBody | Block bodies (empty for genesis) |
| `BlockSignatures` | H256 → BlockSignatures | Signatures (absent for genesis) |
| `States` | H256 → State | Beacon states by root |
| `LatestKnownAttestations` | u64 → AttestationData | Fork-choice-active attestations |
| `LatestNewAttestations` | u64 → AttestationData | Pending (pre-promotion) attestations |
| `GossipSignatures` | SignatureKey → ValidatorSignature | Individual validator signatures |
| `AggregatedPayloads` | SignatureKey → Vec\<AggregatedSignatureProof\> | Aggregated proofs |
| `BlockSignatures` | (slot\|\|root) → BlockSignatures | Type-2 proof blob; keyed slot\|\|root so pruning scans in slot order and stops early; absent for genesis, pruned below finalized |
| `States` | H256 → State | Full-state snapshots; bootstrap + 1024-slot anchors only; never pruned |
| `StateDiffs` | H256 → StateDiff | Parent-linked state diff per non-genesis state; never pruned |
| `Metadata` | string → various | Store state (head, config, checkpoints) |
| `LiveChain` | (slot\|\|root) → parent\_root | Fast fork choice traversal index |

Attestations and gossip signatures are **not** persisted tables; they live in
in-memory `Store` buffers (`new_payloads`, `known_payloads`, `gossip_signatures`)
and are consumed during the tick pipeline (promotion at intervals 0/4,
aggregation at interval 2).

### State Root Computation
- Always computed via `tree_hash_root()` after full state transition
- Must match proposer's pre-computed `block.state_root`
Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ vergen-git2 = { version = "9", features = ["rustc"] }

rayon = "1.11"
rand = "0.10"
lru = "0.16"
rocksdb = "0.24"
libc = "0.2"
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] }
Expand Down
18 changes: 14 additions & 4 deletions crates/blockchain/src/store.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::collections::HashSet;

use ethlambda_state_transition::{is_proposer, slot_is_justifiable_after};
use ethlambda_storage::{ForkCheckpoints, Store};
use ethlambda_storage::{DiffBase, ForkCheckpoints, Store};
use ethlambda_types::{
ShortRoot,
attestation::{
Expand Down Expand Up @@ -556,6 +556,10 @@ fn on_block_core(

let block = signed_block.message.clone();

// Capture the diff base before the parent is consumed into the post-state
// (avoids cloning the multi-MB historical_block_hashes list).
let diff_base = DiffBase::from_state(block.parent_root, &parent_state);

// Execute state transition function to compute post-block state
let state_transition_start = std::time::Instant::now();
let mut post_state = parent_state;
Expand All @@ -576,9 +580,9 @@ fn on_block_core(
store.update_checkpoints(ForkCheckpoints::new(store.head(), Some(justified), None));
}

// Store signed block and state
// Store signed block and state (as a parent-linked diff + snapshot)
store.insert_signed_block(block_root, signed_block.clone());
store.insert_state(block_root, post_state);
store.insert_state_with_diff(block_root, diff_base, post_state);

for att in block.body.attestations.iter() {
// Count each participating validator as a valid attestation.
Expand Down Expand Up @@ -1251,7 +1255,13 @@ mod tests {
let head_justified = Checkpoint { root: a, slot: 1 };
let mut head_state = State::from_genesis(1000, vec![]);
head_state.latest_justified = head_justified;
store.insert_state(b, head_state);
// Persist `b`'s post-state via the diff API, diffed against the genesis
// anchor that already lives in the store. The base must describe the
// parent (genesis) state, not the target; `get_state(b)` then resolves
// via the cache or by replaying this diff onto the genesis snapshot.
let genesis_state = store.get_state(&genesis).expect("genesis state");
let diff_base = DiffBase::from_state(genesis, &genesis_state);
store.insert_state_with_diff(b, diff_base, head_state);

// Store's global justified latched onto a higher, off-head checkpoint,
// as it would after a minority fork justified a slot the head never saw.
Expand Down
3 changes: 3 additions & 0 deletions crates/storage/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ thiserror.workspace = true

libssz.workspace = true
libssz-derive.workspace = true
libssz-types.workspace = true

lru.workspace = true

[dev-dependencies]
tempfile = "3"
Expand Down
13 changes: 12 additions & 1 deletion crates/storage/src/api/tables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,16 @@ pub enum Table {
/// All other blocks must have an entry in this table.
BlockSignatures,
/// State storage: H256 -> State
///
/// Holds full-state snapshots only: the bootstrap anchor plus one anchor per
/// 1024-slot window. Never pruned. Non-anchor states live in `StateDiffs` and
/// are reconstructed on demand (memoized by an in-memory cache).
States,
/// State diffs: H256 -> StateDiff
///
/// Parent-linked diff written for every non-genesis state. Never pruned, so
/// it preserves full state history. See `get_state` for reconstruction.
StateDiffs,
/// Metadata: string keys -> various scalar values
Metadata,
/// Live chain index: (slot || root) -> parent_root
Expand All @@ -23,11 +32,12 @@ pub enum Table {
}

/// All table variants.
pub const ALL_TABLES: [Table; 6] = [
pub const ALL_TABLES: [Table; 7] = [
Table::BlockHeaders,
Table::BlockBodies,
Table::BlockSignatures,
Table::States,
Table::StateDiffs,
Table::Metadata,
Table::LiveChain,
];
Expand All @@ -40,6 +50,7 @@ impl Table {
Table::BlockBodies => "block_bodies",
Table::BlockSignatures => "block_signatures",
Table::States => "states",
Table::StateDiffs => "state_diffs",
Table::Metadata => "metadata",
Table::LiveChain => "live_chain",
}
Expand Down
12 changes: 4 additions & 8 deletions crates/storage/src/backend/rocksdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,11 @@ use std::path::Path;
use std::sync::Arc;

/// Returns the column family name for a table.
///
/// Delegates to [`Table::name`] so the CF name and the metrics label share a
/// single source of truth (and a new table only needs one mapping).
fn cf_name(table: Table) -> &'static str {
match table {
Table::BlockHeaders => "block_headers",
Table::BlockBodies => "block_bodies",
Table::BlockSignatures => "block_signatures",
Table::States => "states",
Table::Metadata => "metadata",
Table::LiveChain => "live_chain",
}
table.name()
}

/// RocksDB storage backend.
Expand Down
2 changes: 2 additions & 0 deletions crates/storage/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
mod api;
pub mod backend;
mod error;
mod state_diff;
mod store;

pub use api::{ALL_TABLES, StorageBackend, StorageReadView, StorageWriteBatch, Table};
pub use state_diff::DiffBase;
pub use store::{ForkCheckpoints, GetForkchoiceStoreError, MAX_RESUMABLE_DB_STATE_AGE, Store};
Loading