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)]
29pub struct SlotPreimages {
30 env: Environment,
31}
32
33impl SlotPreimages {
34 pub fn open(path: &Path) -> eyre::Result<Self> {
39 const GIGABYTE: usize = 1024 * 1024 * 1024;
40 const TERABYTE: usize = GIGABYTE * 1024;
41
42 let mut builder = Environment::builder();
43 builder.set_max_dbs(1);
44 let os_page_size = page_size::get().clamp(4096, 0x10000);
45 builder.set_geometry(Geometry {
46 size: Some(0..(8 * TERABYTE)),
47 growth_step: Some(4 * GIGABYTE as isize),
48 shrink_threshold: Some(0),
49 page_size: Some(reth_libmdbx::PageSize::Set(os_page_size)),
50 });
51 builder.write_map();
52 builder.set_flags(EnvironmentFlags {
53 no_sub_dir: false,
54 no_rdahead: true,
55 mode: Mode::ReadWrite { sync_mode: SyncMode::Durable },
56 ..Default::default()
57 });
58
59 let env = builder.open(path).wrap_err_with(|| {
60 format!("failed to open slot-preimage MDBX env at {}", path.display())
61 })?;
62
63 {
65 let tx = env.begin_rw_txn()?;
66 let _db = tx.create_db(None, DatabaseFlags::empty())?;
67 tx.commit()?;
68 }
69
70 trace!(target: "stages::slot_preimages", ?path, "Opened slot-preimage store");
71
72 Ok(Self { env })
73 }
74
75 pub fn insert_preimages(&self, entries: &[(B256, B256)]) -> eyre::Result<()> {
82 let tx = self.env.begin_rw_txn()?;
83 let db = tx.open_db(None)?;
84 let mut cursor = tx.cursor(db.dbi())?;
85
86 for (hashed_slot, plain_slot) in entries {
87 if cursor.set_key::<[u8; 32], [u8; 32]>(hashed_slot.as_slice())?.is_some() {
88 continue;
89 }
90 cursor.put(hashed_slot.as_slice(), plain_slot.as_slice(), WriteFlags::empty())?;
91 }
92
93 tx.commit()?;
94
95 trace!(target: "stages::slot_preimages", count = entries.len(), "Inserted slot preimages");
96
97 Ok(())
98 }
99
100 pub fn reader(&self) -> eyre::Result<SlotPreimagesReader> {
105 let tx = self.env.begin_ro_txn()?;
106 let dbi = tx.open_db(None)?.dbi();
107 Ok(SlotPreimagesReader { tx, dbi })
108 }
109}
110
111#[derive(Debug)]
113pub struct SlotPreimagesReader {
114 tx: reth_libmdbx::Transaction<RO>,
115 dbi: reth_libmdbx::ffi::MDBX_dbi,
116}
117
118impl SlotPreimagesReader {
119 pub fn get(&self, hashed_slot: &B256) -> eyre::Result<Option<B256>> {
121 let result: Option<[u8; 32]> = self.tx.get(self.dbi, hashed_slot.as_ref())?;
122 Ok(result.map(B256::from))
123 }
124}
125
126pub(super) fn inject_plain_wipe_slots<P: DBProvider, R>(
133 slot_preimages_path: &Path,
134 provider: &P,
135 state: &mut ExecutionOutcome<R>,
136) -> Result<(), StageError> {
137 let mut preimage_entries = Vec::new();
140 let mut seen_hashes = HashSet::new();
141 for account in state.bundle.state().values() {
142 for &slot_key in account.storage.keys() {
143 let plain = B256::from(slot_key.to_be_bytes());
144 let hashed = keccak256(plain);
145 if seen_hashes.insert(hashed) {
146 preimage_entries.push((hashed, plain));
147 }
148 }
149 }
150 for block_reverts in state.bundle.reverts.iter() {
151 for (_, revert) in block_reverts {
152 for &slot_key in revert.storage.keys() {
153 let plain = B256::from(slot_key.to_be_bytes());
154 let hashed = keccak256(plain);
155 if seen_hashes.insert(hashed) {
156 preimage_entries.push((hashed, plain));
157 }
158 }
159 }
160 }
161
162 preimage_entries.par_sort_unstable_by_key(|(hash, _)| *hash);
164
165 let preimages = SlotPreimages::open(slot_preimages_path).map_err(fatal)?;
167
168 if !preimage_entries.is_empty() {
169 preimages.insert_preimages(&preimage_entries).map_err(fatal)?;
170 }
171
172 let reader = preimages.reader().map_err(fatal)?;
176
177 for block_reverts in state.bundle.reverts.iter_mut() {
178 for (address, revert) in block_reverts.iter_mut() {
179 if !revert.wipe_storage {
180 continue;
181 }
182
183 let addr = *address;
186 let hashed_address = keccak256(addr);
187 let mut cursor = provider.tx_ref().cursor_dup_read::<tables::HashedStorages>()?;
188
189 if let Some((_, entry)) = cursor.seek_exact(hashed_address)? {
190 inject_preimage_entry(&reader, revert, addr, entry.key, entry.value)?;
191 while let Some(entry) = cursor.next_dup_val()? {
192 inject_preimage_entry(&reader, revert, addr, entry.key, entry.value)?;
193 }
194 }
195 }
196 }
197
198 Ok(())
199}
200
201fn inject_preimage_entry(
204 reader: &SlotPreimagesReader,
205 revert: &mut reth_revm::revm::database::AccountRevert,
206 address: alloy_primitives::Address,
207 hashed_slot: B256,
208 value: alloy_primitives::U256,
209) -> Result<(), StageError> {
210 let plain_slot = reader.get(&hashed_slot).map_err(fatal)?.ok_or_else(|| {
211 fatal(eyre::eyre!("missing slot preimage for {hashed_slot:?} (addr={address:?})"))
212 })?;
213
214 let plain_key = alloy_primitives::U256::from_be_bytes(plain_slot.0);
216 revert
222 .storage
223 .entry(plain_key)
224 .and_modify(|slot| {
225 if matches!(slot, RevertToSlot::Destroyed) {
226 *slot = RevertToSlot::Some(value);
227 }
228 })
229 .or_insert(RevertToSlot::Some(value));
230 Ok(())
231}
232
233#[inline]
234fn fatal<E>(err: E) -> StageError
235where
236 E: Into<Box<dyn std::error::Error + Send + Sync>>,
237{
238 StageError::Fatal(err.into())
239}