Skip to main content

BlockStore

Struct BlockStore 

Source
pub struct BlockStore { /* private fields */ }
Expand description

Primary handle for all block persistence APIs.

§Chia blockchain analogy

This struct corresponds to BlockStore in chia-blockchain/chia/consensus/block_store.py. Where Chia uses a single SQLite full_blocks table with Python LRU caches, DIG uses RocksDB column families with Rust sharded LRU caches for higher throughput under concurrent access. The API surface mirrors Chia’s: add_full_blockput_block, get_full_blockget_block, get_block_recordget_record.

§Ownership

Thin Arc around [BlockStoreInner]: cheap Clone for tokio::task::spawn_blocking dispatch (BLK-007). Field access on &BlockStore transparently reaches [BlockStoreInner] via std::ops::Deref.

Write pipeline (BLK-008): Self::pipeline_tx holds the lazy mpsc::Sender outside [BlockStoreInner]. The worker task also keeps an Arc to inner for RocksDB; if the sender lived on inner, dropping all BlockStore handles would still leave the sender alive (circular retention), the channel would never close, and AC §8 “flush on shutdown” would not run.

§Construction

Use BlockStore::open for read-write access or BlockStore::open_readonly for read-only access to an existing database. After construction, call BlockStore::init_genesis once to initialize a new chain.

Implementations§

Source§

impl BlockStore

Source

pub fn get_hash_by_height( &self, height: u64, ) -> Result<Option<Bytes32>, BlockStoreError>

Look up the canonical block hash at a given chain height.

§Algorithm (dual-layer, CAN-006)
  1. Hot path — mmap (canonical.bin): O(1) pointer-offset read at height * 32. ~10ns when the page is OS-cache-resident. Consulted first.
  2. Cold path — RocksDB (CF_CANONICAL): 1-10us key lookup with big-endian height key. Used when mmap is unavailable, disabled, or doesn’t cover the height.
§Returns
  • Ok(Some(hash)) — height has a canonical block.
  • Ok(None) — height is beyond the chain or was never canonicalized.
§Chia analogy

Corresponds to Blockchain.height_to_hash(height) in Chia, which reads from an in-memory BlockHeightMap bytearray. DIG adds the durable RocksDB fallback.

§Derived methods

get_block_by_height, get_header_by_height, get_record_by_height, and get_epoch_block_hashes all delegate through this.

Source

pub fn set_canonical(&self, hash: &Bytes32) -> Result<(), BlockStoreError>

CAN-003 — Mark an already stored block as canonical at its header height.

Algorithm (normative order — durable first):

  1. Self::get_record to prove the block is known (header row or cache); on miss → BlockStoreError::BlockNotInStore.
  2. DB::put_cf on CF_CANONICAL with height_key(height) → hash_key(hash).
  3. canonical.bin update via the same path as Self::put_block (canonical_bin + CanonicalDenseFile::write_hash); skipped when mmap acceleration is disabled (reopen rebuilds from CF).
  4. Set BlockRecord::in_canonical_chain = true in [Self::record_cache] (record remains RAM-only per TYP-004) — does not change BlockRecord::status; operators may still use Self::update_status for lifecycle.

Idempotency: Re-calling with the same hash overwrites CF/mmap with identical bytes and leaves the record flag true (CAN-003 § Idempotency).

Height collisions: A second call for a different hash at the same height overwrites the height index (reorg staging); both blocks must exist in the store.

Read-only: BlockStoreError::Serialization with ERR_MUTATION_READ_ONLY — same contract as Self::put_block.

Source

pub fn set_canonical_batch( &self, hashes: &[Bytes32], ) -> Result<(), BlockStoreError>

CAN-004 — Promote many already-stored blocks to the canonical height→hash index in one atomic RocksDB commit.

Why a separate API from Self::set_canonical: Reorgs (ROR-003) must flip many heights at once; a single WriteBatch gives all-or-nothing durability in CF_CANONICAL (NORMATIVE § CAN-004).

Algorithm (matches CAN-004 spec — validate, durable batch, then best-effort hot path):

  1. Fail-fast validation: For each input hash (in order), Self::get_record. First miss → BlockStoreError::BlockNotInStore before any WriteBatch mutation so callers never observe partial CF updates from this method.
  2. Atomic CF write: One WriteBatch with all height_key(record.height) → hash_key(hash) rows, then DB::write.
  3. Post-commit: Same as Self::set_canonical — [Self::canonical_bin]’s mmap writer (CanonicalDenseFile::write_hash via extend_write in src/canonical/mmap.rs) per pair, then set BlockRecord::in_canonical_chain in [Self::record_cache] (re-insert on eviction, same as CAN-003).

Empty slice: Ok(())] immediately — no I/O ([CAN-004` acceptance).

Crash window: If the process dies after db.write but before mmap/cache finish, CAN-001 reopen rebuilds canonical.bin from CF_CANONICAL.

Read-only: Same BlockStoreError::Serialization + ERR_MUTATION_READ_ONLY contract as Self::put_block / Self::set_canonical.

Source

pub fn extend_chain(&self, block: &L2Block) -> Result<bool, BlockStoreError>

Primary block ingestion API for normal chain-following operation.

Combines three operations into one call:

  1. StoreSelf::put writes body to CF_BLOCKS, header to CF_HEADERS, and height→hash to CF_CANONICAL (canonical=true).
  2. Tip advanceSelf::set_tip persists the new chain peak to CF_METADATA and updates the in-memory RwLock<Option<ChainTip>>.
§Returns
  • Ok(true) — block was novel; stored, canonicalized, and tip advanced.
  • Ok(false) — block hash was already in the store (duplicate); no changes made.
§Atomicity (CAN-005)

The individual operations are not wrapped in a single RocksDB transaction, but the ordering ensures safe crash recovery:

  • Crash after put but before set_tip: block is stored and canonical, but tip is stale. On restart, tip can be corrected by scanning CF_CANONICAL.
  • The duplicate check via has_block makes re-ingestion safe.
§Chia analogy

Corresponds to the storage portion of Blockchain.receive_blockBlockStore.add_full_block in Chia, where the block is stored, the peak is updated, and the height map is advanced.

§Errors
Source§

impl BlockStore

Compression and serialization methods on BlockStore.

These are impl BlockStore (not BlockStoreInner) because they were originally defined in the impl BlockStore block and callers use Self::serialize_header() which resolves through BlockStore. Field access goes through Deref<Target=BlockStoreInner>.

Source

pub fn serialize_header( header: &L2BlockHeader, ) -> Result<Vec<u8>, BlockStoreError>

Source

pub fn deserialize_header( bytes: &[u8], ) -> Result<L2BlockHeader, BlockStoreError>

Deserialize a header from [CF_HEADERS] bytes (SER-002).

Read path: raw bincode only — callers MUST NOT pass zstd-compressed payloads (those belong in CF_BLOCKS via Self::deserialize_block).

Source

pub fn serialize_block( &self, block: &L2Block, ) -> Result<Vec<u8>, BlockStoreError>

Serialize then zstd-compress a block for CF_BLOCKS (SER-001).

Pipeline: bincode::serializezstd::bulk::Compressor::with_dictionary when [Self::use_compression_dict] and a dictionary are present; otherwise zstd::encode_all (plain zstd).

Errors: BlockStoreError::Serialization from bincode; BlockStoreError::Compression from zstd.

Source

pub fn deserialize_block( &self, compressed: &[u8], ) -> Result<L2Block, BlockStoreError>

Reverse Self::serialize_block (SER-001).

Fallback: Dictionary decompress is attempted first when configured; on failure, plain zstd::decode_all handles pre-dictionary payloads written before training (SER-005).

Hash invariance: Correct payloads MUST yield an L2Block whose L2Block::hash matches the original pre-serialize block (SER-004; verified in tests/ser_004_tests.rs).

Errors: Decompression failures map to BlockStoreError::Serialization so callers see a single “payload unusable” surface for malformed CF_BYTES; bincode structural errors also use Serialization.

Source

pub fn block_count(&self) -> Result<u64, BlockStoreError>

Full scan of CF_BLOCKS to count stored block rows.

Iterates every key in the column family; used by crate::BlockStore::stats and by Self::maybe_train_dictionary to detect when the training threshold is crossed (SER-005).

Source

pub fn init_dictionary(&self) -> Result<(), BlockStoreError>

SER-005 — Reload dictionary bytes from META_ZSTD_DICT into memory after external maintenance (or to align with Self::train_dictionary persistence).

Startup: Self::open already embeds this via [load_zstd_dict_from_db]; public callers use init_dictionary when a second process trains the dictionary or metadata is repaired online.

Source§

impl BlockStore

Source

pub fn stream_blocks_in_range( &self, start: u64, end: u64, ) -> Result<StreamBlocksInRange<'_>, BlockStoreError>

Stream canonical blocks from height start through end inclusive (BLK-006).

Phase 1 — canonical walk: rocksdb::DB::iterator_cf_opt over CF_CANONICAL with ReadOptions::set_readahead_size and iterate bounds (KEY-002 big-endian order).

Phase 2 — lazy bodies: The returned StreamBlocksInRange walks the captured (height, hash) slice and, for each entry, serves ShardedBlockCache hits without RocksDB, or rocksdb::DB::get_cf_opt on CF_BLOCKS with the same readahead hint (separate ReadOptions instance so canonical and block reads each carry the configured hint).

Why two phases: A live RocksDB iterator over CF_CANONICAL cannot coexist with mutable/immutable borrows of block_cache / decompressors on every Iterator::next without self-referential structs; materializing the height→hash list preserves readahead on the canonical scan while keeping the public API safe and 'static-free.

Errors: Missing CF_BLOCKS row for a canonical hash yields BlockStoreError::BlockNotFound from the stream (BLK-006 AC §6). Malformed canonical keys/values map to BlockStoreError::Serialization.

Empty / inverted range: If start > end, returns an iterator that yields immediately without I/O.

Source

pub async fn put_pipelined( &self, block: L2Block, canonical: bool, ) -> Result<Receiver<Result<bool, BlockStoreError>>, BlockStoreError>

Async batched ingest (BLK-008, IMPLEMENTATION_ORDER.md Phase 5).

Channel + batching (NORMATIVE §1–3): Enqueues into a bounded mpsc queue; a background task accumulates up to pipeline_batch_size jobs or until pipeline_flush_ms elapses, then applies one WriteBatch mirroring BlockStore::put_block semantics.

Per-block ack (IMPLEMENTATION_ORDER.md): The returned oneshot::Receiver resolves to the same Result<bool, BlockStoreError> shape as BlockStore::put_block (Ok(true) inserted, Ok(false) duplicate).

Runtime contract: The first call lazily spawns [run_write_pipeline] via tokio::spawn; therefore an active tokio::runtime::Handle must exist (integration tests should use #[tokio::test]).

Source

pub fn pipeline_write_batch_count(&self) -> u64

Count of successful RocksDB WriteBatch commits executed by the BLK-008 worker.

Instrumentation: Used by tests/blk_008_tests.rs to prove AC §4 “single WriteBatch per flush interval”.

Source§

impl BlockStore

Source

pub fn export_snapshot( &self, start_height: u64, end_height: u64, writer: &mut impl Write, ) -> Result<SnapshotManifest, BlockStoreError>

Export canonical blocks in [start_height, end_height] as a snapshot stream.

§Stream format (SNP-001)
  1. SnapshotManifest (bincode-serialized)
  2. For each height: block_len: u32 LE + compressed_block_bytes
  3. SHA-256 checksum (32 bytes) of all preceding bytes

Block bytes are read directly from CF_BLOCKS (pre-compressed) to avoid decompression/recompression overhead.

§Returns

The finalized SnapshotManifest with the computed checksum.

Source

pub fn import_snapshot( &self, reader: &mut impl Read, ) -> Result<SnapshotManifest, BlockStoreError>

Import a snapshot stream, validating manifest, contiguity, parent links, and checksum.

§Algorithm (SNP-002)
  1. Read and validate SnapshotManifest (schema version check).
  2. For each block: read length-prefixed compressed bytes, decompress + deserialize for validation, verify height contiguity and parent-child links, store via put_block.
  3. Verify trailing SHA-256 checksum.
§Returns

The SnapshotManifest read from the stream.

Source§

impl BlockStore

Source

pub fn open(config: BlockStoreConfig) -> Result<Self, BlockStoreError>

Open or create a store at config.path with all column families (STR-004, TYP-008).

Source

pub fn open_readonly(path: impl AsRef<Path>) -> Result<Self, BlockStoreError>

Open an existing database read-only; fails if path does not exist (STR-004).

Source

pub fn init_genesis(&self, block: &L2Block) -> Result<(), BlockStoreError>

Initialize genesis: empty store only; atomic WriteBatch (STR-004).

Source

pub fn disable_canonical_bin_acceleration(&self)

Diagnostics / tests: Disable the mmap acceleration layer so height→hash resolution uses CF_CANONICAL only until the next BlockStore::open (CAN-001 test plan: mmap fallback).

Production: Do not call — the next process restart re-syncs canonical.bin from RocksDB anyway.

Source

pub fn tip(&self) -> Option<ChainTip>

Current chain tip — hash and height of the highest canonical block.

Returns the in-memory cached copy loaded from META_TIP at startup and updated by BlockStore::set_tip, BlockStore::init_genesis, and future extend_chain / rollback_to_height APIs.

§Performance

This is a hot-path accessor queried on every block ingestion for parent-hash validation. The parking_lot::RwLock::read is lock-free on the uncontended fast path (~2-5ns). No RocksDB I/O occurs.

§Chia analogy

Corresponds to BlockStore.get_peak() in Chia’s block_store.py.

Requirement: CAN-007.

Source

pub fn height(&self) -> Option<u64>

Convenience accessor for the current canonical chain height.

Returns tip().map(|t| t.height)None when the store has no tip (before genesis), Some(0) after genesis, Some(n) after extending to height n.

Requirement: CAN-007 § Accessors.

Source

pub fn set_tip(&self, tip: ChainTip) -> Result<(), BlockStoreError>

Persist a new chain tip to CF_METADATA / META_TIP and update the in-memory cache.

§Encoding

The value written is exactly 40 bytes: hash (32 bytes, raw Bytes32) || height (8 bytes, little-endian u64). This matches ChainTip::to_bytes() and the TYP-006 wire format.

§Ordering

RocksDB write is performed before updating the in-memory RwLock. If the write fails, the in-memory tip remains unchanged (no stale state visible to concurrent readers).

§Errors
§Update points (CAN-007)
OperationNew Tip
extend_chain (CAN-005)Newly added block
rollback_to_height (ROR-001)Block at target height
apply_reorg (ROR-003)Last block in new chain
init_genesis (STR-004)Genesis block (height 0)
Source

pub fn warm_blocks_loaded_count(&self) -> usize

Blocks successfully verified present while warming on last Self::open (STR-004 / CAC-006).

Source

pub fn get_block( &self, hash: &Bytes32, ) -> Result<Option<L2Block>, BlockStoreError>

Serialize a block header for CF_HEADERS (SER-002).

Write path (normative): L2BlockHeaderbincode::serialize → raw bytes (no zstd). Headers are small and read on every chain walk; skipping compression avoids framing overhead and decode latency on the hot path (NORMATIVE.md § SER-002).

Errors: BlockStoreError::Serialization — same variant as corrupt block payloads so upper layers can treat “bytes unusable” uniformly until ERR-* adds finer codes.

Write path: Self::put_block / Self::init_genesis insert fresh values so steady-state reads hit RAM.

Source

pub fn has_block(&self, hash: &Bytes32) -> Result<bool, BlockStoreError>

BLK-011 — Whether any persisted row exists for hash without decoding zstd or bincode (NORMATIVE.md § BLK-011).

Cache first (AC §2): [Self::block_cache] and [Self::header_cache] are consulted via ShardedLruCache::contains (LruCache::peek — no LRU promotion).

RocksDB (AC §1): If both caches miss, probe CF_HEADERS then CF_BLOCKS using hash_key (same key layout as Self::put_block). The second probe covers edge cases where only a body row exists; normal Self::put_block writes both families together (BLK-001).

No deserialize / decompress (AC §3): Uses only DB::get_cf presence checks — returned bytes are discarded without calling Self::deserialize_block or Self::deserialize_header.

Instrumentation: This path does not increment Self::cf_blocks_physical_get_count (that counter remains exclusive to Self::get_block) so tests can prove cache-fast paths avoid the heavy read API (BLK-002 counter semantics).

Source

pub fn stats(&self) -> Result<StorageStats, BlockStoreError>

BLK-012 — Aggregate StorageStats for monitoring / diagnostics (TYP-007, NORMATIVE BLK-012).

Row counts: Each *_count field is the number of keys in the corresponding column family from a linear DB::iterator_cf scan (CF_BLOCKS, CF_HEADERS, CF_CANONICAL, CF_CHECKPOINTS, CF_ATTESTED). This is exact for current store sizes (typical node counts) and matches NORMATIVE wording (“reflects the total number of entries”). If full scans become too costly at scale, a future revision may offer rocksdb.estimate-num-keys behind configuration with documented error bounds.

Tip / pruning: StorageStats::tip_height mirrors Self::tip (RAM snapshot loaded from META_TIP on open, updated by Self::init_genesis today). Self::put_block does not yet advance the tip (CAN-007); operators should not assume tip_height == max(block heights) until chain-tip APIs land. StorageStats::min_height reads META_MIN_HEIGHT in CF_METADATA as 8 bytes little-endian u64 (storage_types/NORMATIVE); missing key means no prune watermark yet (PRN-004).

Disk estimate: StorageStats::total_size_bytes sums per-CF RocksDB property rocksdb.estimate-live-data-size (live SST + memtable footprint estimate). It is not a byte-exact du of the directory; callers should treat it as an order-of-magnitude health signal; use Self::flush / Self::compact before relying on filesystem-level durability or space reclamation (BLK-013).

Source

pub fn flush(&self) -> Result<(), BlockStoreError>

BLK-013 — Persist buffered engine state (NORMATIVE BLK-013).

Semantics: First rocksdb::DB::flush_wal(true) so the write-ahead log is synced through the OS to stable storage, then rocksdb::DB::flush to flush all column-family memtables to SST files. Together this matches operators’ “make my recent writes durable” intent while staying close to the BLK-013 spec snippet (which only showed flush() — WAL sync is required by NORMATIVE item 1’s “WAL flush” wording).

Logical state: Does not mutate dig-blockstore caches, tip, or row keys — only RocksDB I/O.

Errors: Any rocksdb::Error maps to BlockStoreError::RocksDb (ERR-002).

Source

pub fn compact(&self) -> Result<(), BlockStoreError>

BLK-013 — Request manual compaction on every column family in crate::constants::ALL_COLUMN_FAMILIES (TYP-001).

Implementation: For each family, DB::compact_range_cf with a None key range compacts the entire keyspace (RocksDB schedules background work). The rust-rocksdb binding returns () from compact_range_cf (errors surface asynchronously); callers use this for space reclamation and read amplification tuning, not as a transactional barrier.

Logical state: Compaction does not delete live keys written by Self::put_block / Self::init_genesis; it merges SSTables. Same error mapping as Self::flush if future APIs gain fallible compaction entry points.

Source

pub fn get_blocks_by_hash( &self, hashes: &[Bytes32], ) -> Result<Vec<Option<L2Block>>, BlockStoreError>

Batch-fetch blocks by hash (BLK-005).

Algorithm

  1. For each input hash in order, clone from [Self::block_cache] when present (CAC-001).
  2. Collect all cache misses; if non-empty, issue one rocksdb::DB::multi_get_cf over CF_BLOCKS (same (cf, key) pattern as Self::get_block, SER-001 payloads).
  3. For each returned blob: Self::deserialize_block, then insert into [Self::block_cache] and [Self::header_cache] (mirrors single-key read-through in Self::get_block).

Ordering: Output Vec index i always corresponds to hashes[i] (per NORMATIVE BLK-005 §5).

Missing keys: Ok(None) at that index; RocksDB row absent still consumes one slot in the multi_get result vector.

Empty input: Returns Ok(vec![]) without touching RocksDB.

Chunking: Very large batches stay single-call for now (BLK-005.md implementation notes); future work may split to bound peak memory.

Source

pub fn invalidate_block_cache_entry(&self, hash: &Bytes32)

Drop a single entry from the in-memory block LRU — no RocksDB writes (BLK-002 test plan: simulate eviction).

Source

pub fn get_block_by_height( &self, height: u64, ) -> Result<Option<L2Block>, BlockStoreError>

Look up the canonical block at height (CAN-006 precursor, BLK-014 building block).

Algorithm: Self::get_hash_by_height (mmap then CF_CANONICAL) → Self::get_block (BLK-002 decompress + cache).

Returns: Ok(None) when the height index is absent or when the hash is indexed but the body row is missing (same as Self::get_block returning None).

Threading: Safe on any thread; performs synchronous RocksDB + zstd work — use Self::get_block_by_height_async from async contexts that must not block the runtime (BLK-007).

Source

pub fn get_blocks_in_range( &self, start_height: u64, end_height: u64, ) -> Result<Vec<L2Block>, BlockStoreError>

BLK-014 — Collect canonical L2Blocks for heights in [start_height, end_height] inclusive (NORMATIVE BLK-014).

Semantics: Ascending height order; start_height > end_height ⇒ empty Vec (not an error); any height with no canonical row or no retrievable body is omitted (gaps and “beyond tip” behave the same — fewer results).

vs Self::stream_blocks_in_range ([BLK-006]): This API eagerly builds a Vec with simple point lookups per height. StreamBlocksInRange is better for large scans (single readahead iterator over CF_CANONICAL).

Source

pub fn get_record_by_height( &self, height: u64, ) -> Result<Option<BlockRecord>, BlockStoreError>

Look up the canonical BlockRecord at height (BLK-015, BLK-004).

Resolution: Same CF_CANONICALBytes32 step as Self::get_block_by_height, then Self::get_record so misses load bincode headers only from CF_HEADERS (SER-002) — no zstd frame read from CF_BLOCKS.

Returns: Ok(None) when the height index is missing or when neither CF_HEADERS nor caches can supply a header.

Canonical resolution: Same Self::get_hash_by_height dual layer as Self::get_block_by_height (CAN-001).

Source

pub fn get_header_by_height( &self, height: u64, ) -> Result<Option<L2BlockHeader>, BlockStoreError>

Look up the canonical header at height (CAN-006).

Algorithm: Self::get_hash_by_heightSelf::get_header (BLK-003 cache + bincode).

Returns: Ok(None) when the height is not canonical or the header row is absent.

Lighter than get_block_by_height: Headers are uncompressed bincode (~700 bytes) versus full block bodies (zstd decompression + larger payload). Use this when only header fields are needed (e.g., parent-hash walks, timestamp checks).

Source

pub fn get_epoch_block_hashes( &self, epoch: u64, ) -> Result<Vec<Bytes32>, BlockStoreError>

Collect canonical block hashes for all heights in the given epoch.

Algorithm: Uses dig_epoch::first_height_in_epoch and dig_epoch::epoch_checkpoint_height to derive the inclusive [start, end] height range, then calls Self::get_hash_by_height for each height. Stops early when a height returns None (chain hasn’t reached that height yet).

Returns: A Vec<Bytes32> containing one hash per canonical height in the epoch, in ascending height order. May be shorter than BLOCKS_PER_EPOCH if the chain is still growing into the epoch, or empty if the epoch is entirely beyond the chain tip.

Requirement: CAN-006 § Epoch Block Hashes.

Source

pub fn get_records_in_range( &self, start_height: u64, end_height: u64, ) -> Result<Vec<BlockRecord>, BlockStoreError>

BLK-015 — Materialize canonical BlockRecords for [start_height, end_height] inclusive (NORMATIVE BLK-015).

Semantics: Matches Self::get_blocks_in_range ordering and gap rules (BLK-014), but each row comes from Self::get_record_by_height so operators avoid zstd decompression on the hot path (BLK-015 § Specification).

Cache interaction: Self::get_record may insert derived rows into [Self::record_cache] / [Self::header_cache]; repeated scans therefore become cheaper, mirroring single-hash lookups (CAC-003 precursor).

Source

pub fn cf_blocks_physical_get_count(&self) -> u64

How many times Self::get_block reached RocksDB CF_BLOCKS after a cache miss (includes Ok(None) probes).

Tests / ops: [tests/blk_002_tests.rs] asserts hits add zero; misses increment exactly once per call.

Source

pub fn cf_blocks_multi_get_batch_count(&self) -> u64

How many times Self::get_blocks_by_hash invoked rocksdb::DB::multi_get_cf because at least one hash missed [Self::block_cache] (BLK-005; see [tests/blk_005_tests.rs]).

Source

pub fn readahead_size(&self) -> usize

RocksDB readahead hint (bytes) copied from BlockStoreConfig::readahead_size at open (BLK-006 AC §4).

Source

pub fn cf_blocks_stream_physical_get_count(&self) -> u64

How many times StreamBlocksInRange issued DB::get_cf_opt against CF_BLOCKS after a block-cache miss while streaming (BLK-006; [tests/blk_006_tests.rs]).

Source

pub fn get_header( &self, hash: &Bytes32, ) -> Result<Option<L2BlockHeader>, BlockStoreError>

Retrieve a block header by hash (BLK-003).

Order: [Self::header_cache] → on miss, get_cf CF_HEADERSSelf::deserialize_header (no zstd; SER-002).

Write path: Self::put_block / Self::init_genesis insert headers in parallel with block bodies.

Source

pub fn invalidate_header_cache_entry(&self, hash: &Bytes32)

Drop one header from the in-memory LRU (BLK-003 tests / future invalidation).

Source

pub fn cf_headers_physical_get_count(&self) -> u64

Count of RocksDB CF_HEADERS get_cf calls from Self::get_header or Self::get_record when the in-memory header and record caches do not already supply the header/record (BLK-003, BLK-004).

Note: Self::get_record consults [Self::header_cache] before touching RocksDB, so a record-cache miss with a warm header cache does not increment this counter (still satisfies “derive from header”).

Source

pub fn put_block( &self, block: &L2Block, canonical: bool, ) -> Result<bool, BlockStoreError>

BLK-001 — Primary name in IMPLEMENTATION_ORDER.md Phase 5.

Pipeline: zstd payload → CF_BLOCKS, bincode header → CF_HEADERS, optional height index → CF_CANONICAL; BlockRecord is derived with BlockStatus::Validated and stored only in [Self::record_cache] (TYP-004 persistence rule).

Idempotency: If the block hash already exists in CF_BLOCKS, returns Ok(false) and performs no writes (start.md hard requirement §9).

SER-005: A successful insert that makes Self::block_count reach [DICT_TRAINING_THRESHOLD] triggers one-time dictionary training when BlockStoreConfig::use_compression_dict is true.

Source

pub fn put( &self, block: &L2Block, canonical: bool, ) -> Result<bool, BlockStoreError>

Alias for Self::put_block — matches the BLK-001 normative snippet name put (NORMATIVE.md § BLK-001).

Source

pub fn min_retained_height(&self) -> Result<u64, BlockStoreError>

Public accessor for the pruning floor: minimum retained block height.

Returns 0 when no pruning has occurred (all heights retained). After [PRN-001] runs, this reflects the META_MIN_HEIGHT value in CF_METADATA.

Requirement: ROR-005. Public accessor for the pruning floor: minimum retained block height.

Returns 0 when no pruning has occurred (all heights retained). Uses the cached AtomicU64 for fast lock-free access ([PRN-004]).

Source

pub fn blocks_to_revert( &self, target_height: u64, ) -> Result<Vec<Bytes32>, BlockStoreError>

Read-only preview: which canonical blocks would be reverted by a rollback to target_height.

Returns hashes in descending height order (tip first). Returns empty Vec when no tip is set or target_height >= tip.height. Does NOT modify any state.

Requirement: ROR-006.

Source

pub fn rollback_to_height( &self, target_height: u64, ) -> Result<Vec<Bytes32>, BlockStoreError>

Revert the canonical chain to target_height, removing higher heights from the canonical index and updating the tip.

§Validation (ROR-005)

Checked in order before any mutation:

  1. NoTip — no chain tip set.
  2. RollbackAboveTiptarget_height > tip.height.
  3. RollbackBelowMintarget_height < min_retained_height().
§Mutation (ROR-001)
  1. Collect reverted hashes from tip down to target_height + 1.
  2. WriteBatch deletes on CF_CANONICAL for each reverted height.
  3. Truncate canonical.bin to (target_height + 1) * 32.
  4. Update tip to the block at target_height.
  5. Mark reverted blocks as non-canonical in record_cache.
  6. Evict reverted heights from canonical_height_cache.
§Returns

Hashes of reverted blocks in descending height order (tip first). Returns empty Vec for a no-op rollback at the current tip. Block data in CF_BLOCKS is NOT deleted (fork preservation per ROR-004).

Source

pub fn apply_reorg( &self, ancestor_height: u64, new_chain_hashes: &[Bytes32], ) -> Result<ReorgResult, BlockStoreError>

Atomically rollback the canonical chain to ancestor_height and re-canonicalize the blocks in new_chain_hashes.

§Algorithm (ROR-003)
  1. Validate: NoTip → error. EmptyReorgChain → error. Each hash in new_chain_hashes must be in the store (BlockNotInStore if not).
  2. WriteBatch (atomic):
    • Delete CF_CANONICAL entries for heights ancestor_height + 1 .. current_tip.height.
    • Put new canonical entries for each hash in new_chain_hashes (height from record).
    • Write new tip (last hash in new_chain_hashes) to META_TIP.
  3. Post-commit:
    • Truncate canonical.bin to ancestor_height, then write new hashes.
    • Update record_cache: reverted → in_canonical_chain=false, applied → true.
    • Update in-memory tip.
    • Evict/update canonical_height_cache.
§Returns

ReorgResult with reverted (descending), applied (ascending), and new_tip.

Source

pub fn find_common_ancestor( &self, hash: &Bytes32, max_depth: u64, ) -> Result<Option<(Bytes32, u64)>, BlockStoreError>

Walk the parent_hash chain from hash backward, returning the first block that is the canonical block at its height.

§Algorithm (ROR-002)

For up to max_depth steps:

  1. Load the BlockRecord for current_hash (cache or CF_HEADERS derive).
  2. Check if get_hash_by_height(record.height) == current_hash — if so, this block is canonical and is the common ancestor.
  3. Otherwise, follow record.parent_hash and repeat.
§Returns
  • Ok(Some((hash, height))) — the first canonical ancestor found.
  • Ok(None) — hash not in store, parent chain broken, or max_depth exceeded.
§Use case

When a new block arrives whose parent is not the current tip, call this with the new block’s parent hash to find where the fork diverged from the canonical chain. The result feeds into apply_reorg (ROR-003) as the ancestor_height.

§Read-only

This method does not modify any state. Safe to call concurrently.

Source

pub fn put_attestation( &self, hash: &Bytes32, attested: &AttestedBlock, ) -> Result<(), BlockStoreError>

BLK-009 — Persist an AttestedBlock under the block’s hash key.

Key: hash_key(hash) — raw 32 bytes in CF_ATTESTED (KEY-001), identical key shape to CF_BLOCKS / CF_HEADERS.

Value: bincode::serialize of attested (uncompressed; attestations are small per BLK-009 implementation notes).

Hash vs payload: Callers normally pass hash == attested.hash(); this method does not verify that invariant so tests and migration tooling can stage rows independently of body presence in CF_BLOCKS.

Overwrite (AC §4): A second call with the same hash replaces the previous value (DB::put_cf).

Read-only: Returns BlockStoreError::Serialization with ERR_MUTATION_READ_ONLY — same contract as Self::put_block.

Source

pub fn get_attestation( &self, hash: &Bytes32, ) -> Result<Option<AttestedBlock>, BlockStoreError>

BLK-009 — Read AttestedBlock bytes from CF_ATTESTED.

Miss: [Ok(None)] when no row exists (AC §3).

No attestation cache (yet): Each call performs a RocksDB get_cf + bincode decode (BLK-009 notes; hot paths may add [CAC-*] later).

Corrupt rows: Malformed bincode surfaces as BlockStoreError::Serialization so operators can distinguish “missing” vs “bad bytes”.

Source

pub fn put_checkpoint( &self, checkpoint: &StoredCheckpoint, ) -> Result<(), BlockStoreError>

Persist a [StoredCheckpoint] to CF_CHECKPOINTS keyed by epoch.

Key: epoch_key(checkpoint.checkpoint.epoch) — 8-byte big-endian. Value: bincode::serialize of the full [StoredCheckpoint]. Idempotent: overwrites any existing checkpoint at the same epoch.

Requirement: CKP-001.

Source

pub fn get_checkpoint( &self, epoch: u64, ) -> Result<Option<StoredCheckpoint>, BlockStoreError>

Retrieve a [StoredCheckpoint] by epoch from CF_CHECKPOINTS.

Returns Ok(None) if no checkpoint exists for the given epoch.

Requirement: CKP-002.

Source

pub fn get_latest_checkpoint( &self, ) -> Result<Option<StoredCheckpoint>, BlockStoreError>

Retrieve the most recent checkpoint (highest epoch) via reverse iterator.

Returns Ok(None) if no checkpoints are stored.

Requirement: CKP-003.

Source

pub fn get_checkpoints_in_range( &self, start_epoch: u64, end_epoch: u64, ) -> Result<Vec<StoredCheckpoint>, BlockStoreError>

Retrieve all checkpoints within an epoch range [start_epoch, end_epoch] inclusive.

Returns empty Vec if no checkpoints exist in the range. If start_epoch > end_epoch, returns empty (no error).

Requirement: CKP-004.

Source

pub fn prune_before_height(&self, height: u64) -> Result<usize, BlockStoreError>

Remove all blocks, headers, attestations, and canonical entries below height.

§Algorithm (PRN-001)
  1. Iterate CF_CANONICAL from min_retained_height to height - 1, collecting hashes.
  2. Also scan CF_HEADERS for non-canonical blocks below height ([PRN-005]).
  3. Single WriteBatch deletes from CF_BLOCKS, CF_HEADERS, CF_ATTESTED, CF_CANONICAL.
  4. Update META_MIN_HEIGHT in the same batch.
  5. Post-commit: evict from all caches, update AtomicU64.
§Returns

Count of blocks pruned (canonical + non-canonical).

Source

pub fn prune_checkpoints_before_epoch( &self, epoch: u64, ) -> Result<usize, BlockStoreError>

Remove all checkpoints with epoch < epoch from CF_CHECKPOINTS.

Returns the count of pruned checkpoints.

Requirement: PRN-002.

Source

pub fn get_record( &self, hash: &Bytes32, ) -> Result<Option<BlockRecord>, BlockStoreError>

Look up BlockRecord by hash (BLK-004).

Order

  1. [Self::record_cache] (Mutex map) — clone on hit; no RocksDB I/O.
  2. [Self::header_cache] — if the header is already deserialized (e.g. after Self::put_block or Self::get_header), derive BlockRecord::from_header with BlockStatus::Validated and insert into the record cache; no RocksDB get_cf on CF_HEADERS.
  3. Else load raw bytes from CF_HEADERS, increment [Self::cf_headers_physical_gets], deserialize via Self::deserialize_header, warm [Self::header_cache] + record cache.

Persistence: BlockRecord is never written to any column family (TYP-004); only headers live under CF_HEADERS.

Read-only stores: Record/header RAM caches start empty; the first lookup may read CF_HEADERS and populate both caches without mutating on-disk layout beyond normal reads.

Source

pub fn invalidate_record_cache_entry(&self, hash: &Bytes32)

Remove one hash from the in-memory BlockRecord map — no RocksDB writes (BLK-004 test plan: simulate record-cache eviction).

Source

pub fn update_status( &self, hash: &Bytes32, status: BlockStatus, ) -> Result<(), BlockStoreError>

BLK-010 — Set BlockRecord::status for a hash already present in [Self::record_cache].

No disk I/O: BlockRecord is cache-only (TYP-004); this method never touches rocksdb::WriteBatch or DB::put_cf.

in_canonical_chain: Recomputed from BlockStatus::is_canonical so the row stays aligned with BlockRecord::from_header (TYP-004 module docs).

Precondition: The hash must already exist in the record cache (typically after Self::put_block or a cache-warming Self::get_record). Otherwise returns BlockStoreError::Serialization whose message starts with ERR_UPDATE_STATUS_RECORD_NOT_CACHED_PREFIX (ERR-001 caps the public enum at thirteen variants, so this uses the same stable-prefix pattern as read-only mutation guards).

Source

pub async fn get_block_async( &self, hash: &Bytes32, ) -> Result<Option<L2Block>, BlockStoreError>

Async retrieval by hash (BLK-007 AC §1, §4, §5).

Hot path: [Self::block_cache] hits return cloned blocks before any .await, so the generated future can complete as [Poll::Ready] on the first poll without scheduling tokio::task::spawn_blocking (NORMATIVE BLK-007 §1–2).

Cold path: Delegates to Self::get_block on the blocking pool so RocksDB + zstd never run on a cooperative tokio worker thread.

Source

pub async fn get_header_async( &self, hash: &Bytes32, ) -> Result<Option<L2BlockHeader>, BlockStoreError>

Async header retrieval (BLK-007 AC §2, §4–5).

Source

pub async fn get_block_by_height_async( &self, height: u64, ) -> Result<Option<L2Block>, BlockStoreError>

Async canonical-height lookup followed by block load (BLK-007 AC §3).

Always spawn_blocking: height→hash uses CF_CANONICAL I/O; per BLK-007 implementation notes this stays on the blocking pool even when the block body would hit [Self::block_cache], avoiding partial “async hits” that still touch RocksDB in the sync prelude.

Trait Implementations§

Source§

impl Clone for BlockStore

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Deref for BlockStore

Source§

type Target = BlockStoreInner

The resulting type after dereferencing.
Source§

fn deref(&self) -> &Self::Target

Dereferences the value.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> Conv for T

Source§

fn conv<T>(self) -> T
where Self: Into<T>,

Converts self into T using Into<T>. Read more
Source§

impl<T> FmtForward for T

Source§

fn fmt_binary(self) -> FmtBinary<Self>
where Self: Binary,

Causes self to use its Binary implementation when Debug-formatted.
Source§

fn fmt_display(self) -> FmtDisplay<Self>
where Self: Display,

Causes self to use its Display implementation when Debug-formatted.
Source§

fn fmt_lower_exp(self) -> FmtLowerExp<Self>
where Self: LowerExp,

Causes self to use its LowerExp implementation when Debug-formatted.
Source§

fn fmt_lower_hex(self) -> FmtLowerHex<Self>
where Self: LowerHex,

Causes self to use its LowerHex implementation when Debug-formatted.
Source§

fn fmt_octal(self) -> FmtOctal<Self>
where Self: Octal,

Causes self to use its Octal implementation when Debug-formatted.
Source§

fn fmt_pointer(self) -> FmtPointer<Self>
where Self: Pointer,

Causes self to use its Pointer implementation when Debug-formatted.
Source§

fn fmt_upper_exp(self) -> FmtUpperExp<Self>
where Self: UpperExp,

Causes self to use its UpperExp implementation when Debug-formatted.
Source§

fn fmt_upper_hex(self) -> FmtUpperHex<Self>
where Self: UpperHex,

Causes self to use its UpperHex implementation when Debug-formatted.
Source§

fn fmt_list(self) -> FmtList<Self>
where &'a Self: for<'a> IntoIterator,

Formats each item in a sequence. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Pipe for T
where T: ?Sized,

Source§

fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> R
where Self: Sized,

Pipes by value. This is generally the method you want to use. Read more
Source§

fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> R
where R: 'a,

Borrows self and passes that borrow into the pipe function. Read more
Source§

fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> R
where R: 'a,

Mutably borrows self and passes that borrow into the pipe function. Read more
Source§

fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
where Self: Borrow<B>, B: 'a + ?Sized, R: 'a,

Borrows self, then passes self.borrow() into the pipe function. Read more
Source§

fn pipe_borrow_mut<'a, B, R>( &'a mut self, func: impl FnOnce(&'a mut B) -> R, ) -> R
where Self: BorrowMut<B>, B: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.borrow_mut() into the pipe function. Read more
Source§

fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
where Self: AsRef<U>, U: 'a + ?Sized, R: 'a,

Borrows self, then passes self.as_ref() into the pipe function.
Source§

fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
where Self: AsMut<U>, U: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.as_mut() into the pipe function.
Source§

fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
where Self: Deref<Target = T>, T: 'a + ?Sized, R: 'a,

Borrows self, then passes self.deref() into the pipe function.
Source§

fn pipe_deref_mut<'a, T, R>( &'a mut self, func: impl FnOnce(&'a mut T) -> R, ) -> R
where Self: DerefMut<Target = T> + Deref, T: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.deref_mut() into the pipe function.
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<P, T> Receiver for P
where P: Deref<Target = T> + ?Sized, T: ?Sized,

Source§

type Target = T

🔬This is a nightly-only experimental API. (arbitrary_self_types)
The target type on which the method may be called.
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> Tap for T

Source§

fn tap(self, func: impl FnOnce(&Self)) -> Self

Immutable access to a value. Read more
Source§

fn tap_mut(self, func: impl FnOnce(&mut Self)) -> Self

Mutable access to a value. Read more
Source§

fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
where Self: Borrow<B>, B: ?Sized,

Immutable access to the Borrow<B> of a value. Read more
Source§

fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
where Self: BorrowMut<B>, B: ?Sized,

Mutable access to the BorrowMut<B> of a value. Read more
Source§

fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
where Self: AsRef<R>, R: ?Sized,

Immutable access to the AsRef<R> view of a value. Read more
Source§

fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
where Self: AsMut<R>, R: ?Sized,

Mutable access to the AsMut<R> view of a value. Read more
Source§

fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
where Self: Deref<Target = T>, T: ?Sized,

Immutable access to the Deref::Target of a value. Read more
Source§

fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
where Self: DerefMut<Target = T> + Deref, T: ?Sized,

Mutable access to the Deref::Target of a value. Read more
Source§

fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self

Calls .tap() only in debug builds, and is erased in release builds.
Source§

fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self

Calls .tap_mut() only in debug builds, and is erased in release builds.
Source§

fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
where Self: Borrow<B>, B: ?Sized,

Calls .tap_borrow() only in debug builds, and is erased in release builds.
Source§

fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
where Self: BorrowMut<B>, B: ?Sized,

Calls .tap_borrow_mut() only in debug builds, and is erased in release builds.
Source§

fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
where Self: AsRef<R>, R: ?Sized,

Calls .tap_ref() only in debug builds, and is erased in release builds.
Source§

fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
where Self: AsMut<R>, R: ?Sized,

Calls .tap_ref_mut() only in debug builds, and is erased in release builds.
Source§

fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
where Self: Deref<Target = T>, T: ?Sized,

Calls .tap_deref() only in debug builds, and is erased in release builds.
Source§

fn tap_deref_mut_dbg<T>(self, func: impl FnOnce(&mut T)) -> Self
where Self: DerefMut<Target = T> + Deref, T: ?Sized,

Calls .tap_deref_mut() only in debug builds, and is erased in release builds.
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T> TryConv for T

Source§

fn try_conv<T>(self) -> Result<T, Self::Error>
where Self: TryInto<T>,

Attempts to convert self into T using TryInto<T>. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more