Skip to content

Commit ed64021

Browse files
feat: add storage interface with pruning methods (#182)
Fixes #177 I added a few methods that should allow atomically pruning and reorging blocks. Each function should represent a single transaction. We can store trie nodes, reorg, prune in a single transaction now. I kept the bulk insert methods available for storing the current state. --------- Co-authored-by: Arun Dhyani <[email protected]>
1 parent be142ad commit ed64021

File tree

4 files changed

+190
-1
lines changed

4 files changed

+190
-1
lines changed

Cargo.lock

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/exex/external-proofs/Cargo.toml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,21 @@ workspace = true
1414
[dependencies]
1515
reth-exex.workspace = true
1616

17+
# ethereum
18+
alloy-primitives = { workspace = true }
19+
1720
# reth
18-
reth-provider.workspace = true
21+
reth-db-api.workspace = true
1922
reth-node-types.workspace = true
2023
reth-node-api.workspace = true
24+
reth-primitives-traits.workspace = true
25+
reth-provider.workspace = true
26+
reth-trie.workspace = true
2127

2228
# misc
29+
auto_impl.workspace = true
30+
async-trait.workspace = true
2331
eyre.workspace = true
2432
futures-util.workspace = true
33+
serde.workspace = true
34+
thiserror.workspace = true

crates/exex/external-proofs/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ use reth_provider::StateReader;
77

88
use reth_exex::{ExExContext, ExExEvent};
99

10+
mod storage;
11+
1012
/// Saves and serves trie nodes to make proofs faster. This handles the process of
1113
/// saving the current state, new blocks as they're added, and serving proof RPCs
1214
/// based on the saved data.
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
#![expect(dead_code, unreachable_pub)]
2+
use alloy_primitives::{map::HashMap, B256, U256};
3+
use async_trait::async_trait;
4+
use auto_impl::auto_impl;
5+
use reth_primitives_traits::Account;
6+
use reth_trie::{updates::TrieUpdates, BranchNodeCompact, HashedPostState, Nibbles};
7+
use std::fmt::Debug;
8+
use thiserror::Error;
9+
10+
/// Error type for storage operations
11+
#[derive(Debug, Error)]
12+
pub enum ExternalStorageError {
13+
// TODO: add more errors once we know what they are
14+
/// Other error
15+
#[error("Other error: {0}")]
16+
Other(eyre::Error),
17+
}
18+
19+
/// Result type for storage operations
20+
pub type ExternalStorageResult<T> = Result<T, ExternalStorageError>;
21+
22+
/// Seeks and iterates over trie nodes in the database by path (lexicographical order)
23+
pub trait ExternalTrieCursor: Send + Sync {
24+
/// Seek to an exact path, otherwise return None if not found.
25+
fn seek_exact(
26+
&mut self,
27+
path: Nibbles,
28+
) -> ExternalStorageResult<Option<(Nibbles, BranchNodeCompact)>>;
29+
30+
/// Seek to a path, otherwise return the first path greater than the given path
31+
/// lexicographically.
32+
fn seek(
33+
&mut self,
34+
path: Nibbles,
35+
) -> ExternalStorageResult<Option<(Nibbles, BranchNodeCompact)>>;
36+
37+
/// Move the cursor to the next path and return it.
38+
fn next(&mut self) -> ExternalStorageResult<Option<(Nibbles, BranchNodeCompact)>>;
39+
40+
/// Get the current path.
41+
fn current(&mut self) -> ExternalStorageResult<Option<Nibbles>>;
42+
}
43+
44+
/// Seeks and iterates over hashed entries in the database by key.
45+
pub trait ExternalHashedCursor: Send + Sync {
46+
/// Value returned by the cursor.
47+
type Value: std::fmt::Debug;
48+
49+
/// Seek an entry greater or equal to the given key and position the cursor there.
50+
/// Returns the first entry with the key greater or equal to the sought key.
51+
fn seek(&mut self, key: B256) -> ExternalStorageResult<Option<(B256, Self::Value)>>;
52+
53+
/// Move the cursor to the next entry and return it.
54+
fn next(&mut self) -> ExternalStorageResult<Option<(B256, Self::Value)>>;
55+
}
56+
57+
/// Diff of trie updates and post state for a block.
58+
#[derive(Debug, Clone)]
59+
pub struct BlockStateDiff {
60+
pub trie_updates: TrieUpdates,
61+
pub post_state: HashedPostState,
62+
}
63+
64+
/// Trait for reading trie nodes from the database.
65+
///
66+
/// Only leaf nodes and some branch nodes are stored. The bottom layer of branch nodes
67+
/// are not stored to reduce write amplification. This matches Reth's non-historical trie storage.
68+
#[async_trait]
69+
#[auto_impl(Arc)]
70+
pub trait ExternalStorage: Send + Sync + Debug {
71+
type TrieCursor: ExternalTrieCursor;
72+
type StorageCursor: ExternalHashedCursor<Value = U256>;
73+
type AccountHashedCursor: ExternalHashedCursor<Value = Account>;
74+
75+
/// Store a batch of account trie branches. Used for saving existing state. For live state
76+
/// capture, use [store_trie_updates](ExternalStorage::store_trie_updates).
77+
async fn store_account_branches(
78+
&self,
79+
block_number: u64,
80+
updates: Vec<(Nibbles, Option<BranchNodeCompact>)>,
81+
) -> ExternalStorageResult<()>;
82+
83+
/// Store a batch of storage trie branches. Used for saving existing state.
84+
async fn store_storage_branches(
85+
&self,
86+
block_number: u64,
87+
hashed_address: B256,
88+
items: Vec<(Nibbles, Option<BranchNodeCompact>)>,
89+
) -> ExternalStorageResult<()>;
90+
91+
/// Store a batch of account trie leaf nodes. Used for saving existing state.
92+
async fn store_hashed_accounts(
93+
&self,
94+
accounts: Vec<(B256, Option<Account>)>,
95+
block_number: u64,
96+
) -> ExternalStorageResult<()>;
97+
98+
/// Store a batch of storage trie leaf nodes. Used for saving existing state.
99+
async fn store_hashed_storages(
100+
&self,
101+
hashed_address: B256,
102+
storages: Vec<(B256, U256)>,
103+
block_number: u64,
104+
) -> ExternalStorageResult<()>;
105+
106+
/// Get the earliest block number and hash that has been stored
107+
///
108+
/// This is used to determine the block number of trie nodes with block number 0.
109+
/// All earliest block numbers are stored in 0 to reduce updates required to prune trie nodes.
110+
async fn get_earliest_block_number(&self) -> ExternalStorageResult<Option<(u64, B256)>>;
111+
112+
/// Get the latest block number and hash that has been stored
113+
async fn get_latest_block_number(&self) -> ExternalStorageResult<Option<(u64, B256)>>;
114+
115+
/// Get a trie cursor for the storage backend
116+
fn trie_cursor(
117+
&self,
118+
hashed_address: Option<B256>,
119+
max_block_number: u64,
120+
) -> ExternalStorageResult<Self::TrieCursor>;
121+
122+
/// Get a storage cursor for the storage backend
123+
fn storage_hashed_cursor(
124+
&self,
125+
hashed_address: B256,
126+
max_block_number: u64,
127+
) -> ExternalStorageResult<Self::StorageCursor>;
128+
129+
/// Get an account hashed cursor for the storage backend
130+
fn account_hashed_cursor(
131+
&self,
132+
max_block_number: u64,
133+
) -> ExternalStorageResult<Self::AccountHashedCursor>;
134+
135+
/// Store a batch of trie updates.
136+
///
137+
/// If wiped is true, the entire storage trie is wiped, but this is unsupported going forward,
138+
/// so should only happen for legacy reasons.
139+
async fn store_trie_updates(
140+
&self,
141+
block_number: u64,
142+
block_state_diff: BlockStateDiff,
143+
) -> ExternalStorageResult<()>;
144+
145+
/// Fetch all updates for a given block number.
146+
async fn fetch_trie_updates(&self, block_number: u64) -> ExternalStorageResult<BlockStateDiff>;
147+
148+
/// Applies `BlockStateDiff` to the earliest state (updating/deleting nodes) and updates the
149+
/// earliest block number.
150+
async fn prune_earliest_state(
151+
&self,
152+
new_earliest_block_number: u64,
153+
diff: BlockStateDiff,
154+
) -> ExternalStorageResult<()>;
155+
156+
/// Deletes all updates > `latest_common_block_number` and replaces them with the new updates.
157+
async fn replace_updates(
158+
&self,
159+
latest_common_block_number: u64,
160+
blocks_to_add: HashMap<u64, BlockStateDiff>,
161+
) -> ExternalStorageResult<()>;
162+
163+
/// Set the earliest block number and hash that has been stored
164+
async fn set_earliest_block_number(
165+
&self,
166+
block_number: u64,
167+
hash: B256,
168+
) -> ExternalStorageResult<()>;
169+
}

0 commit comments

Comments
 (0)