Skip to main content

erigon_seg/
lib.rs

1//! Reader for the Erigon 3 **seg** state file format.
2//!
3//! Erigon stores a snapshot of domain state (accounts, storage, code, …) as a triple
4//! of sibling files sharing one base name, e.g. `v1.1-accounts.0-1024.{kv,bt,kvei}`:
5//!
6//! * **`.kv`** — the data: a `seg`-compressed stream of *words*. For a domain file the
7//!   words alternate `key`, `value`, `key`, `value`, … and the keys are sorted.
8//! * **`.bt`** — a B-tree index whose payload is an Elias-Fano array giving the `.kv`
9//!   byte offset of every key, enabling an `O(log n)` point lookup. Its newer layout also
10//!   carries the key at every `M`-th position, which narrows a lookup to a single
11//!   `M`-key block before any decompression happens (see [`BtreeIndex::nodes`]).
12//! * **`.kvei`** — an existence (bloom) filter: a *negative* accelerator. If it says a
13//!   key is absent, the `.bt` search can be skipped entirely. It never reports a real
14//!   key as absent (no false negatives), so it is safe to trust for the negative case.
15//!
16//! This crate currently implements **reading and querying**. Writing and merging are
17//! planned as later additions.
18//!
19//! A domain's state usually spans several files covering successive step ranges, where a
20//! newer file overrides keys carried by an older one. [`KvStack`] wraps an ordered set of
21//! [`KvReader`]s and resolves point lookups newest-first so overrides win.
22//!
23//! # Quick start
24//!
25//! ```no_run
26//! use erigon_seg::{KvReader, Salt};
27//!
28//! // Open a .kv and (if present) its sibling .bt / .kvei.
29//! let mut r = KvReader::open("v1.1-accounts.0-1024.kv")?;
30//!
31//! // The .kvei bloom needs the index salt to be useful; resolve it once.
32//! r.enable_bloom(Salt::Find(8));
33//!
34//! // Only worth setting when the data is large relative to RAM — see the method docs.
35//! let _ = r.advise_random();
36//!
37//! // Point lookup (bloom-accelerated if enabled, else B-tree binary search).
38//! if let Some(value) = r.get(b"\x00\x01\x02")? {
39//!     println!("value = {} bytes", value.len());
40//! }
41//!
42//! // Or scan every key/value pair sequentially.
43//! for kv in r.iter() {
44//!     let (key, value) = kv?;
45//!     let _ = (key, value);
46//! }
47//! # Ok::<(), erigon_seg::Error>(())
48//! ```
49//!
50//! # Format notes
51//!
52//! The reader handles both released on-disk layouts:
53//!
54//! * `.kv`: the legacy `v0` header (body at offset 0) and the `v1` header (a leading
55//!   `[version, feature-flags]` pair, an optional page-compression byte, and optional
56//!   out-of-band metadata).
57//! * `.bt`: the legacy layout (Elias-Fano at offset 0) and the newer footer layout
58//!   (a trailing `erigon\0\0` magic locating the Elias-Fano section).
59//! * `.kvei`: the `holiman/bloomfilter` layout (`v02\n` magic). The newer "fuse filter"
60//!   layout is detected and skipped (lookups remain correct, just unaccelerated).
61
62// `unsafe` is `deny`-not-`forbid` so the single mmap call site (util.rs) can opt in.
63#![deny(unsafe_code)]
64#![warn(missing_docs)]
65
66mod bloom;
67mod btree;
68mod eliasfano;
69mod error;
70mod hash;
71mod reader;
72mod salt;
73mod seg;
74mod stack;
75mod util;
76mod varint;
77mod writer;
78
79pub use bloom::{ExistenceFilter, FilterKind};
80pub use btree::{BtreeIndex, Nodes};
81pub use eliasfano::EliasFano;
82pub use error::{Error, Result};
83pub use hash::murmur3_x64_128_h1;
84pub use reader::{KvIter, KvReader};
85pub use salt::{Salt, salt_from_file};
86pub use seg::{Getter, OpenOptions, Seg};
87pub use stack::KvStack;
88pub use writer::{
89    BtLayout, BtOptions, DEFAULT_BTREE_M, DomainOptions, DomainPaths, DomainWriter, KveiBuilder,
90    MergeOptions, SegWriter, build_bt, build_bt_from_seg, build_kvei, build_kvei_from_seg, merge,
91};