Skip to content

Commit 89b3d6e

Browse files
refactor(storage): rename BlockSignatures to BlockProof (#553)
## 🗒️ Description / Motivation This PR renames the storage table previously called `BlockSignatures` to `BlockProof`. The table stores the merged block proof (`MultiMessageAggregate`), not individual block signatures, so the old name was misleading. This makes the storage API, RocksDB column-family name, pruning helpers, tests, and docs match the data that is actually stored. ## What Changed - Updated `crates/storage/src/api/tables.rs` - Renamed `Table::BlockSignatures` to `Table::BlockProof`. - Renamed the table label from `block_signatures` to `block_proof`. - Updated `crates/storage/src/store.rs` - Replaced `Table::BlockSignatures` usages with `Table::BlockProof`. - Renamed block proof pruning helpers and tests. - Updated comments around signed block reconstruction and proof pruning. - Updated `crates/storage/src/backend/rocksdb.rs` - Added compatibility handling for legacy DBs that still contain the old `block_signatures` column family. - Updated docs and comments - Refreshed storage docs and architecture references to use `BlockProof`. ## Correctness / Behavior Guarantees - No block/proof encoding behavior changes. - The table still uses the same `slot || root` key format. - Genesis/anchor behavior is preserved: `get_signed_block` still synthesizes an empty proof only for slot 0. - Finalized block proofs are still pruned by the same retention rule, now via `prune_old_block_proofs`. ## Tests Added / Run ```bash cargo fmt --all -- --check cargo test -p ethlambda-storage --lib --offline block_proof cargo test -p ethlambda-storage --lib --offline prune_old_block_proofs cargo test -p ethlambda-storage --lib --offline get_signed_block make lint git diff --check ``` ## Related Issues / PRs - Closes #527 ## ✅ Verification Checklist - [x] Ran `make fmt` — clean - [x] Ran `make lint` (clippy with `-D warnings`) — clean - [x] Ran `make test` (`cargo test --workspace --profile release-fast`) — all passing --------- Co-authored-by: Tomás Grüner <47506558+MegaRedHand@users.noreply.github.com>
1 parent 82b6c10 commit 89b3d6e

6 files changed

Lines changed: 83 additions & 86 deletions

File tree

bin/ethlambda/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -742,7 +742,7 @@ async fn fetch_initial_state(
742742
// Initialize the store from state + anchor block body, then persist the
743743
// signatures so we can serve the anchor on BlocksByRoot. `insert_signed_block`
744744
// overlaps with what `get_forkchoice_store` already wrote, but it's
745-
// idempotent and the only path that also stores `BlockSignatures`.
745+
// idempotent and the only path that also stores `BlockProof`.
746746
let anchor_root = signed_block.message.header().hash_tree_root();
747747
let mut store = Store::get_forkchoice_store(backend, state, signed_block.message.clone())
748748
.inspect_err(|err| error!(%err, "Failed to initialize store from anchor state and block"))

crates/net/rpc/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,7 @@ mod tests {
532532
use libssz::SszEncode;
533533

534534
// Genesis-anchored store: `init_store` writes the header + state but no
535-
// `BlockSignatures` (proof) row. `get_signed_block` synthesizes an empty
535+
// `BlockProof` (proof) row. `get_signed_block` synthesizes an empty
536536
// proof so peers can still receive the genesis block on BlocksByRoot;
537537
// the HTTP endpoint stays consistent and returns 200 rather than 404.
538538
let state = create_test_state();

crates/storage/src/api/tables.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ pub enum Table {
55
BlockHeaders,
66
/// Block body storage: H256 -> BlockBody
77
BlockBodies,
8-
/// Block signatures storage: (slot || root) -> BlockSignatures
8+
/// Block proof storage: (slot || root) -> BlockProof
99
///
10-
/// Stored separately from blocks because the genesis block has no signatures.
10+
/// Stored separately from blocks because the genesis block has no proof.
1111
/// Keyed by slot || root so pruning can scan in slot order and stop early.
12-
/// Non-genesis blocks have an entry until finalized: signatures below the
13-
/// finalized boundary are pruned (`prune_old_block_signatures`), while
12+
/// Non-genesis blocks have an entry until finalized: proofs below the
13+
/// finalized boundary are pruned (`prune_old_block_proofs`), while
1414
/// headers and bodies are kept forever.
15-
BlockSignatures,
15+
BlockProof,
1616
/// Canonical block index: slot -> block root
1717
BlockRoots,
1818
/// State storage: H256 -> State
@@ -40,7 +40,7 @@ pub enum Table {
4040
pub const ALL_TABLES: [Table; 8] = [
4141
Table::BlockHeaders,
4242
Table::BlockBodies,
43-
Table::BlockSignatures,
43+
Table::BlockProof,
4444
Table::BlockRoots,
4545
Table::States,
4646
Table::StateDiffs,
@@ -54,7 +54,7 @@ impl Table {
5454
match self {
5555
Table::BlockHeaders => "block_headers",
5656
Table::BlockBodies => "block_bodies",
57-
Table::BlockSignatures => "block_signatures",
57+
Table::BlockProof => "block_proof",
5858
Table::BlockRoots => "block_roots",
5959
Table::States => "states",
6060
Table::StateDiffs => "state_diffs",

crates/storage/src/store.rs

Lines changed: 62 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,11 @@ const SNAPSHOT_ANCHOR_INTERVAL: u64 = 1_024;
106106
/// snapshot read or a diff-chain reconstruction.
107107
const STATE_CACHE_CAPACITY: usize = 32;
108108

109-
/// Keep block signatures for at least this many slots below the tip, even once
110-
/// finalized. Signatures older than this window are pruned only when the window
111-
/// lies entirely within finalized history; see [`Store::prune_old_block_signatures`].
109+
/// Keep block proofs for at least this many slots below the tip, even once
110+
/// finalized. Proofs older than this window are pruned only when the window
111+
/// lies entirely within finalized history; see [`Store::prune_old_block_proofs`].
112112
/// ~1 day at 4-second slots.
113-
const SIGNATURE_PRUNING_RANGE: u64 = 21_600;
113+
const BLOCK_PROOF_PRUNING_RANGE: u64 = 21_600;
114114

115115
/// ~30 minutes of resume window at 4-second slots (1800 / 4 = 450).
116116
pub const MAX_RESUMABLE_DB_STATE_AGE: u64 = 450;
@@ -522,7 +522,7 @@ fn encode_slot_root_key(slot: u64, root: &H256) -> Vec<u8> {
522522
result
523523
}
524524

525-
/// Decode a slot||root key (LiveChain / BlockSignatures) from bytes.
525+
/// Decode a slot||root key (LiveChain / BlockProof) from bytes.
526526
fn decode_slot_root_key(bytes: &[u8]) -> (u64, H256) {
527527
let slot = u64::from_be_bytes(bytes[..8].try_into().expect("valid slot bytes"));
528528
let root = H256::from_slice(&bytes[8..]);
@@ -916,10 +916,10 @@ impl Store {
916916
Ok(())
917917
}
918918

919-
/// Prune finalized block signatures to keep signature storage bounded.
919+
/// Prune finalized block proofs to keep proof storage bounded.
920920
///
921921
/// State diffs, block headers, block bodies, and full-state snapshots are
922-
/// all retained for the full history and are never pruned. Only signatures
922+
/// all retained for the full history and are never pruned. Only proofs
923923
/// of finalized blocks older than the pruning window are removed.
924924
///
925925
/// This is separated from `update_checkpoints` so callers can defer heavy
@@ -935,10 +935,10 @@ impl Store {
935935
header.expect("Failed to get block header").slot
936936
});
937937
let pruned_below_slot = self
938-
.prune_old_block_signatures(finalized_slot, tip_slot)
939-
.expect("prune old block signatures");
938+
.prune_old_block_proofs(finalized_slot, tip_slot)
939+
.expect("prune old block proofs");
940940
if pruned_below_slot > 0 {
941-
info!(pruned_below_slot, "Pruned old finalized block signatures");
941+
info!(pruned_below_slot, "Pruned old finalized block proofs");
942942
}
943943
Ok(())
944944
}
@@ -1088,32 +1088,32 @@ impl Store {
10881088
pruned_new + pruned_known
10891089
}
10901090

1091-
/// Prune signatures of old finalized blocks, keeping a recent window.
1091+
/// Prune proofs of old finalized blocks, keeping a recent window.
10921092
///
1093-
/// Signatures within [`SIGNATURE_PRUNING_RANGE`] slots of `tip_slot` are
1094-
/// always kept, as are all signatures of non-finalized blocks. Concretely,
1095-
/// with `cutoff = tip_slot - SIGNATURE_PRUNING_RANGE`:
1093+
/// Proofs within [`BLOCK_PROOF_PRUNING_RANGE`] slots of `tip_slot` are
1094+
/// always kept, as are all proofs of non-finalized blocks. Concretely,
1095+
/// with `cutoff = tip_slot - BLOCK_PROOF_PRUNING_RANGE`:
10961096
///
1097-
/// - if `cutoff <= finalized_slot` (healthy finality): delete signatures for
1097+
/// - if `cutoff <= finalized_slot` (healthy finality): delete proofs for
10981098
/// `slot < cutoff` (entirely within finalized history);
10991099
/// - otherwise (the non-finalized range exceeds the window): prune nothing,
11001100
/// since pruning up to `cutoff` would touch non-finalized blocks.
11011101
///
11021102
/// Headers and bodies are always retained. Finalized blocks can never be
1103-
/// reverted, so their signatures are not needed for fork choice, re-org
1103+
/// reverted, so their proofs are not needed for fork choice, re-org
11041104
/// safety, or re-aggregation once outside the window.
11051105
///
1106-
/// Returns the exclusive slot below which signatures were dropped, or 0 when
1106+
/// Returns the exclusive slot below which proofs were dropped, or 0 when
11071107
/// nothing was pruned. This is a range delete, so the count of removed keys
11081108
/// is not known without reading the table back.
1109-
pub fn prune_old_block_signatures(
1109+
pub fn prune_old_block_proofs(
11101110
&mut self,
11111111
finalized_slot: u64,
11121112
tip_slot: u64,
11131113
) -> Result<u64, Error> {
1114-
let cutoff = tip_slot.saturating_sub(SIGNATURE_PRUNING_RANGE);
1114+
let cutoff = tip_slot.saturating_sub(BLOCK_PROOF_PRUNING_RANGE);
11151115
// Only prune when the whole window is finalized; never touch
1116-
// non-finalized signatures. A zero cutoff covers nothing.
1116+
// non-finalized proofs. A zero cutoff covers nothing.
11171117
if cutoff > finalized_slot || cutoff == 0 {
11181118
return Ok(0);
11191119
}
@@ -1126,11 +1126,11 @@ impl Store {
11261126
let mut batch = self.backend.begin_write().expect("write batch");
11271127
batch
11281128
.delete_range(
1129-
Table::BlockSignatures,
1129+
Table::BlockProof,
11301130
&0u64.to_be_bytes(),
11311131
&cutoff.to_be_bytes(),
11321132
)
1133-
.expect("delete finalized block signatures");
1133+
.expect("delete finalized block proofs");
11341134
batch.commit().expect("commit");
11351135

11361136
Ok(cutoff)
@@ -1149,8 +1149,8 @@ impl Store {
11491149

11501150
/// Insert a block as pending (parent state not yet available).
11511151
///
1152-
/// Stores block data in `BlockHeaders`/`BlockBodies`/`BlockSignatures`
1153-
/// **without** writing to `LiveChain`. This persists the heavy signature
1152+
/// Stores block data in `BlockHeaders`/`BlockBodies`/`BlockProof`
1153+
/// **without** writing to `LiveChain`. This persists the heavy proof
11541154
/// data (~3KB+ per block) to disk while keeping the block invisible to
11551155
/// fork choice.
11561156
///
@@ -1222,15 +1222,15 @@ impl Store {
12221222
/// Get a signed block by combining header, body, and the merged proof.
12231223
///
12241224
/// Returns None if the header or body (for non-empty bodies) is missing,
1225-
/// or if the signature row is missing for any block other than the
1225+
/// or if the proof row is missing for any block other than the
12261226
/// slot-0 anchor.
12271227
///
1228-
/// Signatures are absent in two cases: genesis-style anchor blocks (no
1229-
/// proposer ever signed them), and finalized blocks whose signatures were
1230-
/// pruned by [`prune_old_block_signatures`](Self::prune_old_block_signatures).
1228+
/// Proofs are absent in two cases: genesis-style anchor blocks (no
1229+
/// proposer ever signed them), and finalized blocks whose proofs were
1230+
/// pruned by [`prune_old_block_proofs`](Self::prune_old_block_proofs).
12311231
/// To keep BlocksByRoot symmetric with the fork-choice view for peers,
12321232
/// synthesize an empty proof for the slot-0 anchor only; for any other slot
1233-
/// a missing signature surfaces as `None` (a pruned finalized block can no
1233+
/// a missing proof surfaces as `None` (a pruned finalized block can no
12341234
/// longer be served with its proof) rather than as a fabricated block.
12351235
pub fn get_signed_block(&self, root: &H256) -> Result<Option<SignedBlock>, Error> {
12361236
let view = self.backend.begin_read().expect("read view");
@@ -1252,7 +1252,7 @@ impl Store {
12521252
};
12531253

12541254
let sig_key = encode_slot_root_key(header.slot, root);
1255-
let proof = match view.get(Table::BlockSignatures, &sig_key).expect("get") {
1255+
let proof = match view.get(Table::BlockProof, &sig_key).expect("get") {
12561256
Some(proof_bytes) => {
12571257
MultiMessageAggregate::from_ssz_bytes(&proof_bytes).expect("valid block proof")
12581258
}
@@ -1716,12 +1716,11 @@ fn write_signed_block(
17161716
.expect("put block body");
17171717
}
17181718

1719-
// Store the merged multi-message aggregate proof blob, keyed by slot||root so signature
1720-
// pruning can scan in slot order and stop early. Table name kept for the
1721-
// column-family migration cost; renaming to `BlockProof` is a follow-up.
1719+
// Store the merged multi-message aggregate proof blob, keyed by slot||root
1720+
// so proof pruning can scan in slot order and stop early.
17221721
let proof_entries = vec![(encode_slot_root_key(header.slot, root), proof.to_ssz())];
17231722
batch
1724-
.put_batch(Table::BlockSignatures, proof_entries)
1723+
.put_batch(Table::BlockProof, proof_entries)
17251724
.expect("put block proof");
17261725

17271726
block
@@ -1758,7 +1757,7 @@ mod tests {
17581757
}
17591758
}
17601759

1761-
/// Insert a block header (and dummy body + signature) for a given root, slot,
1760+
/// Insert a block header (and dummy body + proof) for a given root, slot,
17621761
/// and parent. The stored header equals `header_at(slot, parent_root)`, so a
17631762
/// state built from the same `(slot, parent_root)` reconstructs byte-identically.
17641763
fn insert_header(backend: &dyn StorageBackend, root: H256, slot: u64, parent_root: H256) {
@@ -1773,10 +1772,10 @@ mod tests {
17731772
.expect("put body");
17741773
batch
17751774
.put_batch(
1776-
Table::BlockSignatures,
1775+
Table::BlockProof,
17771776
vec![(encode_slot_root_key(slot, &root), vec![0u8; 4])],
17781777
)
1779-
.expect("put sigs");
1778+
.expect("put proof");
17801779
batch
17811780
.put_batch(
17821781
Table::BlockRoots,
@@ -1810,10 +1809,10 @@ mod tests {
18101809
view.get(table, &root.to_ssz()).expect("get").is_some()
18111810
}
18121811

1813-
/// Check whether a block signature exists for a (slot, root) pair.
1814-
fn has_signature(backend: &dyn StorageBackend, slot: u64, root: &H256) -> bool {
1812+
/// Check whether a block proof exists for a (slot, root) pair.
1813+
fn has_block_proof(backend: &dyn StorageBackend, slot: u64, root: &H256) -> bool {
18151814
let view = backend.begin_read().expect("read view");
1816-
view.get(Table::BlockSignatures, &encode_slot_root_key(slot, root))
1815+
view.get(Table::BlockProof, &encode_slot_root_key(slot, root))
18171816
.expect("get")
18181817
.is_some()
18191818
}
@@ -1964,33 +1963,33 @@ mod tests {
19641963
}
19651964

19661965
#[test]
1967-
fn prune_old_blocks_within_retention() {
1966+
fn prune_old_block_proofs_within_retention() {
19681967
let backend = Arc::new(InMemoryBackend::new());
19691968
let mut store = Store::test_store_with_backend(backend.clone());
19701969

1971-
// Blocks at slots 0..12, each with header + body + signature.
1970+
// Blocks at slots 0..12, each with header + body + proof.
19721971
for i in 0..13u64 {
19731972
insert_header(backend.as_ref(), root(i), i, H256::ZERO);
19741973
}
19751974

1976-
// Healthy finality: non-finalized gap (5) < SIGNATURE_PRUNING_RANGE.
1975+
// Healthy finality: non-finalized gap (5) < BLOCK_PROOF_PRUNING_RANGE.
19771976
// tip = range + 10, finalized = range + 5, so cutoff = tip - range = 10.
1978-
let tip_slot = SIGNATURE_PRUNING_RANGE + 10;
1979-
let finalized_slot = SIGNATURE_PRUNING_RANGE + 5;
1977+
let tip_slot = BLOCK_PROOF_PRUNING_RANGE + 10;
1978+
let finalized_slot = BLOCK_PROOF_PRUNING_RANGE + 5;
19801979
let pruned_below_slot = store
1981-
.prune_old_block_signatures(finalized_slot, tip_slot)
1980+
.prune_old_block_proofs(finalized_slot, tip_slot)
19821981
.expect("prune");
19831982

19841983
// cutoff = 10: slots 0..9 pruned, slots 10..12 kept (within the window).
19851984
assert_eq!(pruned_below_slot, 10);
1986-
assert_eq!(count_entries(backend.as_ref(), Table::BlockSignatures), 3);
1985+
assert_eq!(count_entries(backend.as_ref(), Table::BlockProof), 3);
19871986

1988-
// Oldest signatures are gone, but headers, bodies, and roots stay queryable.
1987+
// Oldest proofs are gone, but headers, bodies, and roots stay queryable.
19891988
for i in 0..10u64 {
1990-
assert!(!has_signature(backend.as_ref(), i, &root(i)));
1989+
assert!(!has_block_proof(backend.as_ref(), i, &root(i)));
19911990
}
19921991
for i in 10..13u64 {
1993-
assert!(has_signature(backend.as_ref(), i, &root(i)));
1992+
assert!(has_block_proof(backend.as_ref(), i, &root(i)));
19941993
}
19951994

19961995
// Headers and bodies are always retained for the whole history.
@@ -2000,39 +1999,39 @@ mod tests {
20001999
}
20012000

20022001
#[test]
2003-
fn prune_signatures_noop_when_non_finalized_range_exceeds_window() {
2002+
fn prune_block_proofs_noop_when_non_finalized_range_exceeds_window() {
20042003
let backend = Arc::new(InMemoryBackend::new());
20052004
let mut store = Store::test_store_with_backend(backend.clone());
20062005

20072006
for i in 0..10u64 {
20082007
insert_header(backend.as_ref(), root(i), i, H256::ZERO);
20092008
}
20102009

2011-
// Deep non-finality: gap (tip - finalized) > SIGNATURE_PRUNING_RANGE, so
2010+
// Deep non-finality: gap (tip - finalized) > BLOCK_PROOF_PRUNING_RANGE, so
20122011
// cutoff = tip - range > finalized → prune nothing.
2013-
let tip_slot = SIGNATURE_PRUNING_RANGE + 100;
2012+
let tip_slot = BLOCK_PROOF_PRUNING_RANGE + 100;
20142013
let finalized_slot = 5;
20152014
let pruned_below_slot = store
2016-
.prune_old_block_signatures(finalized_slot, tip_slot)
2015+
.prune_old_block_proofs(finalized_slot, tip_slot)
20172016
.expect("prune");
20182017
assert_eq!(pruned_below_slot, 0);
2019-
assert_eq!(count_entries(backend.as_ref(), Table::BlockSignatures), 10);
2018+
assert_eq!(count_entries(backend.as_ref(), Table::BlockProof), 10);
20202019
}
20212020

20222021
#[test]
2023-
fn prune_signatures_noop_when_tip_within_window() {
2022+
fn prune_block_proofs_noop_when_tip_within_window() {
20242023
let backend = Arc::new(InMemoryBackend::new());
20252024
let mut store = Store::test_store_with_backend(backend.clone());
20262025

20272026
for i in 0..10u64 {
20282027
insert_header(backend.as_ref(), root(i), i, H256::ZERO);
20292028
}
20302029

2031-
// Early chain: tip < SIGNATURE_PRUNING_RANGE → cutoff saturates to 0,
2030+
// Early chain: tip < BLOCK_PROOF_PRUNING_RANGE → cutoff saturates to 0,
20322031
// so nothing is old enough to prune even though slots are finalized.
2033-
let pruned_below_slot = store.prune_old_block_signatures(9, 9).expect("prune");
2032+
let pruned_below_slot = store.prune_old_block_proofs(9, 9).expect("prune");
20342033
assert_eq!(pruned_below_slot, 0);
2035-
assert_eq!(count_entries(backend.as_ref(), Table::BlockSignatures), 10);
2034+
assert_eq!(count_entries(backend.as_ref(), Table::BlockProof), 10);
20362035
}
20372036

20382037
// ============ State Diff Reconstruction Tests ============
@@ -2920,7 +2919,7 @@ mod tests {
29202919
assert_eq!(buf.len(), 2);
29212920
}
29222921

2923-
/// `Store::from_anchor_state` writes the header but no `BlockSignatures`
2922+
/// `Store::from_anchor_state` writes the header but no `BlockProof`
29242923
/// row for the slot-0 anchor. `get_signed_block` must synthesize an empty
29252924
/// proof so the genesis block can still be served on BlocksByRoot /
29262925
/// `/lean/v0/blocks/finalized`.
@@ -2940,14 +2939,14 @@ mod tests {
29402939
}
29412940

29422941
/// The synthesis branch must be confined to the slot-0 anchor: a
2943-
/// non-genesis block whose `BlockSignatures` row is missing is treated
2942+
/// non-genesis block whose `BlockProof` row is missing is treated
29442943
/// as storage corruption and surfaces as `None`, not a fabricated block.
29452944
#[test]
2946-
fn get_signed_block_returns_none_for_non_genesis_with_missing_signatures() {
2945+
fn get_signed_block_returns_none_for_non_genesis_with_missing_proof() {
29472946
let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::new());
29482947

29492948
// Hand-insert a slot-1 header (and empty body, via `EMPTY_BODY_ROOT`)
2950-
// but skip the `BlockSignatures` row. This mimics the corruption case
2949+
// but skip the `BlockProof` row. This mimics the corruption case
29512950
// the guard is meant to catch, without going through the normal
29522951
// `insert_signed_block` write path which always writes all three rows.
29532952
let header = BlockHeader {

0 commit comments

Comments
 (0)