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 no_rdahead: true,
52 mode: Mode::ReadWrite { sync_mode: SyncMode::Durable },
53 ..Default::default()
54 });
55
56 let env = builder.open(path).wrap_err_with(|| {
57 format!("failed to open slot-preimage MDBX env at {}", path.display())
58 })?;
59
60 {
62 let tx = env.begin_rw_txn()?;
63 let _db = tx.create_db(None, DatabaseFlags::empty())?;
64 tx.commit()?;
65 }
66
67 trace!(target: "stages::slot_preimages", ?path, "Opened slot-preimage store");
68
69 Ok(Self { env })
70 }
71
72 fn insert_preimages(&self, entries: &[(B256, B256)]) -> eyre::Result<()> {
77 let tx = self.env.begin_rw_txn()?;
78 let db = tx.open_db(None)?;
79 let mut cursor = tx.cursor(db.dbi())?;
80
81 for (hashed_slot, plain_slot) in entries {
82 if cursor.set_key::<[u8; 32], [u8; 32]>(hashed_slot.as_slice())?.is_some() {
83 continue;
84 }
85 cursor.put(hashed_slot.as_slice(), plain_slot.as_slice(), WriteFlags::empty())?;
86 }
87
88 tx.commit()?;
89
90 trace!(target: "stages::slot_preimages", count = entries.len(), "Inserted slot preimages");
91
92 Ok(())
93 }
94
95 fn reader(&self) -> eyre::Result<SlotPreimagesReader> {
100 let tx = self.env.begin_ro_txn()?;
101 let dbi = tx.open_db(None)?.dbi();
102 Ok(SlotPreimagesReader { tx, dbi })
103 }
104}
105
106struct SlotPreimagesReader {
108 tx: reth_libmdbx::Transaction<RO>,
109 dbi: reth_libmdbx::ffi::MDBX_dbi,
110}
111
112impl SlotPreimagesReader {
113 fn get(&self, hashed_slot: &B256) -> eyre::Result<Option<B256>> {
115 let result: Option<[u8; 32]> = self.tx.get(self.dbi, hashed_slot.as_ref())?;
116 Ok(result.map(B256::from))
117 }
118}
119
120pub(super) fn inject_plain_wipe_slots<P: DBProvider, R>(
127 slot_preimages_path: &Path,
128 provider: &P,
129 state: &mut ExecutionOutcome<R>,
130) -> Result<(), StageError> {
131 let mut preimage_entries = Vec::new();
134 let mut seen_hashes = HashSet::new();
135 for account in state.bundle.state().values() {
136 for &slot_key in account.storage.keys() {
137 let plain = B256::from(slot_key.to_be_bytes());
138 let hashed = keccak256(plain);
139 if seen_hashes.insert(hashed) {
140 preimage_entries.push((hashed, plain));
141 }
142 }
143 }
144 for block_reverts in state.bundle.reverts.iter() {
145 for (_, revert) in block_reverts {
146 for &slot_key in revert.storage.keys() {
147 let plain = B256::from(slot_key.to_be_bytes());
148 let hashed = keccak256(plain);
149 if seen_hashes.insert(hashed) {
150 preimage_entries.push((hashed, plain));
151 }
152 }
153 }
154 }
155
156 preimage_entries.par_sort_unstable_by_key(|(hash, _)| *hash);
158
159 let preimages = SlotPreimages::open(slot_preimages_path).map_err(fatal)?;
161
162 if !preimage_entries.is_empty() {
163 preimages.insert_preimages(&preimage_entries).map_err(fatal)?;
164 }
165
166 let reader = preimages.reader().map_err(fatal)?;
170
171 for block_reverts in state.bundle.reverts.iter_mut() {
172 for (address, revert) in block_reverts.iter_mut() {
173 if !revert.wipe_storage {
174 continue;
175 }
176
177 let addr = *address;
180 let hashed_address = keccak256(addr);
181 let mut cursor = provider.tx_ref().cursor_dup_read::<tables::HashedStorages>()?;
182
183 if let Some((_, entry)) = cursor.seek_exact(hashed_address)? {
184 inject_preimage_entry(&reader, revert, addr, entry.key, entry.value)?;
185 while let Some(entry) = cursor.next_dup_val()? {
186 inject_preimage_entry(&reader, revert, addr, entry.key, entry.value)?;
187 }
188 }
189 }
190 }
191
192 Ok(())
193}
194
195fn inject_preimage_entry(
198 reader: &SlotPreimagesReader,
199 revert: &mut reth_revm::revm::database::AccountRevert,
200 address: alloy_primitives::Address,
201 hashed_slot: B256,
202 value: alloy_primitives::U256,
203) -> Result<(), StageError> {
204 let plain_slot = reader.get(&hashed_slot).map_err(fatal)?.ok_or_else(|| {
205 fatal(eyre::eyre!("missing slot preimage for {hashed_slot:?} (addr={address:?})"))
206 })?;
207
208 let plain_key = alloy_primitives::U256::from_be_bytes(plain_slot.0);
210 revert
216 .storage
217 .entry(plain_key)
218 .and_modify(|slot| {
219 if matches!(slot, RevertToSlot::Destroyed) {
220 *slot = RevertToSlot::Some(value);
221 }
222 })
223 .or_insert(RevertToSlot::Some(value));
224 Ok(())
225}
226
227#[inline]
228fn fatal<E>(err: E) -> StageError
229where
230 E: Into<Box<dyn std::error::Error + Send + Sync>>,
231{
232 StageError::Fatal(err.into())
233}