reth_stages/stages/execution/
slot_preimages.rs1use alloy_primitives::{keccak256, map::HashSet, B256};
2use eyre::Context;
3use rayon::slice::ParallelSliceMut;
4use reth_db::tables;
5use reth_db_api::{
6 cursor::{DbCursorRO, DbDupCursorRO},
7 transaction::DbTx,
8};
9use reth_libmdbx::{
10 DatabaseFlags, Environment, EnvironmentFlags, Geometry, Mode, SyncMode, WriteFlags, RO,
11};
12use reth_provider::{DBProvider, ExecutionOutcome};
13use reth_revm::revm::database::states::RevertToSlot;
14use reth_stages_api::StageError;
15use std::path::Path;
16use tracing::trace;
17
18#[derive(Debug)]
26struct SlotPreimages {
27 env: Environment,
28}
29
30impl SlotPreimages {
31 fn open(path: &Path) -> eyre::Result<Self> {
36 const GIGABYTE: usize = 1024 * 1024 * 1024;
37 const TERABYTE: usize = GIGABYTE * 1024;
38
39 let mut builder = Environment::builder();
40 builder.set_max_dbs(1);
41 let os_page_size = page_size::get().clamp(4096, 0x10000);
42 builder.set_geometry(Geometry {
43 size: Some(0..(8 * TERABYTE)),
44 growth_step: Some(4 * GIGABYTE as isize),
45 shrink_threshold: Some(0),
46 page_size: Some(reth_libmdbx::PageSize::Set(os_page_size)),
47 });
48 builder.write_map();
49 builder.set_flags(EnvironmentFlags {
50 no_sub_dir: false,
51 mode: Mode::ReadWrite { sync_mode: SyncMode::Durable },
52 ..Default::default()
53 });
54
55 let env = builder.open(path).wrap_err_with(|| {
56 format!("failed to open slot-preimage MDBX env at {}", path.display())
57 })?;
58
59 {
61 let tx = env.begin_rw_txn()?;
62 let _db = tx.create_db(None, DatabaseFlags::empty())?;
63 tx.commit()?;
64 }
65
66 trace!(target: "stages::slot_preimages", ?path, "Opened slot-preimage store");
67
68 Ok(Self { env })
69 }
70
71 fn insert_preimages(&self, entries: &[(B256, B256)]) -> eyre::Result<()> {
76 let tx = self.env.begin_rw_txn()?;
77 let db = tx.open_db(None)?;
78 let mut cursor = tx.cursor(db.dbi())?;
79
80 for (hashed_slot, plain_slot) in entries {
81 if cursor.set_key::<[u8; 32], [u8; 32]>(hashed_slot.as_slice())?.is_some() {
82 continue;
83 }
84 cursor.put(hashed_slot.as_slice(), plain_slot.as_slice(), WriteFlags::empty())?;
85 }
86
87 tx.commit()?;
88
89 trace!(target: "stages::slot_preimages", count = entries.len(), "Inserted slot preimages");
90
91 Ok(())
92 }
93
94 fn reader(&self) -> eyre::Result<SlotPreimagesReader> {
99 let tx = self.env.begin_ro_txn()?;
100 let dbi = tx.open_db(None)?.dbi();
101 Ok(SlotPreimagesReader { tx, dbi })
102 }
103}
104
105struct SlotPreimagesReader {
107 tx: reth_libmdbx::Transaction<RO>,
108 dbi: reth_libmdbx::ffi::MDBX_dbi,
109}
110
111impl SlotPreimagesReader {
112 fn get(&self, hashed_slot: &B256) -> eyre::Result<Option<B256>> {
114 let result: Option<[u8; 32]> = self.tx.get(self.dbi, hashed_slot.as_ref())?;
115 Ok(result.map(B256::from))
116 }
117}
118
119pub(super) fn inject_plain_wipe_slots<P: DBProvider, R>(
126 slot_preimages_path: &Path,
127 provider: &P,
128 state: &mut ExecutionOutcome<R>,
129) -> Result<(), StageError> {
130 let mut preimage_entries = Vec::new();
133 let mut seen_hashes = HashSet::new();
134 for account in state.bundle.state().values() {
135 for &slot_key in account.storage.keys() {
136 let plain = B256::from(slot_key.to_be_bytes());
137 let hashed = keccak256(plain);
138 if seen_hashes.insert(hashed) {
139 preimage_entries.push((hashed, plain));
140 }
141 }
142 }
143 for block_reverts in state.bundle.reverts.iter() {
144 for (_, revert) in block_reverts {
145 for &slot_key in revert.storage.keys() {
146 let plain = B256::from(slot_key.to_be_bytes());
147 let hashed = keccak256(plain);
148 if seen_hashes.insert(hashed) {
149 preimage_entries.push((hashed, plain));
150 }
151 }
152 }
153 }
154
155 preimage_entries.par_sort_unstable_by_key(|(hash, _)| *hash);
157
158 let preimages = SlotPreimages::open(slot_preimages_path).map_err(fatal)?;
160
161 if !preimage_entries.is_empty() {
162 preimages.insert_preimages(&preimage_entries).map_err(fatal)?;
163 }
164
165 let reader = preimages.reader().map_err(fatal)?;
169
170 for block_reverts in state.bundle.reverts.iter_mut() {
171 for (address, revert) in block_reverts.iter_mut() {
172 if !revert.wipe_storage {
173 continue;
174 }
175
176 let addr = *address;
179 let hashed_address = keccak256(addr);
180 let mut cursor = provider.tx_ref().cursor_dup_read::<tables::HashedStorages>()?;
181
182 if let Some((_, entry)) = cursor.seek_exact(hashed_address)? {
183 inject_preimage_entry(&reader, revert, addr, entry.key, entry.value)?;
184 while let Some(entry) = cursor.next_dup_val()? {
185 inject_preimage_entry(&reader, revert, addr, entry.key, entry.value)?;
186 }
187 }
188 }
189 }
190
191 Ok(())
192}
193
194fn inject_preimage_entry(
197 reader: &SlotPreimagesReader,
198 revert: &mut reth_revm::revm::database::AccountRevert,
199 address: alloy_primitives::Address,
200 hashed_slot: B256,
201 value: alloy_primitives::U256,
202) -> Result<(), StageError> {
203 let plain_slot = reader.get(&hashed_slot).map_err(fatal)?.ok_or_else(|| {
204 fatal(eyre::eyre!("missing slot preimage for {hashed_slot:?} (addr={address:?})"))
205 })?;
206
207 let plain_key = alloy_primitives::U256::from_be_bytes(plain_slot.0);
209 revert.storage.entry(plain_key).or_insert(RevertToSlot::Some(value));
210 Ok(())
211}
212
213#[inline]
214fn fatal<E>(err: E) -> StageError
215where
216 E: Into<Box<dyn std::error::Error + Send + Sync>>,
217{
218 StageError::Fatal(err.into())
219}