1#[cfg(feature = "rocksdb")]
2use crate::backend::rocksdb::RocksDBBackend;
3use crate::{
4 STORE_METADATA_FILENAME, STORE_SCHEMA_VERSION,
5 api::{
6 StorageBackend, StorageReadView, StorageWriteBatch,
7 tables::{
8 ACCOUNT_CODE_METADATA, ACCOUNT_CODES, ACCOUNT_FLATKEYVALUE, ACCOUNT_TRIE_NODES,
9 BAD_BLOCKS, BLOCK_ACCESS_LISTS, BLOCK_NUMBERS, BODIES, CANONICAL_BLOCK_HASHES,
10 CHAIN_DATA, EXECUTION_WITNESSES, FULLSYNC_HEADERS, HEADERS, INVALID_CHAINS,
11 MISC_VALUES, PENDING_BLOCKS, RECEIPTS_V2, SNAP_STATE, STATE_HISTORY,
12 STORAGE_FLATKEYVALUE, STORAGE_TRIE_NODES, TRANSACTION_LOCATIONS,
13 },
14 },
15 apply_prefix,
16 backend::in_memory::InMemoryBackend,
17 block_data_buffer::BlockDataBuffer,
18 error::StoreError,
19 journal::{FlatDiff, JournalEntry},
20 layering::{Overlay, OverlayCf, TrieLayerCache, TrieWrapper},
21 rlp::{BlockBodyRLP, BlockHeaderRLP, BlockRLP},
22 trie::{BackendTrieDB, BackendTrieDBLocked, classify_trie_key},
23 utils::{ChainDataIndex, SnapStateIndex},
24};
25
26use ethrex_common::{
27 Address, H256, U256,
28 types::{
29 AccountInfo, AccountState, AccountUpdate, Block, BlockBody, BlockHash, BlockHeader,
30 BlockNumber, ChainConfig, Code, CodeMetadata, ForkId, Genesis, GenesisAccount, Index,
31 Receipt, Transaction,
32 block_access_list::BlockAccessList,
33 block_execution_witness::{ExecutionWitness, RpcExecutionWitness},
34 },
35 utils::keccak,
36};
37use ethrex_crypto::{NativeCrypto, keccak::keccak_hash};
38use ethrex_rlp::{
39 decode::{RLPDecode, decode_bytes, decode_rlp_item},
40 encode::RLPEncode,
41};
42use ethrex_trie::{EMPTY_TRIE_HASH, Nibbles, Trie, TrieLogger, TrieNode, TrieWitness};
43use ethrex_trie::{Node, NodeRLP};
44use indexmap::IndexMap;
45use lru::LruCache;
46use rayon::prelude::*;
47use rustc_hash::FxBuildHasher;
48use serde::{Deserialize, Serialize};
49use std::{
50 collections::{BTreeMap, HashMap, HashSet, hash_map::Entry},
51 fmt::Debug,
52 hash::BuildHasher,
53 io::Write,
54 num::NonZeroUsize,
55 path::{Path, PathBuf},
56 sync::{
57 Arc, Condvar, Mutex, RwLock,
58 atomic::{AtomicUsize, Ordering},
59 mpsc::{SyncSender, TryRecvError, sync_channel},
60 },
61 thread::JoinHandle,
62};
63use tracing::{debug, error, info, warn};
64
65pub const MAX_WITNESSES: u64 = 128;
67
68pub const DB_COMMIT_THRESHOLD: usize = 128;
72const IN_MEMORY_COMMIT_THRESHOLD: usize = 10000;
73
74pub const BATCH_COMMIT_THRESHOLD: usize = 4;
80
81const KEYS_PER_SHARD: usize = 256;
88const MAX_SHARDS: usize = 64;
89
90fn shard_count(n: usize) -> usize {
92 n.div_ceil(KEYS_PER_SHARD).clamp(1, MAX_SHARDS)
93}
94
95pub const MAX_ROCKSDB_BLOCK_CACHE_SIZE_BYTES: usize = 12 * 1024 * 1024 * 1024;
105
106pub const MIN_ROCKSDB_BLOCK_CACHE_SIZE_BYTES: usize = 512 * 1024 * 1024;
114
115pub const ROCKSDB_BLOCK_CACHE_MEMORY_PERCENT: usize = 40;
124
125pub fn default_rocksdb_block_cache_size() -> usize {
137 let physical = physical_memory_bytes();
138 let cgroup = cgroup_memory_limit_bytes();
139 let limit = [physical, cgroup].into_iter().flatten().min();
140 let size = rocksdb_block_cache_size_for(limit);
141 let source = match (physical, cgroup) {
146 (None, None) => "undetected, falling back to the ceiling",
147 (Some(_), None) => "physical memory",
148 (None, Some(_)) => "cgroup limit",
149 (Some(p), Some(c)) => {
150 if c <= p {
151 "cgroup limit"
152 } else {
153 "physical memory"
154 }
155 }
156 };
157 info!(
158 detected_limit_bytes = ?limit,
159 source,
160 cache_bytes = size,
161 "sized RocksDB block cache"
162 );
163 size
164}
165
166pub fn rocksdb_block_cache_size_for(memory_limit: Option<usize>) -> usize {
169 let Some(limit) = memory_limit else {
170 return MAX_ROCKSDB_BLOCK_CACHE_SIZE_BYTES;
171 };
172 (limit.saturating_mul(ROCKSDB_BLOCK_CACHE_MEMORY_PERCENT) / 100).clamp(
173 MIN_ROCKSDB_BLOCK_CACHE_SIZE_BYTES,
174 MAX_ROCKSDB_BLOCK_CACHE_SIZE_BYTES,
175 )
176}
177
178fn physical_memory_bytes() -> Option<usize> {
180 let meminfo = std::fs::read_to_string("/proc/meminfo").ok()?;
181 let kib: usize = meminfo
182 .lines()
183 .find_map(|line| line.strip_prefix("MemTotal:"))?
184 .split_whitespace()
185 .next()?
186 .parse()
187 .ok()?;
188 kib.checked_mul(1024)
189}
190
191fn cgroup_memory_limit_bytes() -> Option<usize> {
203 let cgroups = std::fs::read_to_string("/proc/self/cgroup").ok()?;
204 let v2 = cgroups
205 .lines()
206 .find_map(|line| line.strip_prefix("0::"))
207 .and_then(|relative| {
208 cgroup_limit_up_to_root(Path::new("/sys/fs/cgroup"), relative, "memory.max")
209 });
210 v2.or_else(|| {
211 let relative = cgroups.lines().find_map(|line| {
212 let mut fields = line.splitn(3, ':');
214 let controllers = fields.nth(1)?;
215 let path = fields.next()?;
216 controllers
217 .split(',')
218 .any(|controller| controller == "memory")
219 .then_some(path)
220 })?;
221 cgroup_limit_up_to_root(
222 Path::new("/sys/fs/cgroup/memory"),
223 relative,
224 "memory.limit_in_bytes",
225 )
226 })
227}
228
229fn cgroup_limit_up_to_root(mount: &Path, relative: &str, file: &str) -> Option<usize> {
232 let mut dir = mount.join(relative.trim_start_matches('/'));
233 let mut limit: Option<usize> = None;
234 loop {
235 if let Some(value) = std::fs::read_to_string(dir.join(file))
236 .ok()
237 .and_then(|contents| contents.trim().parse::<usize>().ok())
238 {
239 limit = Some(limit.map_or(value, |current| current.min(value)));
240 }
241 if dir == mount || !dir.pop() {
242 return limit;
243 }
244 }
245}
246
247#[derive(Debug, Clone, Copy)]
252pub struct StoreConfig {
253 pub rocksdb_block_cache_size: usize,
258 pub code_cache_size: Option<u64>,
260 pub persist_channel_capacity: usize,
265}
266
267impl StoreConfig {
268 pub fn with_rocksdb_block_cache_size(rocksdb_block_cache_size: usize) -> Self {
273 Self {
274 rocksdb_block_cache_size,
275 code_cache_size: None,
276 persist_channel_capacity: DEFAULT_PERSIST_CHANNEL_CAPACITY,
277 }
278 }
279}
280
281impl Default for StoreConfig {
282 fn default() -> Self {
283 Self::with_rocksdb_block_cache_size(default_rocksdb_block_cache_size())
284 }
285}
286
287#[derive(Debug, PartialEq)]
289enum FKVGeneratorControlMessage {
290 Stop,
291 Continue,
292}
293
294const CODE_CACHE_MAX_SIZE: u64 = 2 * 1024 * 1024 * 1024;
307
308const CODE_METADATA_CACHE_MAX_ENTRIES: usize = (16 * 1024 * 1024) / 64;
316
317const FLUSHED_UPTO_KEY: &[u8] = b"bodies_flushed_upto";
319
320const BAD_BLOCKS_KEY: &[u8] = b"bad_blocks";
322
323const MAX_BAD_BLOCKS: usize = 16;
325
326const CODE_CACHE_ENTRY_OVERHEAD: u64 = 160;
332
333struct CodeCache {
341 entries: IndexMap<H256, Code, FxBuildHasher>,
342 cache_size: u64,
343 max_size: u64,
344 rng: u64,
346}
347
348impl std::fmt::Debug for CodeCache {
351 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
352 f.debug_struct("CodeCache")
353 .field("len", &self.entries.len())
354 .field("cache_size", &self.cache_size)
355 .field("max_size", &self.max_size)
356 .finish_non_exhaustive()
357 }
358}
359
360impl Default for CodeCache {
361 fn default() -> Self {
362 Self {
363 entries: IndexMap::default(),
364 cache_size: 0,
365 max_size: CODE_CACHE_MAX_SIZE,
366 rng: std::collections::hash_map::RandomState::new().hash_one(0u64) | 1,
368 }
369 }
370}
371
372impl CodeCache {
373 fn entry_size(code: &Code) -> u64 {
375 (code.size() as u64).saturating_add(CODE_CACHE_ENTRY_OVERHEAD)
376 }
377
378 fn get(&self, code_hash: &H256) -> Result<Option<Code>, StoreError> {
379 Ok(self.entries.get(code_hash).cloned())
380 }
381
382 fn contains(&self, code_hash: &H256) -> bool {
383 self.entries.contains_key(code_hash)
384 }
385
386 fn insert(&mut self, code: &Code) -> Result<(), StoreError> {
387 if self.entries.contains_key(&code.hash) {
390 return Ok(());
391 }
392 let size = Self::entry_size(code);
395 if size > self.max_size {
396 return Ok(());
397 }
398
399 while self.cache_size + size > self.max_size && !self.entries.is_empty() {
402 let victim = (self.next_random() % self.entries.len() as u64) as usize;
403 if let Some((_, evicted)) = self.entries.swap_remove_index(victim) {
404 self.cache_size -= Self::entry_size(&evicted);
405 }
406 }
407 self.cache_size += size;
408 self.entries.insert(code.hash, code.clone());
409
410 let cache_len = self.entries.len();
411 let current_size = self.cache_size;
412 debug!(
413 "[ACCOUNT CODE CACHE] cache elements (): {cache_len}, total size: {current_size} bytes"
414 );
415 Ok(())
416 }
417
418 fn next_random(&mut self) -> u64 {
419 self.rng ^= self.rng >> 12;
420 self.rng ^= self.rng << 25;
421 self.rng ^= self.rng >> 27;
422 self.rng.wrapping_mul(0x2545_F491_4F6C_DD1D)
423 }
424}
425
426#[derive(Debug, Clone)]
432pub struct Store {
433 db_path: PathBuf,
435 backend: Arc<dyn StorageBackend>,
437 chain_config: ChainConfig,
439 trie_cache: Arc<RwLock<Arc<TrieLayerCache>>>,
441 flatkeyvalue_control_tx: std::sync::mpsc::SyncSender<FKVGeneratorControlMessage>,
443 block_data_buffer: Arc<RwLock<Arc<BlockDataBuffer>>>,
445 persist_tx: std::sync::mpsc::SyncSender<PersistMessage>,
449 pending_trie_roots: Arc<PendingTrieRoots>,
453 latest_block_header: LatestBlockHeaderCache,
456 last_computed_flatkeyvalue: Arc<RwLock<Vec<u8>>>,
458
459 account_code_cache: Arc<Mutex<CodeCache>>,
464
465 code_metadata_cache: Arc<Mutex<LruCache<H256, CodeMetadata, FxBuildHasher>>>,
471
472 fcu_lock: Arc<tokio::sync::Mutex<()>>,
475
476 safe_commit_root: Arc<RwLock<H256>>,
482
483 journal_pruning_paused: Arc<std::sync::atomic::AtomicBool>,
493
494 background_threads: Arc<ThreadList>,
495}
496
497#[derive(Debug, Default)]
498struct ThreadList {
499 list: Vec<JoinHandle<()>>,
500}
501
502impl Drop for ThreadList {
503 fn drop(&mut self) {
504 for handle in self.list.drain(..) {
505 let _ = handle.join();
506 }
507 }
508}
509
510pub type StorageTrieNodes = Vec<(H256, Vec<(Nibbles, Vec<u8>)>)>;
515type StorageTries = HashMap<Address, (TrieWitness, Trie)>;
516
517#[derive(Debug, Clone, Copy, PartialEq, Eq)]
521pub enum EngineType {
522 InMemory,
524 #[cfg(feature = "rocksdb")]
526 RocksDB,
527}
528
529pub struct UpdateBatch {
534 pub account_updates: Vec<TrieNode>,
536 pub storage_updates: Vec<(H256, Vec<TrieNode>)>,
538 pub blocks: Vec<Block>,
540 pub receipts: Vec<(H256, Vec<Receipt>)>,
542 pub code_updates: Vec<(H256, Code)>,
544 pub commit_depth: Option<usize>,
552 pub wait_for_flush: bool,
563}
564
565pub struct BackfilledBlock {
573 pub header: BlockHeader,
574 pub body: BlockBody,
575 pub receipts: Vec<Receipt>,
576 pub index_transactions: bool,
577}
578
579pub type StorageUpdates = Vec<(H256, Vec<(Nibbles, Vec<u8>)>)>;
581
582pub struct AccountUpdatesList {
587 pub state_trie_hash: H256,
589 pub state_updates: Vec<(Nibbles, Vec<u8>)>,
591 pub storage_updates: StorageUpdates,
593 pub code_updates: Vec<(H256, Code)>,
595}
596
597pub(crate) fn encode_tx_location_operand(
608 block_number: BlockNumber,
609 block_hash: BlockHash,
610 index: Index,
611) -> Vec<u8> {
612 vec![(block_number, block_hash, index)].encode_to_vec()
613}
614
615pub fn tx_locations_merge(
633 existing: Option<&[u8]>,
634 operands: impl IntoIterator<Item = impl AsRef<[u8]>>,
635) -> Option<Vec<u8>> {
636 fn fold_chunk(
640 list: &mut Vec<(BlockNumber, BlockHash, Index)>,
641 bytes: &[u8],
642 what: &str,
643 ) -> bool {
644 match <Vec<(BlockNumber, BlockHash, Index)>>::decode(bytes) {
645 Ok(entries) => {
646 for (bn, bh, idx) in entries {
647 list.retain(|(_, existing_bh, _)| *existing_bh != bh);
648 list.push((bn, bh, idx));
649 }
650 true
651 }
652 Err(e) => {
653 error!(
654 "tx_locations_merge: failed to decode {what} ({} bytes): {e}; \
655 aborting merge to avoid silent data loss",
656 bytes.len()
657 );
658 false
659 }
660 }
661 }
662
663 let mut list: Vec<(BlockNumber, BlockHash, Index)> = Vec::new();
664
665 if let Some(bytes) = existing
667 && !fold_chunk(&mut list, bytes, "existing value")
668 {
669 return None;
670 }
671 for op in operands {
672 if !fold_chunk(&mut list, op.as_ref(), "operand") {
673 return None;
674 }
675 }
676 Some(list.encode_to_vec())
677}
678
679impl Store {
680 pub async fn wait_for_persistence_idle(&self) -> Result<(), StoreError> {
691 let tx = self.persist_tx.clone();
692 tokio::task::spawn_blocking(move || {
693 let (ack_tx, ack_rx) = sync_channel::<Result<(), StoreError>>(1);
694 tx.send(PersistMessage::Ping(ack_tx))
695 .map_err(|e| StoreError::Custom(format!("wait_for_persistence_idle send: {e}")))?;
696 ack_rx
697 .recv()
698 .map_err(|e| StoreError::Custom(format!("wait_for_persistence_idle ack: {e}")))?
699 })
700 .await
701 .map_err(|e| StoreError::Custom(format!("wait_for_persistence_idle join: {e}")))?
702 }
703
704 pub async fn shutdown(&self) -> Result<(), StoreError> {
723 let tx = self.persist_tx.clone();
724 let backend = self.backend.clone();
725 tokio::task::spawn_blocking(move || {
726 let (ack_tx, ack_rx) = sync_channel::<Result<(), StoreError>>(1);
727 tx.send(PersistMessage::Shutdown { ack: ack_tx })
728 .map_err(|e| StoreError::Custom(format!("shutdown send: {e}")))?;
729 ack_rx
730 .recv()
731 .map_err(|e| StoreError::Custom(format!("shutdown ack: {e}")))??;
732 backend.flush()
735 })
736 .await
737 .map_err(|e| StoreError::Custom(format!("shutdown join: {e}")))?
738 }
739
740 pub async fn add_block(&self, block: Block) -> Result<(), StoreError> {
743 self.add_blocks(vec![block]).await
744 }
745
746 pub async fn add_blocks(&self, blocks: Vec<Block>) -> Result<(), StoreError> {
749 let db = self.backend.clone();
750 tokio::task::spawn_blocking(move || {
751 let mut tx = db.begin_write()?;
752
753 for block in blocks {
754 write_block_data(
755 tx.as_mut(),
756 block.header.number,
757 block.hash(),
758 &block.header,
759 &block.body,
760 )?;
761 }
762
763 tx.commit()
764 })
765 .await
766 .map_err(|e| StoreError::Custom(format!("Task panicked: {}", e)))?
767 }
768
769 pub async fn add_block_header(
771 &self,
772 block_hash: BlockHash,
773 block_header: BlockHeader,
774 ) -> Result<(), StoreError> {
775 let hash_key = block_hash.encode_to_vec();
776 let header_value = BlockHeaderRLP::from(block_header).into_vec();
777 self.write_async(HEADERS, hash_key, header_value).await
778 }
779
780 pub async fn add_block_headers(
782 &self,
783 block_headers: Vec<BlockHeader>,
784 ) -> Result<(), StoreError> {
785 let mut txn = self.backend.begin_write()?;
786
787 for header in block_headers {
788 let block_hash = header.hash();
789 let block_number = header.number;
790 let hash_key = block_hash.encode_to_vec();
791 let header_value = BlockHeaderRLP::from(header).into_vec();
792
793 txn.put(HEADERS, &hash_key, &header_value)?;
794
795 let number_key = block_number.to_le_bytes().to_vec();
796 txn.put(BLOCK_NUMBERS, &hash_key, &number_key)?;
797 }
798 txn.commit()?;
799 Ok(())
800 }
801
802 pub fn get_block_header(
804 &self,
805 block_number: BlockNumber,
806 ) -> Result<Option<BlockHeader>, StoreError> {
807 let latest = self.latest_block_header.get();
808 if block_number == latest.number {
809 return Ok(Some((*latest).clone()));
810 }
811 let Some(block_hash) = self.get_canonical_block_hash_sync(block_number)? else {
816 return Ok(None);
817 };
818 self.get_block_header_by_hash(block_hash)
819 }
820
821 pub async fn add_block_body(
823 &self,
824 block_hash: BlockHash,
825 block_body: BlockBody,
826 ) -> Result<(), StoreError> {
827 let hash_key = block_hash.encode_to_vec();
828 let body_value = BlockBodyRLP::from(block_body).into_vec();
829 self.write_async(BODIES, hash_key, body_value).await
830 }
831
832 pub async fn add_backfilled_blocks(
850 &self,
851 blocks: Vec<BackfilledBlock>,
852 new_earliest: BlockNumber,
853 ) -> Result<(), StoreError> {
854 let backend = self.backend.clone();
855 tokio::task::spawn_blocking(move || {
856 let mut txn = backend.begin_write()?;
857 for block in &blocks {
858 let hash = block.header.hash();
859 let hash_key = hash.encode_to_vec();
860 txn.put(
861 BODIES,
862 &hash_key,
863 &BlockBodyRLP::from(block.body.clone()).into_vec(),
864 )?;
865 for (index, receipt) in block.receipts.iter().enumerate() {
866 txn.put(
867 RECEIPTS_V2,
868 &receipt_key(&hash, index as u64),
869 &receipt.encode_storage(),
870 )?;
871 }
872 if block.index_transactions {
873 for (index, transaction) in block.body.transactions.iter().enumerate() {
874 txn.merge(
875 TRANSACTION_LOCATIONS,
876 transaction.hash(&NativeCrypto).as_bytes(),
877 &encode_tx_location_operand(block.header.number, hash, index as u64),
878 )?;
879 }
880 }
881 }
882 txn.put(
883 CHAIN_DATA,
884 &chain_data_key(ChainDataIndex::EarliestBlockNumber),
885 &new_earliest.to_le_bytes(),
886 )?;
887 txn.commit()
888 })
889 .await
890 .map_err(|e| StoreError::Custom(format!("Backfill write task panicked: {e}")))?
891 }
892
893 pub fn rocksdb_stats(&self) -> Option<crate::api::RocksDbStats> {
897 self.backend.db_stats()
898 }
899
900 pub async fn get_block_body(
902 &self,
903 block_number: BlockNumber,
904 ) -> Result<Option<BlockBody>, StoreError> {
905 let Some(block_hash) = self.get_canonical_block_hash_sync(block_number)? else {
906 return Ok(None);
907 };
908
909 self.get_block_body_by_hash(block_hash).await
910 }
911
912 pub async fn remove_block(&self, block_number: BlockNumber) -> Result<(), StoreError> {
914 let Some(hash) = self.get_canonical_block_hash_sync(block_number)? else {
915 return Ok(());
916 };
917
918 let backend = self.backend.clone();
919 tokio::task::spawn_blocking(move || {
920 let hash_key = hash.encode_to_vec();
921
922 let mut txn = backend.begin_write()?;
923 txn.delete(
924 CANONICAL_BLOCK_HASHES,
925 block_number.to_le_bytes().as_slice(),
926 )?;
927 txn.delete(BODIES, &hash_key)?;
928 txn.delete(HEADERS, &hash_key)?;
929 txn.delete(BLOCK_NUMBERS, &hash_key)?;
930 txn.commit()
931 })
932 .await
933 .map_err(|e| StoreError::Custom(format!("Task panicked: {}", e)))?
934 }
935
936 pub async fn get_block_bodies(
938 &self,
939 from: BlockNumber,
940 to: BlockNumber,
941 ) -> Result<Vec<Option<BlockBody>>, StoreError> {
942 let buffer = self.buffer()?;
944 let backend = self.backend.clone();
945 tokio::task::spawn_blocking(move || {
946 let numbers: Vec<BlockNumber> = (from..=to).collect();
947 let mut block_bodies = Vec::new();
948
949 let txn = backend.begin_read()?;
950 for number in numbers {
951 let Some(hash) = txn
952 .get(CANONICAL_BLOCK_HASHES, number.to_le_bytes().as_slice())?
953 .map(|bytes| H256::decode(bytes.as_slice()))
954 .transpose()?
955 else {
956 block_bodies.push(None);
957 continue;
958 };
959 if let Some(body) = buffer.get_body(&hash) {
962 block_bodies.push(Some(body));
963 continue;
964 }
965 let hash_key = hash.encode_to_vec();
966 let block_body_opt = txn
967 .get(BODIES, &hash_key)?
968 .map(|bytes| BlockBodyRLP::from_bytes(bytes).to())
969 .transpose()
970 .map_err(StoreError::from)?;
971
972 block_bodies.push(block_body_opt);
973 }
974
975 Ok(block_bodies)
976 })
977 .await
978 .map_err(|e| StoreError::Custom(format!("Task panicked: {}", e)))?
979 }
980
981 pub async fn get_block_bodies_by_hash(
983 &self,
984 hashes: Vec<BlockHash>,
985 ) -> Result<Vec<BlockBody>, StoreError> {
986 let buffer = self.buffer()?;
987 let backend = self.backend.clone();
988 tokio::task::spawn_blocking(move || {
990 let txn = backend.begin_read()?;
991 let mut block_bodies = Vec::new();
992 for hash in hashes {
993 if let Some(body) = buffer.get_body(&hash) {
996 block_bodies.push(body);
997 continue;
998 }
999 let hash_key = hash.encode_to_vec();
1000
1001 let Some(block_body) = txn
1002 .get(BODIES, &hash_key)?
1003 .map(|bytes| BlockBodyRLP::from_bytes(bytes).to())
1004 .transpose()
1005 .map_err(StoreError::from)?
1006 else {
1007 return Err(StoreError::Custom(format!(
1008 "Block body not found for hash: {hash}"
1009 )));
1010 };
1011 block_bodies.push(block_body);
1012 }
1013 Ok(block_bodies)
1014 })
1015 .await
1016 .map_err(|e| StoreError::Custom(format!("Task panicked: {}", e)))?
1017 }
1018
1019 pub async fn get_block_body_by_hash(
1021 &self,
1022 block_hash: BlockHash,
1023 ) -> Result<Option<BlockBody>, StoreError> {
1024 if let Some(b) = self.buffer()?.get_body(&block_hash) {
1025 return Ok(Some(b));
1026 }
1027 self.read_async(BODIES, block_hash.encode_to_vec())
1028 .await?
1029 .map(|bytes| BlockBodyRLP::from_bytes(bytes).to())
1030 .transpose()
1031 .map_err(StoreError::from)
1032 }
1033
1034 pub fn get_block_header_by_hash(
1035 &self,
1036 block_hash: BlockHash,
1037 ) -> Result<Option<BlockHeader>, StoreError> {
1038 let latest = self.latest_block_header.get();
1039 if block_hash == latest.hash() {
1040 return Ok(Some((*latest).clone()));
1041 }
1042 if let Some(h) = self.buffer()?.get_header(&block_hash) {
1043 return Ok(Some(h));
1044 }
1045 self.load_block_header_by_hash(block_hash)
1046 }
1047
1048 pub fn add_pending_block(&self, block: Block) -> Result<(), StoreError> {
1049 let block_hash = block.hash();
1050 let block_value = BlockRLP::from(block).into_vec();
1051 self.write(PENDING_BLOCKS, block_hash.as_bytes().to_vec(), block_value)
1052 }
1053
1054 pub async fn get_pending_block(
1055 &self,
1056 block_hash: BlockHash,
1057 ) -> Result<Option<Block>, StoreError> {
1058 self.read_async(PENDING_BLOCKS, block_hash.as_bytes().to_vec())
1059 .await?
1060 .map(|bytes| BlockRLP::from_bytes(bytes).to())
1061 .transpose()
1062 .map_err(StoreError::from)
1063 }
1064
1065 pub async fn add_block_number(
1067 &self,
1068 block_hash: BlockHash,
1069 block_number: BlockNumber,
1070 ) -> Result<(), StoreError> {
1071 let number_value = block_number.to_le_bytes().to_vec();
1072 self.write_async(BLOCK_NUMBERS, block_hash.encode_to_vec(), number_value)
1073 .await
1074 }
1075
1076 pub async fn get_block_number(
1078 &self,
1079 block_hash: BlockHash,
1080 ) -> Result<Option<BlockNumber>, StoreError> {
1081 if let Some(n) = self.buffer()?.get_number(&block_hash) {
1082 return Ok(Some(n));
1083 }
1084 self.read_async(BLOCK_NUMBERS, block_hash.encode_to_vec())
1085 .await?
1086 .map(decode_block_number)
1087 .transpose()
1088 }
1089
1090 pub async fn add_transaction_location(
1092 &self,
1093 transaction_hash: H256,
1094 block_number: BlockNumber,
1095 block_hash: BlockHash,
1096 index: Index,
1097 ) -> Result<(), StoreError> {
1098 self.add_transaction_locations(vec![(transaction_hash, block_number, block_hash, index)])
1099 .await
1100 }
1101
1102 pub async fn add_transaction_locations(
1104 &self,
1105 locations: Vec<(H256, BlockNumber, BlockHash, Index)>,
1106 ) -> Result<(), StoreError> {
1107 let db = self.backend.clone();
1108 tokio::task::spawn_blocking(move || {
1109 let mut tx = db.begin_write()?;
1110 for (tx_hash, block_number, block_hash, index) in locations {
1111 tx.merge(
1112 TRANSACTION_LOCATIONS,
1113 tx_hash.as_bytes(),
1114 &encode_tx_location_operand(block_number, block_hash, index),
1115 )?;
1116 }
1117 tx.commit()
1118 })
1119 .await
1120 .map_err(|e| StoreError::Custom(format!("Task panicked: {}", e)))?
1121 }
1122
1123 pub async fn get_transaction_location(
1125 &self,
1126 transaction_hash: H256,
1127 ) -> Result<Option<(BlockNumber, BlockHash, Index)>, StoreError> {
1128 let buffered = self.buffer()?.get_tx_locations(&transaction_hash);
1129 let db = self.backend.clone();
1130 tokio::task::spawn_blocking(move || {
1131 let tx = db.begin_read()?;
1132 let mut locations = buffered;
1133 if let Some(bytes) = tx.get(TRANSACTION_LOCATIONS, transaction_hash.as_bytes())? {
1134 locations.extend(<Vec<(BlockNumber, BlockHash, Index)>>::decode(&bytes)?);
1135 }
1136 for (block_number, block_hash, index) in locations {
1137 let canonical_hash = tx
1138 .get(
1139 CANONICAL_BLOCK_HASHES,
1140 block_number.to_le_bytes().as_slice(),
1141 )?
1142 .map(|bytes| H256::decode(bytes.as_slice()))
1143 .transpose()?;
1144 if canonical_hash == Some(block_hash) {
1145 return Ok(Some((block_number, block_hash, index)));
1146 }
1147 }
1148 Ok(None)
1149 })
1150 .await
1151 .map_err(|e| StoreError::Custom(format!("Task panicked: {}", e)))?
1152 }
1153
1154 pub async fn add_receipt(
1156 &self,
1157 block_hash: BlockHash,
1158 index: Index,
1159 receipt: Receipt,
1160 ) -> Result<(), StoreError> {
1161 let key = receipt_key(&block_hash, index);
1162 let value = receipt.encode_storage();
1166 self.write_async(RECEIPTS_V2, key, value).await
1167 }
1168
1169 pub async fn add_receipts(
1171 &self,
1172 block_hash: BlockHash,
1173 receipts: Vec<Receipt>,
1174 ) -> Result<(), StoreError> {
1175 let batch_items: Vec<_> = receipts
1176 .into_iter()
1177 .enumerate()
1178 .map(|(index, receipt)| {
1179 let key = receipt_key(&block_hash, index as u64);
1180 let value = receipt.encode_storage();
1181 (key, value)
1182 })
1183 .collect();
1184 self.write_batch_async(RECEIPTS_V2, batch_items).await
1185 }
1186
1187 pub async fn get_receipt(
1189 &self,
1190 block_number: BlockNumber,
1191 index: Index,
1192 ) -> Result<Option<Receipt>, StoreError> {
1193 let Some(block_hash) = self.get_canonical_block_hash(block_number).await? else {
1195 return Ok(None);
1196 };
1197 self.get_receipt_by_block_hash(block_hash, index).await
1198 }
1199
1200 async fn get_receipt_by_block_hash(
1202 &self,
1203 block_hash: BlockHash,
1204 index: Index,
1205 ) -> Result<Option<Receipt>, StoreError> {
1206 if let Some(r) = self.buffer()?.get_receipt(&block_hash, index) {
1207 return Ok(Some(r));
1208 }
1209 let key = receipt_key(&block_hash, index);
1210 self.read_async(RECEIPTS_V2, key)
1211 .await?
1212 .map(|bytes| Receipt::decode_storage(bytes.as_slice()))
1213 .transpose()
1214 .map_err(StoreError::from)
1215 }
1216
1217 pub fn code_cache_budget_bytes(&self) -> u64 {
1224 self.account_code_cache
1225 .lock()
1226 .map(|cache| cache.max_size)
1227 .unwrap_or(0)
1228 }
1229
1230 pub fn get_account_code(&self, code_hash: H256) -> Result<Option<Code>, StoreError> {
1237 if let Some(code) = self.buffer()?.get_code(&code_hash) {
1238 return Ok(Some(code));
1239 }
1240 if let Some(code) = self
1242 .account_code_cache
1243 .lock()
1244 .map_err(|_| StoreError::LockError)?
1245 .get(&code_hash)?
1246 {
1247 return Ok(Some(code));
1248 }
1249
1250 let Some(bytes) = self
1251 .backend
1252 .begin_read()?
1253 .get(ACCOUNT_CODES, code_hash.as_bytes())?
1254 else {
1255 return Ok(None);
1256 };
1257 let (bytecode_slice, jumpdests) = decode_bytes(&bytes)?;
1258 let (jumpdests, was_legacy) = decode_jumpdests(bytecode_slice, jumpdests)?;
1259 let code = Code::from_parts_unchecked(code_hash, bytecode_slice, jumpdests);
1260
1261 if was_legacy && let Ok(handle) = tokio::runtime::Handle::try_current() {
1268 let buf = encode_code(&code);
1269 let hash_key = code_hash.0.to_vec();
1270 let backend = self.backend.clone();
1271 handle.spawn(async move {
1272 if let Err(e) = async {
1273 let mut tx = backend.begin_write()?;
1274 tx.put(ACCOUNT_CODES, &hash_key, &buf)?;
1275 tx.commit()
1276 }
1277 .await
1278 {
1279 tracing::warn!("Failed to rewrite legacy jumpdests during backfill: {e}");
1280 }
1281 });
1282 }
1283
1284 self.account_code_cache
1286 .lock()
1287 .map_err(|_| StoreError::LockError)?
1288 .insert(&code)?;
1289
1290 Ok(Some(code))
1291 }
1292
1293 pub fn get_account_codes_batch(
1308 &self,
1309 code_hashes: &[H256],
1310 ) -> Result<Vec<Option<Code>>, StoreError> {
1311 let mut out: Vec<Option<Code>> = vec![None; code_hashes.len()];
1312 let mut pending: HashMap<H256, Vec<usize>> = HashMap::new();
1314
1315 {
1316 let buffer = self.buffer()?;
1317 let cache = self
1318 .account_code_cache
1319 .lock()
1320 .map_err(|_| StoreError::LockError)?;
1321 for (i, hash) in code_hashes.iter().enumerate() {
1322 if let Some(code) = buffer.get_code(hash) {
1323 out[i] = Some(code);
1324 } else if let Some(code) = cache.get(hash)? {
1325 out[i] = Some(code);
1326 } else {
1327 pending.entry(*hash).or_default().push(i);
1328 }
1329 }
1330 }
1331
1332 if pending.is_empty() {
1333 return Ok(out);
1334 }
1335
1336 let mut missing: Vec<H256> = pending.keys().copied().collect();
1337 missing.sort_unstable();
1338
1339 const KEYS_PER_SHARD: usize = 256;
1361 const MIN_SHARD_CAP: usize = 64;
1362 let parallelism = std::thread::available_parallelism().map_or(8, |p| p.get());
1363 let max_shards = parallelism.saturating_mul(2).max(MIN_SHARD_CAP);
1364 let shards = missing.len().div_ceil(KEYS_PER_SHARD).min(max_shards);
1365 let read_view = self.backend.begin_read()?;
1366 let decode = |hash: &H256, value: Option<Vec<u8>>| -> Result<Option<Code>, StoreError> {
1369 let Some(bytes) = value else { return Ok(None) };
1370 let (bytecode_slice, jumpdests) = decode_bytes(&bytes)?;
1371 let (jumpdests, _was_legacy) = decode_jumpdests(bytecode_slice, jumpdests)?;
1374 Ok(Some(Code::from_parts_unchecked(
1375 *hash,
1376 bytecode_slice,
1377 jumpdests,
1378 )))
1379 };
1380 let decoded: Vec<Result<Option<Code>, StoreError>> = if shards > parallelism {
1381 let chunk = missing.len().div_ceil(shards);
1382 let rv = read_view.as_ref();
1383 let read_shard = |hashes: &[H256]| -> Vec<Result<Option<Code>, StoreError>> {
1384 let keys: Vec<&[u8]> = hashes.iter().map(|h| h.as_bytes()).collect();
1385 rv.multi_get(ACCOUNT_CODES, &keys)
1386 .into_iter()
1387 .zip(hashes.iter())
1388 .map(|(value, hash)| decode(hash, value?))
1389 .collect()
1390 };
1391 std::thread::scope(|scope| {
1392 let handles: Vec<_> = missing
1393 .chunks(chunk)
1394 .map(|ck| (ck, scope.spawn(move || read_shard(ck))))
1395 .collect();
1396 handles
1397 .into_iter()
1398 .flat_map(|(shard, handle)| {
1399 handle.join().unwrap_or_else(|_| {
1404 shard
1405 .iter()
1406 .map(|_| {
1407 Err(StoreError::Custom(
1408 "account code shard panicked".to_string(),
1409 ))
1410 })
1411 .collect()
1412 })
1413 })
1414 .collect()
1415 })
1416 } else {
1417 let rv = read_view.as_ref();
1420 missing
1421 .par_iter()
1422 .map(|hash| decode(hash, rv.get(ACCOUNT_CODES, hash.as_bytes())?))
1423 .collect()
1424 };
1425
1426 let mut fetched: Vec<Code> = Vec::new();
1427 for (hash, code) in missing.iter().zip(decoded.into_iter()) {
1428 let Some(code) = code? else { continue };
1429 for &i in pending.get(hash).into_iter().flatten() {
1430 out[i] = Some(code.clone());
1431 }
1432 fetched.push(code);
1433 }
1434
1435 if !fetched.is_empty() {
1436 let mut cache = self
1437 .account_code_cache
1438 .lock()
1439 .map_err(|_| StoreError::LockError)?;
1440 for code in &fetched {
1441 cache.insert(code)?;
1442 }
1443 }
1444
1445 Ok(out)
1446 }
1447
1448 pub fn code_exists(&self, code_hash: H256) -> Result<bool, StoreError> {
1453 if self.buffer()?.get_code(&code_hash).is_some() {
1457 return Ok(true);
1458 }
1459 if self
1461 .account_code_cache
1462 .lock()
1463 .map_err(|_| StoreError::LockError)?
1464 .contains(&code_hash)
1465 {
1466 return Ok(true);
1467 }
1468 Ok(self
1470 .backend
1471 .begin_read()?
1472 .get(ACCOUNT_CODES, code_hash.as_bytes())?
1473 .is_some())
1474 }
1475
1476 pub fn get_code_metadata(&self, code_hash: H256) -> Result<Option<CodeMetadata>, StoreError> {
1481 use ethrex_common::constants::EMPTY_KECCAK_HASH;
1482
1483 if code_hash == *EMPTY_KECCAK_HASH {
1485 return Ok(Some(CodeMetadata { length: 0 }));
1486 }
1487
1488 if let Some(metadata) = self
1490 .code_metadata_cache
1491 .lock()
1492 .map_err(|_| StoreError::LockError)?
1493 .get(&code_hash)
1494 .copied()
1495 {
1496 return Ok(Some(metadata));
1497 }
1498
1499 let metadata = if let Some(bytes) = self
1501 .backend
1502 .begin_read()?
1503 .get(ACCOUNT_CODE_METADATA, code_hash.as_bytes())?
1504 {
1505 let length =
1506 u64::from_be_bytes(bytes.try_into().map_err(|_| {
1507 StoreError::Custom("Invalid metadata length encoding".to_string())
1508 })?);
1509 CodeMetadata { length }
1510 } else {
1511 let Some(code) = self.get_account_code(code_hash)? else {
1513 return Ok(None);
1514 };
1515 let metadata = CodeMetadata {
1516 length: code.len() as u64,
1517 };
1518
1519 if let Ok(handle) = tokio::runtime::Handle::try_current() {
1527 let metadata_buf = metadata.length.to_be_bytes().to_vec();
1528 let hash_key = code_hash.0.to_vec();
1529 let backend = self.backend.clone();
1530 handle.spawn(async move {
1531 if let Err(e) = async {
1532 let mut tx = backend.begin_write()?;
1533 tx.put(ACCOUNT_CODE_METADATA, &hash_key, &metadata_buf)?;
1534 tx.commit()
1535 }
1536 .await
1537 {
1538 tracing::warn!("Failed to write code metadata during backfill: {}", e);
1539 }
1540 });
1541 }
1542
1543 metadata
1544 };
1545
1546 self.code_metadata_cache
1548 .lock()
1549 .map_err(|_| StoreError::LockError)?
1550 .put(code_hash, metadata);
1551
1552 Ok(Some(metadata))
1553 }
1554
1555 pub async fn add_account_code(&self, code: Code) -> Result<(), StoreError> {
1557 let hash_key = code.hash.0.to_vec();
1558 let buf = encode_code(&code);
1559 let metadata_buf = (code.len() as u64).to_be_bytes();
1560
1561 let backend = self.backend.clone();
1563 tokio::task::spawn_blocking(move || {
1564 let mut tx = backend.begin_write()?;
1565 tx.put(ACCOUNT_CODES, &hash_key, &buf)?;
1566 tx.put(ACCOUNT_CODE_METADATA, &hash_key, &metadata_buf)?;
1567 tx.commit()
1568 })
1569 .await
1570 .map_err(|e| StoreError::Custom(format!("Task panicked: {}", e)))?
1571 }
1572
1573 pub async fn clear_snap_state(&self) -> Result<(), StoreError> {
1575 let db = self.backend.clone();
1576 tokio::task::spawn_blocking(move || db.clear_table(SNAP_STATE))
1577 .await
1578 .map_err(|e| StoreError::Custom(format!("Task panicked: {}", e)))?
1579 }
1580
1581 pub async fn get_transaction_by_hash(
1582 &self,
1583 transaction_hash: H256,
1584 ) -> Result<Option<Transaction>, StoreError> {
1585 let (_block_number, block_hash, index) =
1586 match self.get_transaction_location(transaction_hash).await? {
1587 Some(location) => location,
1588 None => return Ok(None),
1589 };
1590 self.get_transaction_by_location(block_hash, index).await
1591 }
1592
1593 pub async fn get_transaction_by_location(
1594 &self,
1595 block_hash: H256,
1596 index: u64,
1597 ) -> Result<Option<Transaction>, StoreError> {
1598 let block_body = match self.get_block_body_by_hash(block_hash).await? {
1599 Some(body) => body,
1600 None => return Ok(None),
1601 };
1602 let index: usize = index.try_into()?;
1603 Ok(block_body.transactions.get(index).cloned())
1604 }
1605
1606 pub async fn get_block_by_hash(
1607 &self,
1608 block_hash: BlockHash,
1609 ) -> Result<Option<Block>, StoreError> {
1610 let header = match self.get_block_header_by_hash(block_hash)? {
1611 Some(header) => header,
1612 None => return Ok(None),
1613 };
1614 let body = match self.get_block_body_by_hash(block_hash).await? {
1615 Some(body) => body,
1616 None => return Ok(None),
1617 };
1618 Ok(Some(Block::new(header, body)))
1619 }
1620
1621 pub async fn get_block_by_number(
1622 &self,
1623 block_number: BlockNumber,
1624 ) -> Result<Option<Block>, StoreError> {
1625 let Some(block_hash) = self.get_canonical_block_hash(block_number).await? else {
1626 return Ok(None);
1627 };
1628 self.get_block_by_hash(block_hash).await
1629 }
1630
1631 pub async fn get_canonical_block_hash(
1633 &self,
1634 block_number: BlockNumber,
1635 ) -> Result<Option<BlockHash>, StoreError> {
1636 let last = self.latest_block_header.get();
1637 if last.number == block_number {
1638 return Ok(Some(last.hash()));
1639 }
1640 let backend = self.backend.clone();
1641 tokio::task::spawn_blocking(move || {
1642 backend
1643 .begin_read()?
1644 .get(
1645 CANONICAL_BLOCK_HASHES,
1646 block_number.to_le_bytes().as_slice(),
1647 )?
1648 .map(|bytes| H256::decode(bytes.as_slice()))
1649 .transpose()
1650 .map_err(StoreError::from)
1651 })
1652 .await
1653 .map_err(|e| StoreError::Custom(format!("Task panicked: {}", e)))?
1654 }
1655
1656 pub async fn set_chain_config(&mut self, chain_config: &ChainConfig) -> Result<(), StoreError> {
1659 self.chain_config = *chain_config;
1660 let key = chain_data_key(ChainDataIndex::ChainConfig);
1661 let value = serde_json::to_string(chain_config)
1662 .map_err(|_| StoreError::Custom("Failed to serialize chain config".to_string()))?
1663 .into_bytes();
1664 self.write_async(CHAIN_DATA, key, value).await
1665 }
1666
1667 pub async fn update_earliest_block_number(
1669 &self,
1670 block_number: BlockNumber,
1671 ) -> Result<(), StoreError> {
1672 let key = chain_data_key(ChainDataIndex::EarliestBlockNumber);
1673 let value = block_number.to_le_bytes().to_vec();
1674 self.write_async(CHAIN_DATA, key, value).await
1675 }
1676
1677 async fn read_chain_data_block_number(
1679 &self,
1680 index: ChainDataIndex,
1681 ) -> Result<Option<BlockNumber>, StoreError> {
1682 self.read_async(CHAIN_DATA, chain_data_key(index))
1683 .await?
1684 .map(decode_block_number)
1685 .transpose()
1686 }
1687
1688 pub async fn get_earliest_block_number(&self) -> Result<BlockNumber, StoreError> {
1690 self.read_chain_data_block_number(ChainDataIndex::EarliestBlockNumber)
1691 .await?
1692 .ok_or(StoreError::MissingEarliestBlockNumber)
1693 }
1694
1695 pub async fn get_finalized_block_number(&self) -> Result<Option<BlockNumber>, StoreError> {
1697 self.read_chain_data_block_number(ChainDataIndex::FinalizedBlockNumber)
1698 .await
1699 }
1700
1701 pub async fn get_safe_block_number(&self) -> Result<Option<BlockNumber>, StoreError> {
1703 self.read_chain_data_block_number(ChainDataIndex::SafeBlockNumber)
1704 .await
1705 }
1706
1707 pub fn get_latest_block_number(&self) -> Result<BlockNumber, StoreError> {
1709 Ok(self.latest_block_header.get().number)
1710 }
1711
1712 pub async fn update_pending_block_number(
1714 &self,
1715 block_number: BlockNumber,
1716 ) -> Result<(), StoreError> {
1717 let key = chain_data_key(ChainDataIndex::PendingBlockNumber);
1718 let value = block_number.to_le_bytes().to_vec();
1719 self.write_async(CHAIN_DATA, key, value).await
1720 }
1721
1722 pub async fn get_pending_block_number(&self) -> Result<Option<BlockNumber>, StoreError> {
1724 self.read_chain_data_block_number(ChainDataIndex::PendingBlockNumber)
1725 .await
1726 }
1727
1728 async fn forkchoice_update_inner(
1743 &self,
1744 new_canonical_blocks: Vec<(BlockNumber, BlockHash)>,
1745 head_number: BlockNumber,
1746 head_hash: BlockHash,
1747 safe: Option<BlockNumber>,
1748 finalized: Option<BlockNumber>,
1749 ) -> Result<(), StoreError> {
1750 let latest = self.load_latest_block_number().await?.unwrap_or(0);
1751 let db = self.backend.clone();
1752 let journal_pruning_paused = self.journal_pruning_paused.clone();
1753 tokio::task::spawn_blocking(move || {
1754 let mut txn = db.begin_write()?;
1755
1756 for (block_number, block_hash) in new_canonical_blocks {
1757 let head_key = block_number.to_le_bytes();
1758 let head_value = block_hash.encode_to_vec();
1759 txn.put(CANONICAL_BLOCK_HASHES, &head_key, &head_value)?;
1760 }
1761
1762 for number in (head_number + 1)..=(latest) {
1769 txn.delete(CANONICAL_BLOCK_HASHES, number.to_le_bytes().as_slice())?;
1770 }
1771
1772 let head_key = head_number.to_le_bytes();
1774 let head_value = head_hash.encode_to_vec();
1775 txn.put(CANONICAL_BLOCK_HASHES, &head_key, &head_value)?;
1776
1777 let latest_key = chain_data_key(ChainDataIndex::LatestBlockNumber);
1779 txn.put(CHAIN_DATA, &latest_key, &head_number.to_le_bytes())?;
1780
1781 if let Some(safe) = safe {
1782 let safe_key = chain_data_key(ChainDataIndex::SafeBlockNumber);
1783 txn.put(CHAIN_DATA, &safe_key, &safe.to_le_bytes())?;
1784 }
1785
1786 if let Some(finalized) = finalized {
1787 let finalized_key = chain_data_key(ChainDataIndex::FinalizedBlockNumber);
1788
1789 let prev_finalized = match db.begin_read()?.get(CHAIN_DATA, &finalized_key)? {
1799 Some(bytes) => {
1800 let arr: [u8; 8] = bytes.as_slice().try_into().map_err(|_| {
1801 StoreError::Custom(format!(
1802 "FinalizedBlockNumber has unexpected length {} (want 8)",
1803 bytes.len()
1804 ))
1805 })?;
1806 BlockNumber::from_le_bytes(arr)
1807 }
1808 None => 0,
1809 };
1810
1811 txn.put(CHAIN_DATA, &finalized_key, &finalized.to_le_bytes())?;
1812
1813 if finalized > prev_finalized
1825 && !journal_pruning_paused.load(std::sync::atomic::Ordering::Acquire)
1826 {
1827 let start = 0u64.to_be_bytes();
1828 let end = finalized.saturating_add(1).to_be_bytes();
1829 txn.delete_range(STATE_HISTORY, &start, &end)?;
1830 }
1831 }
1832
1833 txn.commit()
1834 })
1835 .await
1836 .map_err(|e| StoreError::Custom(format!("Task panicked: {}", e)))?
1837 }
1838
1839 pub async fn get_receipts_for_block(
1840 &self,
1841 block_hash: &BlockHash,
1842 ) -> Result<Vec<Receipt>, StoreError> {
1843 self.get_receipts_for_block_from_index(block_hash, 0, None)
1844 .await
1845 }
1846
1847 pub async fn get_receipts_for_block_from_index(
1856 &self,
1857 block_hash: &BlockHash,
1858 start_index: u64,
1859 max_count: Option<usize>,
1860 ) -> Result<Vec<Receipt>, StoreError> {
1861 if let Some(all) = self.buffer()?.get_receipts(block_hash) {
1862 let start = start_index as usize;
1863 let slice = all.into_iter().skip(start);
1864 return Ok(match max_count {
1865 Some(max) => slice.take(max).collect(),
1866 None => slice.collect(),
1867 });
1868 }
1869 let backend = self.backend.clone();
1870 let block_hash = *block_hash;
1871
1872 tokio::task::spawn_blocking(move || {
1873 let txn = backend.begin_read()?;
1874 let prefix = block_hash.as_bytes().to_vec();
1875 let mut seek_key = prefix.clone();
1878 seek_key.extend_from_slice(&start_index.to_be_bytes());
1879 let iter = txn.prefix_iterator(RECEIPTS_V2, &seek_key)?;
1880 let mut receipts = Vec::new();
1881 for result in iter {
1882 let (k, v) = result?;
1883 if !k.starts_with(&prefix) {
1884 break;
1885 }
1886 if k.len() != 40 {
1887 continue;
1888 }
1889 receipts.push(Receipt::decode_storage(v.as_ref())?);
1890 if let Some(max) = max_count
1891 && receipts.len() >= max
1892 {
1893 break;
1894 }
1895 }
1896 Ok(receipts)
1897 })
1898 .await
1899 .map_err(|e| StoreError::Custom(format!("Task panicked: {e}")))?
1900 }
1901
1902 pub async fn set_header_download_checkpoint(
1906 &self,
1907 block_hash: BlockHash,
1908 ) -> Result<(), StoreError> {
1909 let key = snap_state_key(SnapStateIndex::HeaderDownloadCheckpoint);
1910 let value = block_hash.encode_to_vec();
1911 self.write_async(SNAP_STATE, key, value).await
1912 }
1913
1914 pub async fn get_header_download_checkpoint(&self) -> Result<Option<BlockHash>, StoreError> {
1916 let key = snap_state_key(SnapStateIndex::HeaderDownloadCheckpoint);
1917 self.backend
1918 .begin_read()?
1919 .get(SNAP_STATE, &key)?
1920 .map(|bytes| H256::decode(bytes.as_slice()))
1921 .transpose()
1922 .map_err(StoreError::from)
1923 }
1924
1925 pub async fn set_latest_valid_ancestor(
1931 &self,
1932 bad_block: BlockHash,
1933 latest_valid: BlockHash,
1934 ) -> Result<(), StoreError> {
1935 let value = latest_valid.encode_to_vec();
1936 self.write_async(INVALID_CHAINS, bad_block.as_bytes().to_vec(), value)
1937 .await
1938 }
1939
1940 pub async fn get_latest_valid_ancestor(
1943 &self,
1944 block: BlockHash,
1945 ) -> Result<Option<BlockHash>, StoreError> {
1946 self.read_async(INVALID_CHAINS, block.as_bytes().to_vec())
1947 .await?
1948 .map(|bytes| H256::decode(bytes.as_slice()))
1949 .transpose()
1950 .map_err(StoreError::from)
1951 }
1952
1953 pub async fn add_bad_block(&self, block: Block) -> Result<(), StoreError> {
1958 let mut bad_blocks = self.get_bad_blocks().await?;
1959 let block_number = block.header.number;
1960 let block_hash = block.hash();
1961 if bad_blocks
1962 .iter()
1963 .any(|b| b.header.number == block_number && b.hash() == block_hash)
1964 {
1965 return Ok(());
1966 }
1967 bad_blocks.push(block);
1968 bad_blocks.sort_by(|a, b| b.header.number.cmp(&a.header.number));
1969 bad_blocks.truncate(MAX_BAD_BLOCKS);
1970 self.write_async(
1971 BAD_BLOCKS,
1972 BAD_BLOCKS_KEY.to_vec(),
1973 bad_blocks.encode_to_vec(),
1974 )
1975 .await
1976 }
1977
1978 pub async fn get_bad_blocks(&self) -> Result<Vec<Block>, StoreError> {
1981 match self.read_async(BAD_BLOCKS, BAD_BLOCKS_KEY.to_vec()).await? {
1982 Some(bytes) => Vec::<Block>::decode(&bytes).map_err(StoreError::from),
1983 None => Ok(Vec::new()),
1984 }
1985 }
1986
1987 pub fn get_block_number_sync(
1989 &self,
1990 block_hash: BlockHash,
1991 ) -> Result<Option<BlockNumber>, StoreError> {
1992 if let Some(n) = self.buffer()?.get_number(&block_hash) {
1993 return Ok(Some(n));
1994 }
1995 let txn = self.backend.begin_read()?;
1996 txn.get(BLOCK_NUMBERS, &block_hash.encode_to_vec())?
1997 .map(decode_block_number)
1998 .transpose()
1999 }
2000
2001 pub fn get_canonical_block_hash_sync(
2003 &self,
2004 block_number: BlockNumber,
2005 ) -> Result<Option<BlockHash>, StoreError> {
2006 let last = self.latest_block_header.get();
2007 if last.number == block_number {
2008 return Ok(Some(last.hash()));
2009 }
2010 let txn = self.backend.begin_read()?;
2011 txn.get(
2012 CANONICAL_BLOCK_HASHES,
2013 block_number.to_le_bytes().as_slice(),
2014 )?
2015 .map(|bytes| H256::decode(bytes.as_slice()))
2016 .transpose()
2017 .map_err(StoreError::from)
2018 }
2019
2020 pub async fn write_storage_trie_nodes_batch(
2023 &self,
2024 storage_trie_nodes: StorageUpdates,
2025 ) -> Result<(), StoreError> {
2026 let mut txn = self.backend.begin_write()?;
2027 tokio::task::spawn_blocking(move || {
2028 for (address_hash, nodes) in storage_trie_nodes {
2029 for (node_path, node_data) in nodes {
2030 let key = apply_prefix(Some(address_hash), node_path);
2031 if node_data.is_empty() {
2032 txn.delete(STORAGE_TRIE_NODES, key.as_ref())?;
2033 } else {
2034 txn.put(STORAGE_TRIE_NODES, key.as_ref(), &node_data)?;
2035 }
2036 }
2037 }
2038 txn.commit()
2039 })
2040 .await
2041 .map_err(|e| StoreError::Custom(format!("Task panicked: {}", e)))?
2042 }
2043
2044 pub async fn write_account_code_batch(
2047 &self,
2048 account_codes: Vec<(H256, Code)>,
2049 ) -> Result<(), StoreError> {
2050 let mut code_batch_items = Vec::new();
2051 let mut metadata_batch_items = Vec::new();
2052
2053 for (code_hash, code) in account_codes {
2054 let buf = encode_code(&code);
2055 let metadata_buf = (code.len() as u64).to_be_bytes().to_vec();
2056 code_batch_items.push((code_hash.as_bytes().to_vec(), buf));
2057 metadata_batch_items.push((code_hash.as_bytes().to_vec(), metadata_buf));
2058 }
2059
2060 self.write_batch_async(ACCOUNT_CODES, code_batch_items)
2062 .await?;
2063 self.write_batch_async(ACCOUNT_CODE_METADATA, metadata_batch_items)
2064 .await
2065 }
2066
2067 fn buffer(&self) -> Result<Arc<BlockDataBuffer>, StoreError> {
2069 Ok(self
2070 .block_data_buffer
2071 .read()
2072 .map_err(|_| StoreError::LockError)?
2073 .clone())
2074 }
2075
2076 pub fn write(
2082 &self,
2083 table: &'static str,
2084 key: Vec<u8>,
2085 value: Vec<u8>,
2086 ) -> Result<(), StoreError> {
2087 let backend = self.backend.clone();
2088 let mut txn = backend.begin_write()?;
2089 txn.put(table, &key, &value)?;
2090 txn.commit()
2091 }
2092
2093 async fn write_async(
2096 &self,
2097 table: &'static str,
2098 key: Vec<u8>,
2099 value: Vec<u8>,
2100 ) -> Result<(), StoreError> {
2101 let backend = self.backend.clone();
2102
2103 tokio::task::spawn_blocking(move || {
2104 let mut txn = backend.begin_write()?;
2105 txn.put(table, &key, &value)?;
2106 txn.commit()
2107 })
2108 .await
2109 .map_err(|e| StoreError::Custom(format!("Task panicked: {}", e)))?
2110 }
2111
2112 pub async fn read_async(
2115 &self,
2116 table: &'static str,
2117 key: Vec<u8>,
2118 ) -> Result<Option<Vec<u8>>, StoreError> {
2119 let backend = self.backend.clone();
2120
2121 tokio::task::spawn_blocking(move || {
2122 let txn = backend.begin_read()?;
2123 txn.get(table, &key)
2124 })
2125 .await
2126 .map_err(|e| StoreError::Custom(format!("Task panicked: {}", e)))?
2127 }
2128
2129 pub fn read(&self, table: &'static str, key: Vec<u8>) -> Result<Option<Vec<u8>>, StoreError> {
2132 let backend = self.backend.clone();
2133 let txn = backend.begin_read()?;
2134 txn.get(table, &key)
2135 }
2136
2137 pub async fn write_batch_async(
2141 &self,
2142 table: &'static str,
2143 batch_ops: Vec<(Vec<u8>, Vec<u8>)>,
2144 ) -> Result<(), StoreError> {
2145 let backend = self.backend.clone();
2146
2147 tokio::task::spawn_blocking(move || {
2148 let mut txn = backend.begin_write()?;
2149 txn.put_batch(table, batch_ops)?;
2150 txn.commit()
2151 })
2152 .await
2153 .map_err(|e| StoreError::Custom(format!("Task panicked: {}", e)))?
2154 }
2155
2156 pub fn write_batch(
2158 &self,
2159 table: &'static str,
2160 batch_ops: Vec<(Vec<u8>, Vec<u8>)>,
2161 ) -> Result<(), StoreError> {
2162 let backend = self.backend.clone();
2163 let mut txn = backend.begin_write()?;
2164 txn.put_batch(table, batch_ops)?;
2165 txn.commit()
2166 }
2167
2168 pub async fn add_fullsync_batch(&self, headers: Vec<BlockHeader>) -> Result<(), StoreError> {
2169 self.write_batch_async(
2170 FULLSYNC_HEADERS,
2171 headers
2172 .into_iter()
2173 .map(|header| (header.number.to_le_bytes().to_vec(), header.encode_to_vec()))
2174 .collect(),
2175 )
2176 .await
2177 }
2178
2179 pub async fn read_fullsync_batch(
2180 &self,
2181 start: BlockNumber,
2182 limit: u64,
2183 ) -> Result<Vec<Option<BlockHeader>>, StoreError> {
2184 let mut res = vec![];
2185 let read_tx = self.backend.begin_read()?;
2186 for key in start..start + limit {
2188 let header_opt = read_tx
2189 .get(FULLSYNC_HEADERS, &key.to_le_bytes())?
2190 .map(|header| BlockHeader::decode(&header))
2191 .transpose()?;
2192 res.push(header_opt);
2193 }
2194 Ok(res)
2195 }
2196
2197 pub async fn clear_fullsync_headers(&self) -> Result<(), StoreError> {
2198 self.backend.clear_table(FULLSYNC_HEADERS)
2199 }
2200
2201 pub fn delete(&self, table: &'static str, key: Vec<u8>) -> Result<(), StoreError> {
2203 let mut txn = self.backend.begin_write()?;
2204 txn.delete(table, &key)?;
2205 txn.commit()
2206 }
2207
2208 pub fn store_block_updates(&self, update_batch: UpdateBatch) -> Result<(), StoreError> {
2209 self.apply_updates(update_batch)
2210 }
2211
2212 fn batch_state_roots(
2219 &self,
2220 update_batch: &UpdateBatch,
2221 ) -> Result<(H256, H256, BlockNumber, H256), StoreError> {
2222 let parent_state_root = self
2223 .get_block_header_by_hash(
2224 update_batch
2225 .blocks
2226 .first()
2227 .ok_or(StoreError::UpdateBatchNoBlocks)?
2228 .header
2229 .parent_hash,
2230 )?
2231 .map(|header| header.state_root)
2232 .unwrap_or_default();
2233 let last_block = update_batch
2234 .blocks
2235 .last()
2236 .ok_or(StoreError::UpdateBatchNoBlocks)?;
2237 let last_state_root = last_block.header.state_root;
2238 let last_block_number = last_block.header.number;
2239 let last_block_hash = last_block.hash();
2240 Ok((
2241 parent_state_root,
2242 last_state_root,
2243 last_block_number,
2244 last_block_hash,
2245 ))
2246 }
2247
2248 fn apply_updates(&self, update_batch: UpdateBatch) -> Result<(), StoreError> {
2252 let (parent_state_root, last_state_root, last_block_number, last_block_hash) =
2253 self.batch_state_roots(&update_batch)?;
2254
2255 let UpdateBatch {
2256 account_updates,
2257 storage_updates,
2258 blocks,
2259 receipts,
2260 code_updates,
2261 commit_depth,
2262 wait_for_flush,
2263 } = update_batch;
2264
2265 self.pending_trie_roots.register(last_state_root)?;
2269
2270 let blocks_with_receipts: Vec<(Block, Vec<Receipt>)> = if blocks.len() == 1 {
2273 let block = blocks.into_iter().next().expect("len == 1");
2274 let hash = block.hash();
2275 let r = receipts
2276 .into_iter()
2277 .find(|(h, _)| *h == hash)
2278 .map(|(_, r)| r)
2279 .unwrap_or_default();
2280 vec![(block, r)]
2281 } else {
2282 let mut receipts_by_hash: std::collections::HashMap<BlockHash, Vec<Receipt>> =
2283 receipts.into_iter().collect();
2284 blocks
2285 .into_iter()
2286 .map(|b| {
2287 let r = receipts_by_hash.remove(&b.hash()).unwrap_or_default();
2288 (b, r)
2289 })
2290 .collect()
2291 };
2292
2293 let (ack_tx, ack_rx) = sync_channel(1);
2298 self.persist_tx
2299 .send(PersistMessage::Block(Box::new(BlockPersist {
2300 blocks: blocks_with_receipts,
2301 codes: code_updates,
2302 parent_state_root,
2303 child_state_root: last_state_root,
2304 account_updates,
2305 storage_updates,
2306 commit_depth,
2307 wait_for_flush,
2308 block_number: last_block_number,
2309 block_hash: last_block_hash,
2310 ack: ack_tx,
2311 })))
2312 .map_err(|e| StoreError::Custom(format!("failed to send block persist: {e}")))?;
2313 ack_rx
2314 .recv()
2315 .map_err(|e| StoreError::Custom(format!("block persist ack failed: {e}")))??;
2316
2317 Ok(())
2318 }
2319
2320 pub fn new(path: impl AsRef<Path>, engine_type: EngineType) -> Result<Self, StoreError> {
2325 Self::new_with_config(path, engine_type, StoreConfig::default())
2326 }
2327
2328 pub fn new_with_config(
2330 path: impl AsRef<Path>,
2331 engine_type: EngineType,
2332 config: StoreConfig,
2333 ) -> Result<Self, StoreError> {
2334 let code_cache_size = config.code_cache_size.unwrap_or(CODE_CACHE_MAX_SIZE);
2335 let store = Self::open_with_config(path, engine_type, config)?;
2336 if let Ok(mut cache) = store.account_code_cache.lock() {
2337 cache.max_size = code_cache_size;
2338 }
2339 info!(cache_bytes = code_cache_size, "sized bytecode cache");
2340 Ok(store)
2341 }
2342
2343 fn open_with_config(
2344 path: impl AsRef<Path>,
2345 engine_type: EngineType,
2346 #[cfg_attr(not(feature = "rocksdb"), allow(unused_variables))] config: StoreConfig,
2348 ) -> Result<Self, StoreError> {
2349 let db_path = path.as_ref().to_path_buf();
2350
2351 if engine_type != EngineType::InMemory {
2352 let version = read_store_schema_version(&db_path)?;
2353
2354 match version {
2355 None if db_path.exists() && dir_contains_legacy_db(&db_path)? => {
2356 return Err(StoreError::NotFoundDBVersion);
2358 }
2359 None => {
2360 init_metadata_file(&db_path)?;
2366 }
2367 Some(v) if v < 1 => {
2371 return Err(StoreError::IncompatibleDBVersion {
2373 found: v,
2374 expected: STORE_SCHEMA_VERSION,
2375 });
2376 }
2377 Some(v) if v > STORE_SCHEMA_VERSION => {
2378 return Err(StoreError::IncompatibleDBVersion {
2381 found: v,
2382 expected: STORE_SCHEMA_VERSION,
2383 });
2384 }
2385 #[cfg(feature = "rocksdb")]
2386 Some(v) if v < STORE_SCHEMA_VERSION => {
2387 let rocksdb = Arc::new(RocksDBBackend::open(
2391 &path,
2392 config.rocksdb_block_cache_size,
2393 )?);
2394 crate::migrations::run_pending_migrations(rocksdb.as_ref(), &db_path, v)?;
2395 rocksdb.drop_obsolete_cfs(&path);
2396 let backend: Arc<dyn crate::api::StorageBackend> = rocksdb;
2397 return Self::from_backend(
2398 backend,
2399 db_path,
2400 DB_COMMIT_THRESHOLD,
2401 config.persist_channel_capacity,
2402 );
2403 }
2404 Some(_) => {
2405 }
2410 }
2411 }
2412
2413 match engine_type {
2414 #[cfg(feature = "rocksdb")]
2415 EngineType::RocksDB => {
2416 let rocksdb = RocksDBBackend::open(&path, config.rocksdb_block_cache_size)?;
2417 rocksdb.drop_obsolete_cfs(&path);
2418 let backend: Arc<dyn StorageBackend> = Arc::new(rocksdb);
2419 Self::from_backend(
2420 backend,
2421 db_path,
2422 DB_COMMIT_THRESHOLD,
2423 config.persist_channel_capacity,
2424 )
2425 }
2426 EngineType::InMemory => {
2427 let backend = Arc::new(InMemoryBackend::open()?);
2428 Self::from_backend(
2429 backend,
2430 db_path,
2431 IN_MEMORY_COMMIT_THRESHOLD,
2432 config.persist_channel_capacity,
2433 )
2434 }
2435 }
2436 }
2437
2438 fn from_backend(
2439 backend: Arc<dyn StorageBackend>,
2440 db_path: PathBuf,
2441 commit_threshold: usize,
2442 persist_channel_capacity: usize,
2443 ) -> Result<Self, StoreError> {
2444 debug!("Initializing Store with {commit_threshold} in-memory diff-layers");
2445 let (fkv_tx, fkv_rx) = std::sync::mpsc::sync_channel(0);
2446 let persist_cap = persist_channel_capacity.max(1); let (persist_tx, persist_rx) = std::sync::mpsc::sync_channel(persist_cap);
2448
2449 let (last_written, initial_flushed_upto) = {
2450 let tx = backend.begin_read()?;
2451 let last_written = tx
2452 .get(MISC_VALUES, "last_written".as_bytes())?
2453 .unwrap_or_else(|| vec![0u8; 64]);
2454 let last_written = if last_written == [0xff] {
2455 vec![0xff; 64]
2456 } else {
2457 last_written
2458 };
2459 let initial_flushed_upto = match tx.get(MISC_VALUES, FLUSHED_UPTO_KEY)? {
2460 Some(bytes) => decode_flushed_upto(&bytes)?,
2461 None => 0,
2462 };
2463 (last_written, initial_flushed_upto)
2464 };
2465 let mut initial_buffer = BlockDataBuffer::new();
2466 initial_buffer.set_flushed_upto(initial_flushed_upto);
2467
2468 let mut background_threads = Vec::new();
2469 let safe_commit_root = Arc::new(RwLock::new(H256::zero()));
2470 let mut store = Self {
2471 db_path,
2472 backend,
2473 chain_config: Default::default(),
2474 latest_block_header: Default::default(),
2475 trie_cache: Arc::new(RwLock::new(Arc::new(TrieLayerCache::new_with_safe_commit(
2476 commit_threshold,
2477 safe_commit_root.clone(),
2478 )))),
2479 flatkeyvalue_control_tx: fkv_tx,
2480 block_data_buffer: Arc::new(RwLock::new(Arc::new(initial_buffer))),
2481 persist_tx,
2482 pending_trie_roots: Arc::new(PendingTrieRoots::default()),
2483 last_computed_flatkeyvalue: Arc::new(RwLock::new(last_written)),
2484 account_code_cache: Arc::new(Mutex::new(CodeCache::default())),
2485 code_metadata_cache: Arc::new(Mutex::new(LruCache::with_hasher(
2486 NonZeroUsize::new(CODE_METADATA_CACHE_MAX_ENTRIES).unwrap_or(NonZeroUsize::MIN),
2487 FxBuildHasher,
2488 ))),
2489 fcu_lock: Arc::new(tokio::sync::Mutex::new(())),
2490 safe_commit_root,
2491 journal_pruning_paused: Arc::new(std::sync::atomic::AtomicBool::new(false)),
2492 background_threads: Default::default(),
2493 };
2494 let backend_clone = store.backend.clone();
2495 let last_computed_fkv = store.last_computed_flatkeyvalue.clone();
2496 background_threads.push(std::thread::spawn(move || {
2497 let rx = fkv_rx;
2498 loop {
2500 match rx.recv() {
2501 Ok(FKVGeneratorControlMessage::Continue) => break,
2502 Ok(FKVGeneratorControlMessage::Stop) => {}
2503 Err(std::sync::mpsc::RecvError) => {
2504 debug!("Closing FlatKeyValue generator.");
2505 return;
2506 }
2507 }
2508 }
2509
2510 let _ = flatkeyvalue_generator(&backend_clone, &last_computed_fkv, &rx)
2511 .inspect_err(|err| error!("Error while generating FlatKeyValue: {err}"));
2512 }));
2513 let persist_backend = store.backend.clone();
2516 let persist_buffer = store.block_data_buffer.clone();
2517 let persist_trie_cache = store.trie_cache.clone();
2518 let persist_pending_roots = store.pending_trie_roots.clone();
2519 let persist_fkv_ctl = store.flatkeyvalue_control_tx.clone();
2520 background_threads.push(std::thread::spawn(move || {
2521 let rx = persist_rx;
2522 let mut last_flush_result: Result<(), StoreError> = Ok(());
2525 loop {
2526 match rx.recv() {
2527 Ok(PersistMessage::Block(bp)) => {
2528 let bp = *bp;
2529 let staged = mutate_block_buffer(&persist_buffer, move |b| {
2532 let mut codes = Some(bp.codes);
2533 for (block, receipts) in bp.blocks {
2534 b.insert(block, receipts, codes.take().unwrap_or_default());
2535 }
2536 });
2537 if let Err(e) = staged {
2538 persist_pending_roots.clear(bp.child_state_root);
2543 let _ = bp.ack.send(Err(e));
2544 continue;
2545 }
2546 if !bp.wait_for_flush {
2555 let _ = bp
2556 .ack
2557 .send(std::mem::replace(&mut last_flush_result, Ok(())));
2558 }
2559 if let Err(err) = apply_trie_phase1(
2561 &persist_trie_cache,
2562 &persist_pending_roots,
2563 bp.parent_state_root,
2564 bp.child_state_root,
2565 bp.block_number,
2566 bp.block_hash,
2567 bp.account_updates,
2568 bp.storage_updates,
2569 ) {
2570 error!("persist worker trie phase-1 failed: {err}");
2571 if bp.wait_for_flush {
2572 let _ = bp.ack.send(Err(err));
2573 } else {
2574 last_flush_result = Err(err);
2575 }
2576 continue;
2577 }
2578 let flushed = flush_block_data(persist_backend.as_ref(), &persist_buffer)
2580 .inspect_err(|err| error!("flush_block_data failed: {err}"))
2581 .and_then(|_| {
2582 commit_trie_if_due(
2583 persist_backend.as_ref(),
2584 &persist_trie_cache,
2585 &persist_fkv_ctl,
2586 bp.parent_state_root,
2587 bp.commit_depth,
2588 bp.wait_for_flush,
2589 )
2590 });
2591 if bp.wait_for_flush {
2594 let prior = std::mem::replace(&mut last_flush_result, Ok(()));
2595 let _ = bp.ack.send(prior.and(flushed));
2596 } else {
2597 last_flush_result = flushed;
2598 }
2599 }
2600 Ok(PersistMessage::Commit(root)) => match persist_trie_cache.read() {
2601 Ok(guard) => {
2602 let trie = guard.clone();
2603 drop(guard);
2604 let _ = commit_to_disk(
2607 persist_backend.as_ref(),
2608 &persist_fkv_ctl,
2609 &persist_trie_cache,
2610 &trie,
2611 root,
2612 false,
2613 )
2614 .inspect_err(|err| error!("commit_to_disk failed: {err}"));
2615 }
2616 Err(_) => error!("trie cache lock poisoned during commit"),
2617 },
2618 Ok(PersistMessage::Ping(ack)) => {
2619 let _ = ack.send(std::mem::replace(&mut last_flush_result, Ok(())));
2623 }
2624 Ok(PersistMessage::Shutdown { ack }) => {
2625 let result = flush_block_data(persist_backend.as_ref(), &persist_buffer);
2632 let prior = std::mem::replace(&mut last_flush_result, Ok(()));
2633 let _ = ack.send(prior.and(result));
2634 return;
2636 }
2637 Err(_) => return,
2638 }
2639 }
2640 }));
2641 store.background_threads = Arc::new(ThreadList {
2642 list: background_threads,
2643 });
2644 Ok(store)
2645 }
2646
2647 pub async fn new_from_genesis(
2650 store_path: &Path,
2651 engine_type: EngineType,
2652 genesis_path: &str,
2653 ) -> Result<Self, StoreError> {
2654 Self::new_from_genesis_with_config(
2655 store_path,
2656 engine_type,
2657 genesis_path,
2658 StoreConfig::default(),
2659 )
2660 .await
2661 }
2662
2663 pub async fn new_from_genesis_with_config(
2666 store_path: &Path,
2667 engine_type: EngineType,
2668 genesis_path: &str,
2669 config: StoreConfig,
2670 ) -> Result<Self, StoreError> {
2671 let file = std::fs::File::open(genesis_path)
2672 .map_err(|error| StoreError::Custom(format!("Failed to open genesis file: {error}")))?;
2673 let reader = std::io::BufReader::new(file);
2674 let genesis: Genesis = serde_json::from_reader(reader)
2675 .map_err(|e| StoreError::Custom(format!("Failed to deserialize genesis file: {e}")))?;
2676 let mut store = Self::new_with_config(store_path, engine_type, config)?;
2677 store.add_initial_state(genesis).await?;
2678 Ok(store)
2679 }
2680
2681 pub async fn get_account_info(
2682 &self,
2683 block_number: BlockNumber,
2684 address: Address,
2685 ) -> Result<Option<AccountInfo>, StoreError> {
2686 match self.get_canonical_block_hash(block_number).await? {
2687 Some(block_hash) => self.get_account_info_by_hash(block_hash, address),
2688 None => Ok(None),
2689 }
2690 }
2691
2692 pub fn get_account_info_by_hash(
2693 &self,
2694 block_hash: BlockHash,
2695 address: Address,
2696 ) -> Result<Option<AccountInfo>, StoreError> {
2697 let Some(state_trie) = self.state_trie(block_hash)? else {
2698 return Ok(None);
2699 };
2700 let hashed_address = hash_address_fixed(&address);
2701
2702 let Some(encoded_state) = state_trie.get(hashed_address.as_bytes())? else {
2703 return Ok(None);
2704 };
2705
2706 let account_state = AccountState::decode(&encoded_state)?;
2707 Ok(Some(AccountInfo {
2708 code_hash: account_state.code_hash,
2709 balance: account_state.balance,
2710 nonce: account_state.nonce,
2711 }))
2712 }
2713
2714 pub fn get_account_state_by_acc_hash(
2715 &self,
2716 block_hash: BlockHash,
2717 account_hash: H256,
2718 ) -> Result<Option<AccountState>, StoreError> {
2719 let Some(state_trie) = self.state_trie(block_hash)? else {
2720 return Ok(None);
2721 };
2722 let Some(encoded_state) = state_trie.get(account_hash.as_bytes())? else {
2723 return Ok(None);
2724 };
2725 let account_state = AccountState::decode(&encoded_state)?;
2726 Ok(Some(account_state))
2727 }
2728
2729 pub async fn get_fork_id(&self) -> Result<ForkId, StoreError> {
2730 let chain_config = self.get_chain_config();
2731 let genesis_header = self
2732 .load_block_header(0)?
2733 .ok_or(StoreError::MissingEarliestBlockNumber)?;
2734 let block_header = self.latest_block_header.get();
2735
2736 Ok(ForkId::new(
2737 chain_config,
2738 genesis_header,
2739 block_header.timestamp,
2740 block_header.number,
2741 ))
2742 }
2743
2744 pub async fn get_code_by_account_address(
2745 &self,
2746 block_number: BlockNumber,
2747 address: Address,
2748 ) -> Result<Option<Code>, StoreError> {
2749 let Some(block_hash) = self.get_canonical_block_hash(block_number).await? else {
2750 return Ok(None);
2751 };
2752 let Some(state_trie) = self.state_trie(block_hash)? else {
2753 return Ok(None);
2754 };
2755 let hashed_address = hash_address_fixed(&address);
2756 let Some(encoded_state) = state_trie.get(hashed_address.as_bytes())? else {
2757 return Ok(None);
2758 };
2759 let account_state = AccountState::decode(&encoded_state)?;
2760 self.get_account_code(account_state.code_hash)
2761 }
2762
2763 pub async fn get_nonce_by_account_address(
2764 &self,
2765 block_number: BlockNumber,
2766 address: Address,
2767 ) -> Result<Option<u64>, StoreError> {
2768 let Some(block_hash) = self.get_canonical_block_hash(block_number).await? else {
2769 return Ok(None);
2770 };
2771 let Some(state_trie) = self.state_trie(block_hash)? else {
2772 return Ok(None);
2773 };
2774 let hashed_address = hash_address_fixed(&address);
2775 let Some(encoded_state) = state_trie.get(hashed_address.as_bytes())? else {
2776 return Ok(None);
2777 };
2778 let account_state = AccountState::decode(&encoded_state)?;
2779 Ok(Some(account_state.nonce))
2780 }
2781
2782 pub fn apply_account_updates_batch(
2785 &self,
2786 block_hash: BlockHash,
2787 account_updates: &[AccountUpdate],
2788 ) -> Result<Option<AccountUpdatesList>, StoreError> {
2789 let Some(mut state_trie) = self.state_trie(block_hash)? else {
2790 return Ok(None);
2791 };
2792
2793 Ok(Some(self.apply_account_updates_from_trie_batch(
2794 &mut state_trie,
2795 account_updates,
2796 )?))
2797 }
2798
2799 pub fn apply_account_updates_from_trie_batch<'a>(
2800 &self,
2801 state_trie: &mut Trie,
2802 account_updates: impl IntoIterator<Item = &'a AccountUpdate>,
2803 ) -> Result<AccountUpdatesList, StoreError> {
2804 let mut ret_storage_updates = Vec::new();
2805 let mut code_updates = Vec::new();
2806 let state_root = state_trie.hash_no_commit(&NativeCrypto);
2807 for update in account_updates {
2808 let hashed_address = hash_address_fixed(&update.address);
2809 if update.removed {
2810 state_trie.remove(hashed_address.as_bytes())?;
2812 continue;
2813 }
2814 let mut account_state = match state_trie.get(hashed_address.as_bytes())? {
2817 Some(encoded_state) => AccountState::decode(&encoded_state)?,
2818 None => AccountState::default(),
2819 };
2820 if update.removed_storage {
2821 account_state.storage_root = EMPTY_TRIE_HASH;
2822 }
2823 if let Some(info) = &update.info {
2824 account_state.nonce = info.nonce;
2825 account_state.balance = info.balance;
2826 account_state.code_hash = info.code_hash;
2827 if let Some(code) = &update.code {
2829 code_updates.push((info.code_hash, code.clone()));
2830 }
2831 }
2832 if !update.added_storage.is_empty() {
2834 let mut storage_trie =
2835 self.open_storage_trie(hashed_address, state_root, account_state.storage_root)?;
2836 for (storage_key, storage_value) in &update.added_storage {
2837 let hashed_key = hash_key(storage_key);
2838 if storage_value.is_zero() {
2839 storage_trie.remove(&hashed_key)?;
2840 } else {
2841 storage_trie.insert(hashed_key, storage_value.encode_to_vec())?;
2842 }
2843 }
2844 let (storage_hash, storage_updates) =
2845 storage_trie.collect_changes_since_last_hash(&NativeCrypto);
2846 account_state.storage_root = storage_hash;
2847 ret_storage_updates.push((hashed_address, storage_updates));
2848 }
2849 state_trie.insert(
2850 hashed_address.as_bytes().to_vec(),
2851 account_state.encode_to_vec(),
2852 )?;
2853 }
2854 let (state_trie_hash, state_updates) =
2855 state_trie.collect_changes_since_last_hash(&NativeCrypto);
2856
2857 Ok(AccountUpdatesList {
2858 state_trie_hash,
2859 state_updates,
2860 storage_updates: ret_storage_updates,
2861 code_updates,
2862 })
2863 }
2864
2865 pub fn apply_account_updates_from_trie_with_witness(
2868 &self,
2869 mut state_trie: Trie,
2870 account_updates: &[AccountUpdate],
2871 mut storage_tries: StorageTries,
2872 ) -> Result<(StorageTries, AccountUpdatesList), StoreError> {
2873 let mut ret_storage_updates = Vec::new();
2874
2875 let mut code_updates = Vec::new();
2876
2877 let state_root = state_trie.hash_no_commit(&NativeCrypto);
2878
2879 for update in account_updates.iter() {
2880 let hashed_address = hash_address(&update.address);
2881
2882 if update.removed {
2883 state_trie.remove(&hashed_address)?;
2885
2886 continue;
2887 }
2888
2889 let mut account_state = match state_trie.get(&hashed_address)? {
2892 Some(encoded_state) => AccountState::decode(&encoded_state)?,
2893 None => AccountState::default(),
2894 };
2895
2896 if update.removed_storage {
2897 account_state.storage_root = EMPTY_TRIE_HASH;
2898 }
2899
2900 if let Some(info) = &update.info {
2901 account_state.nonce = info.nonce;
2902
2903 account_state.balance = info.balance;
2904
2905 account_state.code_hash = info.code_hash;
2906
2907 if let Some(code) = &update.code {
2909 code_updates.push((info.code_hash, code.clone()));
2910 }
2911 }
2912
2913 if !update.added_storage.is_empty() {
2915 let (_witness, storage_trie) = match storage_tries.entry(update.address) {
2916 Entry::Occupied(value) => value.into_mut(),
2917 Entry::Vacant(vacant) => {
2918 let trie = self.open_storage_trie(
2919 H256::from_slice(&hashed_address),
2920 state_root,
2921 account_state.storage_root,
2922 )?;
2923 vacant.insert(TrieLogger::open_trie(trie))
2924 }
2925 };
2926
2927 for (storage_key, storage_value) in &update.added_storage {
2928 let hashed_key = hash_key(storage_key);
2929
2930 if storage_value.is_zero() {
2931 storage_trie.remove(&hashed_key)?;
2932 } else {
2933 storage_trie.insert(hashed_key, storage_value.encode_to_vec())?;
2934 }
2935 }
2936
2937 let (storage_hash, storage_updates) =
2938 storage_trie.collect_changes_since_last_hash(&NativeCrypto);
2939
2940 account_state.storage_root = storage_hash;
2941
2942 ret_storage_updates.push((H256::from_slice(&hashed_address), storage_updates));
2943 }
2944
2945 state_trie.insert(hashed_address, account_state.encode_to_vec())?;
2946 }
2947
2948 let (state_trie_hash, state_updates) =
2949 state_trie.collect_changes_since_last_hash(&NativeCrypto);
2950
2951 let account_updates_list = AccountUpdatesList {
2952 state_trie_hash,
2953 state_updates,
2954 storage_updates: ret_storage_updates,
2955 code_updates,
2956 };
2957
2958 Ok((storage_tries, account_updates_list))
2959 }
2960
2961 pub async fn setup_genesis_state_trie(
2963 &self,
2964 genesis_accounts: BTreeMap<Address, GenesisAccount>,
2965 ) -> Result<H256, StoreError> {
2966 let mut storage_trie_nodes = vec![];
2967 let mut genesis_state_trie = self.open_direct_state_trie(EMPTY_TRIE_HASH)?;
2968 for (address, account) in genesis_accounts {
2969 let hashed_address = hash_address(&address);
2970 let h256_hashed_address = H256::from_slice(&hashed_address);
2971
2972 let code = Code::from_bytecode(account.code, &NativeCrypto);
2974 let code_hash = code.hash;
2975 self.add_account_code(code).await?;
2976
2977 let mut storage_trie =
2979 self.open_direct_storage_trie(h256_hashed_address, EMPTY_TRIE_HASH)?;
2980 for (storage_key, storage_value) in account.storage {
2981 if !storage_value.is_zero() {
2982 let hashed_key = hash_key(&H256(storage_key.to_big_endian()));
2983 storage_trie.insert(hashed_key, storage_value.encode_to_vec())?;
2984 }
2985 }
2986
2987 let (storage_root, storage_nodes) =
2988 storage_trie.collect_changes_since_last_hash(&NativeCrypto);
2989
2990 storage_trie_nodes.extend(
2991 storage_nodes
2992 .into_iter()
2993 .map(|(path, n)| (apply_prefix(Some(h256_hashed_address), path).into_vec(), n)),
2994 );
2995
2996 let account_state = AccountState {
2998 nonce: account.nonce,
2999 balance: account.balance,
3000 storage_root,
3001 code_hash,
3002 };
3003 genesis_state_trie.insert(hashed_address, account_state.encode_to_vec())?;
3004 }
3005
3006 let (state_root, account_trie_nodes) =
3007 genesis_state_trie.collect_changes_since_last_hash(&NativeCrypto);
3008 let account_trie_nodes = account_trie_nodes
3009 .into_iter()
3010 .map(|(path, n)| (apply_prefix(None, path).into_vec(), n))
3011 .collect::<Vec<_>>();
3012
3013 let mut tx = self.backend.begin_write()?;
3014 tx.put_batch(ACCOUNT_TRIE_NODES, account_trie_nodes)?;
3015 tx.put_batch(STORAGE_TRIE_NODES, storage_trie_nodes)?;
3016 tx.commit()?;
3017
3018 Ok(state_root)
3019 }
3020
3021 fn make_witness_key(block_number: u64, block_hash: &BlockHash) -> Vec<u8> {
3023 let mut composite_key = Vec::with_capacity(8 + 32);
3024 composite_key.extend_from_slice(&block_number.to_be_bytes());
3025 composite_key.extend_from_slice(block_hash.as_bytes());
3026 composite_key
3027 }
3028
3029 pub fn store_witness(
3035 &self,
3036 block_hash: BlockHash,
3037 block_number: u64,
3038 witness: ExecutionWitness,
3039 ) -> Result<(), StoreError> {
3040 let rpc_witness = RpcExecutionWitness::try_from(witness)?;
3042 let key = Self::make_witness_key(block_number, &block_hash);
3043 let value = serde_json::to_vec(&rpc_witness)?;
3044 self.write(EXECUTION_WITNESSES, key, value)?;
3045 self.cleanup_old_witnesses(block_number)
3047 }
3048
3049 fn cleanup_old_witnesses(&self, latest_block_number: u64) -> Result<(), StoreError> {
3050 if latest_block_number <= MAX_WITNESSES {
3052 return Ok(());
3053 }
3054
3055 let threshold = latest_block_number - MAX_WITNESSES;
3056
3057 if let Some(oldest_block_number) = self.get_oldest_witness_number()? {
3058 let prefix = oldest_block_number.to_be_bytes();
3059 let mut to_delete = Vec::new();
3060
3061 {
3062 let read_txn = self.backend.begin_read()?;
3063 let iter = read_txn.prefix_iterator(EXECUTION_WITNESSES, &prefix)?;
3064
3065 for item in iter {
3067 let (key, _value) = item?;
3068 let mut block_number_bytes = [0u8; 8];
3069 block_number_bytes.copy_from_slice(&key[0..8]);
3070 let block_number = u64::from_be_bytes(block_number_bytes);
3071 if block_number > threshold {
3072 break;
3073 }
3074 to_delete.push(key.to_vec());
3075 }
3076 }
3077
3078 for key in to_delete {
3079 self.delete(EXECUTION_WITNESSES, key)?;
3080 }
3081 };
3082
3083 self.update_oldest_witness_number(threshold + 1)?;
3084
3085 Ok(())
3086 }
3087
3088 fn update_oldest_witness_number(&self, oldest_block_number: u64) -> Result<(), StoreError> {
3089 self.write(
3090 MISC_VALUES,
3091 b"oldest_witness_block_number".to_vec(),
3092 oldest_block_number.to_le_bytes().to_vec(),
3093 )?;
3094 Ok(())
3095 }
3096
3097 fn get_oldest_witness_number(&self) -> Result<Option<u64>, StoreError> {
3098 let Some(value) = self.read(MISC_VALUES, b"oldest_witness_block_number".to_vec())? else {
3099 return Ok(None);
3100 };
3101
3102 let array: [u8; 8] = value.as_slice().try_into().map_err(|_| {
3103 StoreError::Custom("Invalid oldest witness block number bytes".to_string())
3104 })?;
3105 Ok(Some(u64::from_le_bytes(array)))
3106 }
3107
3108 pub fn get_witness_json_bytes(
3114 &self,
3115 block_number: u64,
3116 block_hash: BlockHash,
3117 ) -> Result<Option<Vec<u8>>, StoreError> {
3118 let key = Self::make_witness_key(block_number, &block_hash);
3119 self.read(EXECUTION_WITNESSES, key)
3120 }
3121
3122 pub fn get_witness_by_number_and_hash(
3127 &self,
3128 block_number: u64,
3129 block_hash: BlockHash,
3130 ) -> Result<Option<RpcExecutionWitness>, StoreError> {
3131 let key = Self::make_witness_key(block_number, &block_hash);
3132 match self.read(EXECUTION_WITNESSES, key)? {
3133 Some(value) => {
3134 let witness: RpcExecutionWitness = serde_json::from_slice(&value)?;
3135 Ok(Some(witness))
3136 }
3137 None => Ok(None),
3138 }
3139 }
3140
3141 pub fn store_block_access_list(
3143 &self,
3144 block_hash: BlockHash,
3145 bal: &BlockAccessList,
3146 ) -> Result<(), StoreError> {
3147 let mut value = vec![];
3148 bal.encode(&mut value);
3149 self.store_block_access_list_encoded(block_hash, value)
3150 }
3151
3152 pub fn store_block_access_list_encoded(
3155 &self,
3156 block_hash: BlockHash,
3157 encoded: Vec<u8>,
3158 ) -> Result<(), StoreError> {
3159 self.write(BLOCK_ACCESS_LISTS, block_hash.as_bytes().to_vec(), encoded)
3160 }
3161
3162 pub fn get_block_access_list(
3164 &self,
3165 block_hash: BlockHash,
3166 ) -> Result<Option<BlockAccessList>, StoreError> {
3167 let key = block_hash.as_bytes().to_vec();
3168 match self.read(BLOCK_ACCESS_LISTS, key)? {
3169 Some(value) => {
3170 let bal = BlockAccessList::decode(&value)
3171 .map_err(|e| StoreError::Custom(format!("Failed to decode BAL: {e}")))?;
3172 Ok(Some(bal))
3173 }
3174 None => Ok(None),
3175 }
3176 }
3177
3178 pub fn iter_block_access_lists_by_hashes(
3183 &self,
3184 hashes: &[BlockHash],
3185 ) -> Result<Vec<Option<BlockAccessList>>, StoreError> {
3186 let mut out = Vec::with_capacity(hashes.len());
3187 for hash in hashes {
3188 out.push(self.get_block_access_list(*hash)?);
3189 }
3190 Ok(out)
3191 }
3192
3193 pub async fn add_initial_state(&mut self, genesis: Genesis) -> Result<(), StoreError> {
3194 self.add_initial_state_inner(genesis, false).await
3195 }
3196
3197 pub async fn add_initial_state_skip_validation(
3208 &mut self,
3209 genesis: Genesis,
3210 ) -> Result<(), StoreError> {
3211 self.add_initial_state_inner(genesis, true).await
3212 }
3213
3214 async fn add_initial_state_inner(
3215 &mut self,
3216 genesis: Genesis,
3217 skip_genesis_validation: bool,
3218 ) -> Result<(), StoreError> {
3219 debug!("Storing initial state from genesis");
3220
3221 let genesis_block = genesis.get_block();
3223 let genesis_block_number = genesis_block.header.number;
3224
3225 let genesis_hash = genesis_block.hash();
3226
3227 let stored_genesis_header = self.load_block_header(genesis_block_number)?;
3228
3229 self.set_chain_config(&genesis.config).await?;
3236
3237 if let Some(latest) = self.load_latest_block_number().await? {
3242 self.anchor_to_durable_head(latest).await?;
3243 }
3244
3245 match stored_genesis_header {
3246 Some(header) if skip_genesis_validation => {
3247 info!(
3248 stored_genesis = %header.hash(),
3249 "Skipping genesis state validation; trusting the genesis header and state already stored in the datadir"
3250 );
3251 return Ok(());
3252 }
3253 Some(header) if header.hash() == genesis_hash => {
3254 info!("Received genesis file matching a previously stored one, nothing to do");
3255 return Ok(());
3256 }
3257 Some(_) => {
3258 error!(
3259 "The chain configuration stored in the database is incompatible with the provided configuration. If you intended to switch networks, choose another datadir or clear the database (e.g., run `ethrex removedb`) and try again."
3260 );
3261 return Err(StoreError::IncompatibleChainConfig);
3262 }
3263 None => {
3264 self.add_block_header(genesis_hash, genesis_block.header.clone())
3265 .await?
3266 }
3267 }
3268 let genesis_state_root = self.setup_genesis_state_trie(genesis.alloc).await?;
3271 debug_assert_eq!(genesis_state_root, genesis_block.header.state_root);
3272
3273 info!(hash = %genesis_hash, "Storing genesis block");
3275
3276 self.add_block(genesis_block).await?;
3277 self.update_earliest_block_number(genesis_block_number)
3278 .await?;
3279 self.forkchoice_update(vec![], genesis_block_number, genesis_hash, None, None)
3280 .await?;
3281 Ok(())
3282 }
3283
3284 pub async fn load_initial_state(&self) -> Result<(), StoreError> {
3285 info!("Loading initial state from DB");
3286 let Some(latest) = self.load_latest_block_number().await? else {
3287 return Err(StoreError::MissingLatestBlockNumber);
3288 };
3289 self.anchor_to_durable_head(latest).await?;
3293 Ok(())
3294 }
3295
3296 pub fn get_storage_at(
3297 &self,
3298 block_number: BlockNumber,
3299 address: Address,
3300 storage_key: H256,
3301 ) -> Result<Option<U256>, StoreError> {
3302 match self.get_block_header(block_number)? {
3303 Some(header) => self.get_storage_at_root(header.state_root, address, storage_key),
3304 None => Ok(None),
3305 }
3306 }
3307
3308 pub fn get_storage_at_root(
3309 &self,
3310 state_root: H256,
3311 address: Address,
3312 storage_key: H256,
3313 ) -> Result<Option<U256>, StoreError> {
3314 let account_hash = hash_address_fixed(&address);
3315
3316 let read_view = self.backend.begin_read()?;
3318 let cache = self.gated_snapshot(state_root)?;
3319 let last_written = self.last_written()?;
3320 let use_fkv = Self::flatkeyvalue_computed_with_last_written(account_hash, &last_written)
3327 && !cache.overlay_serves(state_root);
3328
3329 let storage_root = if use_fkv {
3330 EMPTY_TRIE_HASH
3332 } else {
3333 let state_trie = self.open_state_trie_shared(
3334 state_root,
3335 read_view.clone(),
3336 cache.clone(),
3337 last_written.clone(),
3338 )?;
3339 let Some(encoded_account) = state_trie.get(account_hash.as_bytes())? else {
3340 return Ok(None);
3341 };
3342 let account = AccountState::decode(&encoded_account)?;
3343 account.storage_root
3344 };
3345 let storage_trie = self.open_storage_trie_shared(
3346 account_hash,
3347 state_root,
3348 storage_root,
3349 read_view,
3350 cache,
3351 last_written,
3352 )?;
3353
3354 let hashed_key = hash_key_fixed(&storage_key);
3355 storage_trie
3356 .get(&hashed_key)?
3357 .map(|rlp| U256::decode(&rlp).map_err(StoreError::RLPDecode))
3358 .transpose()
3359 }
3360
3361 pub fn get_storage_at_root_with_known_storage_root(
3366 &self,
3367 state_root: H256,
3368 account_hash: H256,
3369 storage_root: H256,
3370 storage_key: H256,
3371 ) -> Result<Option<U256>, StoreError> {
3372 let read_view = self.backend.begin_read()?;
3373 let cache = self.gated_snapshot(state_root)?;
3374 let last_written = self.last_written()?;
3375 let storage_root =
3382 if Self::flatkeyvalue_computed_with_last_written(account_hash, &last_written)
3383 && !cache.overlay_serves(state_root)
3384 {
3385 EMPTY_TRIE_HASH
3386 } else {
3387 storage_root
3388 };
3389 let storage_trie = self.open_storage_trie_shared(
3390 account_hash,
3391 state_root,
3392 storage_root,
3393 read_view,
3394 cache,
3395 last_written,
3396 )?;
3397
3398 let hashed_key = hash_key_fixed(&storage_key);
3399 storage_trie
3400 .get(&hashed_key)?
3401 .map(|rlp| U256::decode(&rlp).map_err(StoreError::RLPDecode))
3402 .transpose()
3403 }
3404
3405 pub fn get_chain_config(&self) -> ChainConfig {
3406 self.chain_config
3407 }
3408
3409 pub async fn get_latest_canonical_block_hash(&self) -> Result<Option<BlockHash>, StoreError> {
3410 Ok(Some(self.latest_block_header.get().hash()))
3411 }
3412
3413 pub async fn forkchoice_update(
3418 &self,
3419 new_canonical_blocks: Vec<(BlockNumber, BlockHash)>,
3420 head_number: BlockNumber,
3421 head_hash: BlockHash,
3422 safe: Option<BlockNumber>,
3423 finalized: Option<BlockNumber>,
3424 ) -> Result<(), StoreError> {
3425 let _guard = self.fcu_lock.lock().await;
3431
3432 let previous_head = self.latest_block_header.get();
3437 let new_head = self
3438 .get_block_header_by_hash(head_hash)?
3439 .ok_or_else(|| StoreError::MissingLatestBlockNumber)?;
3440 self.latest_block_header.update(new_head);
3441 if let Err(err) = self
3442 .forkchoice_update_inner(
3443 new_canonical_blocks,
3444 head_number,
3445 head_hash,
3446 safe,
3447 finalized,
3448 )
3449 .await
3450 {
3451 self.latest_block_header.update((*previous_head).clone());
3452 return Err(err);
3453 }
3454
3455 if let Some(root) = self.compute_safe_commit_root(head_number)? {
3461 if self.set_safe_commit_root(root)? {
3466 let tx = self.persist_tx.clone();
3467 let _ = tokio::task::spawn_blocking(move || tx.send(PersistMessage::Commit(root)))
3468 .await;
3469 }
3470 }
3471
3472 Ok(())
3473 }
3474
3475 pub(crate) fn set_safe_commit_root(&self, root: H256) -> Result<bool, StoreError> {
3483 let mut guard = self
3484 .safe_commit_root
3485 .write()
3486 .map_err(|_| StoreError::LockError)?;
3487 let changed = *guard != root;
3488 *guard = root;
3489 Ok(changed)
3490 }
3491
3492 pub(crate) fn compute_safe_commit_root(
3501 &self,
3502 head: BlockNumber,
3503 ) -> Result<Option<H256>, StoreError> {
3504 let commit_threshold = self
3505 .trie_cache
3506 .read()
3507 .map_err(|_| StoreError::LockError)?
3508 .commit_threshold;
3509 let Some(target) = head.checked_sub(commit_threshold as u64) else {
3510 return Ok(None);
3511 };
3512 let Some(hash) = self.get_canonical_block_hash_sync(target)? else {
3513 return Ok(None);
3514 };
3515 let Some(header) = self.get_block_header_by_hash(hash)? else {
3516 return Ok(None);
3517 };
3518 Ok(Some(header.state_root))
3519 }
3520
3521 pub fn state_trie(&self, block_hash: BlockHash) -> Result<Option<Trie>, StoreError> {
3523 let Some(header) = self.get_block_header_by_hash(block_hash)? else {
3524 return Ok(None);
3525 };
3526 Ok(Some(self.open_state_trie(header.state_root)?))
3527 }
3528
3529 pub fn storage_trie(
3531 &self,
3532 block_hash: BlockHash,
3533 address: Address,
3534 ) -> Result<Option<Trie>, StoreError> {
3535 let Some(header) = self.get_block_header_by_hash(block_hash)? else {
3536 return Ok(None);
3537 };
3538 let Some(state_trie) = self.state_trie(block_hash)? else {
3540 return Ok(None);
3541 };
3542 let hashed_address = hash_address_fixed(&address);
3543 let Some(encoded_account) = state_trie.get(hashed_address.as_bytes())? else {
3544 return Ok(None);
3545 };
3546 let account = AccountState::decode(&encoded_account)?;
3547 let storage_root = account.storage_root;
3549 Ok(Some(self.open_storage_trie(
3550 hashed_address,
3551 header.state_root,
3552 storage_root,
3553 )?))
3554 }
3555
3556 pub async fn get_account_state(
3557 &self,
3558 block_number: BlockNumber,
3559 address: Address,
3560 ) -> Result<Option<AccountState>, StoreError> {
3561 let Some(block_hash) = self.get_canonical_block_hash(block_number).await? else {
3562 return Ok(None);
3563 };
3564 let Some(state_trie) = self.state_trie(block_hash)? else {
3565 return Ok(None);
3566 };
3567 self.get_account_state_from_trie(&state_trie, address)
3568 }
3569
3570 pub fn get_account_state_by_root(
3571 &self,
3572 state_root: H256,
3573 address: Address,
3574 ) -> Result<Option<AccountState>, StoreError> {
3575 let state_trie = self.open_state_trie(state_root)?;
3576 self.get_account_state_from_trie(&state_trie, address)
3577 }
3578
3579 pub fn get_account_state_from_trie(
3580 &self,
3581 state_trie: &Trie,
3582 address: Address,
3583 ) -> Result<Option<AccountState>, StoreError> {
3584 let hashed_address = hash_address_fixed(&address);
3585 let Some(encoded_state) = state_trie.get(hashed_address.as_bytes())? else {
3586 return Ok(None);
3587 };
3588 Ok(Some(AccountState::decode(&encoded_state)?))
3589 }
3590
3591 pub fn get_account_states_batch_by_root(
3600 &self,
3601 state_root: H256,
3602 addresses: &[Address],
3603 ) -> Result<Vec<Option<AccountState>>, StoreError> {
3604 if addresses.is_empty() {
3605 return Ok(Vec::new());
3606 }
3607
3608 let last_written = self.last_written()?;
3609 let trie_cache = self
3610 .trie_cache
3611 .read()
3612 .map_err(|_| StoreError::LockError)?
3613 .clone();
3614
3615 let mut results: Vec<Option<AccountState>> = vec![None; addresses.len()];
3616 let leaf_paths: Vec<Vec<u8>> = addresses
3618 .iter()
3619 .map(|addr| {
3620 let hashed = hash_address_fixed(addr);
3621 Nibbles::from_bytes(hashed.as_bytes()).into_vec()
3622 })
3623 .collect();
3624
3625 let mut fkv_indices: Vec<usize> = Vec::new();
3626 let mut trie_indices: Vec<usize> = Vec::new();
3627
3628 let overlay_active = trie_cache.overlay_serves(state_root);
3644 let fkv_cursor: &[u8] = last_written.as_slice();
3645 for (i, path) in leaf_paths.iter().enumerate() {
3646 if let Some(value) = trie_cache.get(state_root, path.as_slice()) {
3647 if !value.is_empty() {
3648 results[i] = Some(AccountState::decode(&value)?);
3649 }
3650 continue;
3651 }
3652 if !overlay_active && fkv_cursor >= path.as_slice() {
3653 fkv_indices.push(i);
3654 } else {
3655 trie_indices.push(i);
3656 }
3657 }
3658
3659 if !fkv_indices.is_empty() {
3660 fkv_indices.sort_unstable_by(|&a, &b| leaf_paths[a].cmp(&leaf_paths[b]));
3667 let read_view = self.backend.begin_read()?;
3668 let keys: Vec<&[u8]> = fkv_indices
3669 .iter()
3670 .map(|&i| leaf_paths[i].as_slice())
3671 .collect();
3672 let shards = shard_count(keys.len());
3673 let raw: Vec<Result<Option<Vec<u8>>, StoreError>> = if shards <= 1 {
3674 read_view.multi_get(ACCOUNT_FLATKEYVALUE, &keys)
3675 } else {
3676 let chunk = keys.len().div_ceil(shards);
3677 let rv = read_view.as_ref();
3678 std::thread::scope(|scope| {
3679 let handles: Vec<_> = keys
3680 .chunks(chunk)
3681 .map(|ck| scope.spawn(move || rv.multi_get(ACCOUNT_FLATKEYVALUE, ck)))
3682 .collect();
3683 handles
3684 .into_iter()
3685 .flat_map(|h| {
3691 h.join().unwrap_or_else(|_| {
3692 vec![Err(StoreError::Custom(
3693 "account prefetch shard panicked".into(),
3694 ))]
3695 })
3696 })
3697 .collect()
3698 })
3699 };
3700 for (slot, res) in fkv_indices.iter().zip(raw.into_iter()) {
3701 let Some(encoded) = res? else { continue };
3702 if encoded.is_empty() {
3703 continue;
3704 }
3705 results[*slot] = Some(AccountState::decode(&encoded)?);
3706 }
3707 }
3708
3709 if !trie_indices.is_empty() {
3710 let state_trie = self.open_state_trie(state_root)?;
3716 let fetched: Result<Vec<(usize, Option<AccountState>)>, StoreError> = trie_indices
3717 .par_iter()
3718 .map(|&i| {
3719 self.get_account_state_from_trie(&state_trie, addresses[i])
3720 .map(|s| (i, s))
3721 })
3722 .collect();
3723 for (i, s) in fetched? {
3724 results[i] = s;
3725 }
3726 }
3727
3728 Ok(results)
3729 }
3730
3731 pub fn get_storage_values_batch_by_root(
3751 &self,
3752 state_root: H256,
3753 slots: &[(H256, H256, H256)],
3754 ) -> Result<Vec<Option<U256>>, StoreError> {
3755 if slots.is_empty() {
3756 return Ok(Vec::new());
3757 }
3758
3759 let last_written = self.last_written()?;
3760 let trie_cache = self
3761 .trie_cache
3762 .read()
3763 .map_err(|_| StoreError::LockError)?
3764 .clone();
3765
3766 let mut results: Vec<Option<U256>> = vec![None; slots.len()];
3767 let leaf_paths: Vec<Vec<u8>> = slots
3773 .iter()
3774 .map(|(account_hash, _storage_root, slot)| {
3775 let hashed_slot = hash_key_fixed(slot);
3776 apply_prefix(Some(*account_hash), Nibbles::from_bytes(&hashed_slot)).into_vec()
3777 })
3778 .collect();
3779
3780 let mut fkv_indices: Vec<usize> = Vec::new();
3781 let mut trie_indices: Vec<usize> = Vec::new();
3782
3783 let overlay_active = trie_cache.overlay_serves(state_root);
3797 let fkv_cursor: &[u8] = last_written.as_slice();
3798 for (i, path) in leaf_paths.iter().enumerate() {
3799 if let Some(value) = trie_cache.get(state_root, path.as_slice()) {
3804 if !value.is_empty() {
3805 results[i] = Some(U256::decode(&value).map_err(StoreError::RLPDecode)?);
3806 }
3807 continue;
3808 }
3809 if !overlay_active && fkv_cursor >= path.as_slice() {
3810 fkv_indices.push(i);
3811 } else {
3812 trie_indices.push(i);
3813 }
3814 }
3815
3816 if !fkv_indices.is_empty() {
3817 fkv_indices.sort_unstable_by(|&a, &b| leaf_paths[a].cmp(&leaf_paths[b]));
3822 let read_view = self.backend.begin_read()?;
3823 let keys: Vec<&[u8]> = fkv_indices
3824 .iter()
3825 .map(|&i| leaf_paths[i].as_slice())
3826 .collect();
3827 let shards = shard_count(keys.len());
3840 let raw: Vec<Result<Option<Vec<u8>>, StoreError>> = if shards <= 1 {
3841 read_view.multi_get(STORAGE_FLATKEYVALUE, &keys)
3842 } else {
3843 let chunk = keys.len().div_ceil(shards);
3844 let rv = read_view.as_ref();
3845 std::thread::scope(|scope| {
3846 let handles: Vec<_> = keys
3847 .chunks(chunk)
3848 .map(|ck| scope.spawn(move || rv.multi_get(STORAGE_FLATKEYVALUE, ck)))
3849 .collect();
3850 handles
3851 .into_iter()
3852 .flat_map(|h| {
3858 h.join().unwrap_or_else(|_| {
3859 vec![Err(StoreError::Custom(
3860 "storage prefetch shard panicked".into(),
3861 ))]
3862 })
3863 })
3864 .collect()
3865 })
3866 };
3867 for (slot_idx, res) in fkv_indices.iter().zip(raw.into_iter()) {
3868 let Some(encoded) = res? else { continue };
3869 if encoded.is_empty() {
3870 continue;
3871 }
3872 results[*slot_idx] = Some(U256::decode(&encoded).map_err(StoreError::RLPDecode)?);
3873 }
3874 }
3875
3876 if !trie_indices.is_empty() {
3877 let fetched: Result<Vec<(usize, Option<U256>)>, StoreError> = trie_indices
3885 .par_iter()
3886 .map(|&i| {
3887 let (account_hash, storage_root, slot) = slots[i];
3888 self.get_storage_at_root_with_known_storage_root(
3889 state_root,
3890 account_hash,
3891 storage_root,
3892 slot,
3893 )
3894 .map(|v| (i, v))
3895 })
3896 .collect();
3897 for (i, v) in fetched? {
3898 results[i] = v;
3899 }
3900 }
3901
3902 Ok(results)
3903 }
3904
3905 pub fn prefetch_trie_nodes(
3932 &self,
3933 storage_slots: &[(Address, H256)],
3934 accounts: &[Address],
3935 ) -> Result<usize, StoreError> {
3936 const MAX_PREFETCH_DEPTH: usize = 8;
3941
3942 let mut storage_keys: Vec<Vec<u8>> =
3945 Vec::with_capacity(storage_slots.len() * (MAX_PREFETCH_DEPTH + 1));
3946 for (address, slot) in storage_slots {
3947 let hashed_address = hash_address_fixed(address);
3948 let hashed_slot = hash_key_fixed(slot);
3949 let slot_nibbles = Nibbles::from_bytes(&hashed_slot);
3950 let max_d = MAX_PREFETCH_DEPTH.min(slot_nibbles.len());
3951 for d in 0..=max_d {
3952 let prefix = apply_prefix(Some(hashed_address), slot_nibbles.slice(0, d));
3953 storage_keys.push(prefix.into_vec());
3954 }
3955 }
3956
3957 let mut account_keys: Vec<Vec<u8>> =
3959 Vec::with_capacity(accounts.len() * (MAX_PREFETCH_DEPTH + 1));
3960 for address in accounts {
3961 let hashed_address = hash_address_fixed(address);
3962 let addr_nibbles = Nibbles::from_bytes(hashed_address.as_bytes());
3963 let max_d = MAX_PREFETCH_DEPTH.min(addr_nibbles.len());
3964 for d in 0..=max_d {
3965 account_keys.push(addr_nibbles.slice(0, d).into_vec());
3966 }
3967 }
3968
3969 let hits = self.warm_trie_node_keys(STORAGE_TRIE_NODES, storage_keys)?
3970 + self.warm_trie_node_keys(ACCOUNT_TRIE_NODES, account_keys)?;
3971 Ok(hits)
3972 }
3973
3974 fn warm_trie_node_keys(
3981 &self,
3982 table: &'static str,
3983 mut keys: Vec<Vec<u8>>,
3984 ) -> Result<usize, StoreError> {
3985 if keys.is_empty() {
3986 return Ok(0);
3987 }
3988 keys.sort_unstable();
3989 keys.dedup();
3990
3991 let shards = shard_count(keys.len());
3992 let read_view = self.backend.begin_read()?;
3993 let count_hits = |res: Vec<Result<Option<Vec<u8>>, StoreError>>| {
3994 res.into_iter().filter(|r| matches!(r, Ok(Some(_)))).count()
3995 };
3996 if shards <= 1 {
3997 let refs: Vec<&[u8]> = keys.iter().map(|k| k.as_slice()).collect();
3998 return Ok(count_hits(read_view.multi_get(table, &refs)));
3999 }
4000 let chunk = keys.len().div_ceil(shards);
4001 let rv = read_view.as_ref();
4002 let hits = std::thread::scope(|scope| {
4003 let handles: Vec<_> = keys
4004 .chunks(chunk)
4005 .map(|ck| {
4006 scope.spawn(move || {
4007 let refs: Vec<&[u8]> = ck.iter().map(|k| k.as_slice()).collect();
4008 count_hits(rv.multi_get(table, &refs))
4010 })
4011 })
4012 .collect();
4013 handles
4014 .into_iter()
4015 .map(|h| h.join().unwrap_or(0))
4020 .sum::<usize>()
4021 });
4022 Ok(hits)
4023 }
4024
4025 pub async fn get_account_proof(
4030 &self,
4031 state_root: H256,
4032 address: Address,
4033 storage_keys: &[H256],
4034 ) -> Result<Option<AccountProof>, StoreError> {
4035 let state_trie = self.open_state_trie(state_root)?;
4040 let address_path = hash_address_fixed(&address);
4041 let proof = state_trie.get_proof(address_path.as_bytes())?;
4042 let account_opt = state_trie
4043 .get(address_path.as_bytes())?
4044 .map(|encoded_state| AccountState::decode(&encoded_state))
4045 .transpose()?;
4046
4047 let mut storage_proof = Vec::with_capacity(storage_keys.len());
4048
4049 if let Some(account) = &account_opt {
4050 let storage_trie =
4051 self.open_storage_trie(address_path, state_root, account.storage_root)?;
4052
4053 for key in storage_keys {
4054 let hashed_key = hash_key(key);
4055 let proof = storage_trie.get_proof(&hashed_key)?;
4056 let value = storage_trie
4057 .get(&hashed_key)?
4058 .map(|rlp| U256::decode(&rlp).map_err(StoreError::RLPDecode))
4059 .transpose()?
4060 .unwrap_or_default();
4061
4062 let slot_proof = StorageSlotProof {
4063 proof,
4064 key: *key,
4065 value,
4066 };
4067 storage_proof.push(slot_proof);
4068 }
4069 } else {
4070 storage_proof.extend(storage_keys.iter().map(|key| StorageSlotProof {
4071 proof: Vec::new(),
4072 key: *key,
4073 value: U256::zero(),
4074 }));
4075 }
4076 let account = account_opt.unwrap_or_default();
4077 let account_proof = AccountProof {
4078 proof,
4079 account,
4080 storage_proof,
4081 };
4082 Ok(Some(account_proof))
4083 }
4084
4085 pub fn iter_accounts_from(
4088 &self,
4089 state_root: H256,
4090 starting_address: H256,
4091 ) -> Result<impl Iterator<Item = (H256, AccountState)>, StoreError> {
4092 let mut iter = self.open_locked_state_trie(state_root)?.into_iter();
4093 iter.advance(starting_address.0.to_vec())?;
4094 Ok(iter.content().map_while(|(path, value)| {
4095 Some((H256::from_slice(&path), AccountState::decode(&value).ok()?))
4096 }))
4097 }
4098
4099 pub fn iter_accounts(
4102 &self,
4103 state_root: H256,
4104 ) -> Result<impl Iterator<Item = (H256, AccountState)>, StoreError> {
4105 self.iter_accounts_from(state_root, H256::zero())
4106 }
4107
4108 pub fn iter_storage_from(
4111 &self,
4112 state_root: H256,
4113 hashed_address: H256,
4114 starting_slot: H256,
4115 ) -> Result<Option<impl Iterator<Item = (H256, U256)>>, StoreError> {
4116 let state_trie = self.open_locked_state_trie(state_root)?;
4117 let Some(account_rlp) = state_trie.get(hashed_address.as_bytes())? else {
4118 return Ok(None);
4119 };
4120 let storage_root = AccountState::decode(&account_rlp)?.storage_root;
4121 let mut iter = self
4122 .open_locked_storage_trie(hashed_address, state_root, storage_root)?
4123 .into_iter();
4124 iter.advance(starting_slot.0.to_vec())?;
4125 Ok(Some(iter.content().map_while(|(path, value)| {
4126 Some((H256::from_slice(&path), U256::decode(&value).ok()?))
4127 })))
4128 }
4129
4130 pub fn iter_storage(
4133 &self,
4134 state_root: H256,
4135 hashed_address: H256,
4136 ) -> Result<Option<impl Iterator<Item = (H256, U256)>>, StoreError> {
4137 self.iter_storage_from(state_root, hashed_address, H256::zero())
4138 }
4139
4140 pub fn get_account_range_proof(
4141 &self,
4142 state_root: H256,
4143 starting_hash: H256,
4144 last_hash: Option<H256>,
4145 ) -> Result<Vec<Vec<u8>>, StoreError> {
4146 let state_trie = self.open_state_trie(state_root)?;
4147 let mut proof = state_trie.get_proof(starting_hash.as_bytes())?;
4148 if let Some(last_hash) = last_hash {
4149 proof.extend_from_slice(&state_trie.get_proof(last_hash.as_bytes())?);
4150 }
4151 Ok(proof)
4152 }
4153
4154 pub fn get_storage_range_proof(
4155 &self,
4156 state_root: H256,
4157 hashed_address: H256,
4158 starting_hash: H256,
4159 last_hash: Option<H256>,
4160 ) -> Result<Option<Vec<Vec<u8>>>, StoreError> {
4161 let state_trie = self.open_state_trie(state_root)?;
4162 let Some(account_rlp) = state_trie.get(hashed_address.as_bytes())? else {
4163 return Ok(None);
4164 };
4165 let storage_root = AccountState::decode(&account_rlp)?.storage_root;
4166 let storage_trie = self.open_storage_trie(hashed_address, state_root, storage_root)?;
4167 let mut proof = storage_trie.get_proof(starting_hash.as_bytes())?;
4168 if let Some(last_hash) = last_hash {
4169 proof.extend_from_slice(&storage_trie.get_proof(last_hash.as_bytes())?);
4170 }
4171 Ok(Some(proof))
4172 }
4173
4174 pub fn get_trie_nodes(
4181 &self,
4182 state_root: H256,
4183 paths: Vec<Vec<u8>>,
4184 byte_limit: u64,
4185 ) -> Result<Vec<Vec<u8>>, StoreError> {
4186 let Some(account_path) = paths.first() else {
4187 return Ok(vec![]);
4188 };
4189 let state_trie = self.open_state_trie(state_root)?;
4190 if paths.len() == 1 {
4192 let node = state_trie.get_node(account_path)?;
4194 return Ok(vec![node]);
4195 }
4196 let Some(account_state) = state_trie
4198 .get(account_path)?
4199 .map(|ref rlp| AccountState::decode(rlp))
4200 .transpose()?
4201 else {
4202 return Ok(vec![]);
4203 };
4204 let Ok(hashed_address) = account_path.clone().try_into().map(H256) else {
4206 return Ok(vec![]);
4207 };
4208 let storage_trie =
4209 self.open_storage_trie(hashed_address, state_root, account_state.storage_root)?;
4210 let mut nodes = vec![];
4212 let mut bytes_used = 0;
4213 for path in paths.iter().skip(1) {
4217 if bytes_used >= byte_limit {
4218 break;
4219 }
4220 let node = storage_trie.get_node(path)?;
4221 bytes_used += node.len() as u64;
4222 nodes.push(node);
4223 }
4224 Ok(nodes)
4225 }
4226
4227 fn gated_snapshot(&self, state_root: H256) -> Result<Arc<TrieLayerCache>, StoreError> {
4236 self.pending_trie_roots.wait_until_ready(state_root)?;
4237 Ok(self
4238 .trie_cache
4239 .read()
4240 .map_err(|_| StoreError::LockError)?
4241 .clone())
4242 }
4243
4244 pub fn open_state_trie(&self, state_root: H256) -> Result<Trie, StoreError> {
4248 let trie_db = TrieWrapper::new(
4249 state_root,
4250 self.gated_snapshot(state_root)?,
4251 Box::new(BackendTrieDB::new_for_accounts(
4252 self.backend.clone(),
4253 self.last_written()?,
4254 )?),
4255 None,
4256 );
4257 Ok(Trie::open(Box::new(trie_db), state_root))
4258 }
4259
4260 pub fn open_direct_state_trie(&self, state_root: H256) -> Result<Trie, StoreError> {
4264 Ok(Trie::open(
4265 Box::new(BackendTrieDB::new_for_accounts(
4266 self.backend.clone(),
4267 self.last_written()?,
4268 )?),
4269 state_root,
4270 ))
4271 }
4272
4273 pub fn open_locked_state_trie(&self, state_root: H256) -> Result<Trie, StoreError> {
4277 let trie_db = TrieWrapper::new(
4278 state_root,
4279 self.gated_snapshot(state_root)?,
4280 Box::new(state_trie_locked_backend(
4281 self.backend.as_ref(),
4282 self.last_written()?,
4283 )?),
4284 None,
4285 );
4286 Ok(Trie::open(Box::new(trie_db), state_root))
4287 }
4288
4289 pub fn open_storage_trie(
4292 &self,
4293 account_hash: H256,
4294 state_root: H256,
4295 storage_root: H256,
4296 ) -> Result<Trie, StoreError> {
4297 let trie_db = TrieWrapper::new(
4298 state_root,
4299 self.gated_snapshot(state_root)?,
4300 Box::new(BackendTrieDB::new_for_storages(
4301 self.backend.clone(),
4302 self.last_written()?,
4303 )?),
4304 Some(account_hash),
4305 );
4306 Ok(Trie::open(Box::new(trie_db), storage_root))
4307 }
4308
4309 fn open_state_trie_shared(
4313 &self,
4314 state_root: H256,
4315 read_view: Arc<dyn StorageReadView>,
4316 cache: Arc<TrieLayerCache>,
4317 last_written: Vec<u8>,
4318 ) -> Result<Trie, StoreError> {
4319 let trie_db = TrieWrapper::new(
4320 state_root,
4321 cache,
4322 Box::new(BackendTrieDB::new_for_accounts_with_view(
4323 self.backend.clone(),
4324 read_view,
4325 last_written,
4326 )?),
4327 None,
4328 );
4329 Ok(Trie::open(Box::new(trie_db), state_root))
4330 }
4331
4332 fn open_storage_trie_shared(
4334 &self,
4335 account_hash: H256,
4336 state_root: H256,
4337 storage_root: H256,
4338 read_view: Arc<dyn StorageReadView>,
4339 cache: Arc<TrieLayerCache>,
4340 last_written: Vec<u8>,
4341 ) -> Result<Trie, StoreError> {
4342 let trie_db = TrieWrapper::new(
4343 state_root,
4344 cache,
4345 Box::new(BackendTrieDB::new_for_storages_with_view(
4346 self.backend.clone(),
4347 read_view,
4348 last_written,
4349 )?),
4350 Some(account_hash),
4351 );
4352 Ok(Trie::open(Box::new(trie_db), storage_root))
4353 }
4354
4355 pub fn open_direct_storage_trie(
4358 &self,
4359 account_hash: H256,
4360 storage_root: H256,
4361 ) -> Result<Trie, StoreError> {
4362 Ok(Trie::open(
4363 Box::new(BackendTrieDB::new_for_account_storage(
4364 self.backend.clone(),
4365 account_hash,
4366 self.last_written()?,
4367 )?),
4368 storage_root,
4369 ))
4370 }
4371
4372 pub fn open_locked_storage_trie(
4375 &self,
4376 account_hash: H256,
4377 state_root: H256,
4378 storage_root: H256,
4379 ) -> Result<Trie, StoreError> {
4380 let trie_db = TrieWrapper::new(
4381 state_root,
4382 self.gated_snapshot(state_root)?,
4383 Box::new(state_trie_locked_backend(
4384 self.backend.as_ref(),
4385 self.last_written()?,
4386 )?),
4387 Some(account_hash),
4388 );
4389 Ok(Trie::open(Box::new(trie_db), storage_root))
4390 }
4391
4392 pub fn has_state_root(&self, state_root: H256) -> Result<bool, StoreError> {
4393 if state_root == EMPTY_TRIE_HASH {
4395 return Ok(true);
4396 }
4397 let trie = self.open_state_trie(state_root)?;
4398 let Some(root) = trie.db().get(Nibbles::default())? else {
4400 return Ok(false);
4401 };
4402 let root_hash = ethrex_trie::Node::decode(&root)?
4403 .compute_hash(&NativeCrypto)
4404 .finalize(&NativeCrypto);
4405 Ok(state_root == root_hash)
4406 }
4407
4408 pub fn is_state_in_layer_cache(&self, state_root: H256) -> Result<bool, StoreError> {
4417 let trie = self
4418 .trie_cache
4419 .read()
4420 .map_err(|_| StoreError::LockError)?
4421 .clone();
4422 Ok(trie.contains(state_root))
4423 }
4424
4425 pub fn highest_state_history_block_number(&self) -> Result<Option<BlockNumber>, StoreError> {
4431 let read = self.backend.begin_read()?;
4432 let Some(key) = read.last_key(STATE_HISTORY)? else {
4433 return Ok(None);
4434 };
4435 let arr = <[u8; 8]>::try_from(key.as_slice()).map_err(|_| {
4436 StoreError::Custom(format!(
4437 "STATE_HISTORY key has unexpected length: {}",
4438 key.len()
4439 ))
4440 })?;
4441 Ok(Some(BlockNumber::from_be_bytes(arr)))
4442 }
4443
4444 pub fn get_state_history_block_hash(
4451 &self,
4452 block_number: BlockNumber,
4453 ) -> Result<Option<BlockHash>, StoreError> {
4454 let read = self.backend.begin_read()?;
4455 let Some(bytes) = read.get(STATE_HISTORY, &block_number.to_be_bytes())? else {
4456 return Ok(None);
4457 };
4458 JournalEntry::decode_block_hash(&bytes)
4459 .map(Some)
4460 .map_err(|e| StoreError::Custom(format!("STATE_HISTORY entry {block_number}: {e}")))
4461 }
4462
4463 pub fn lowest_state_history_block_number(&self) -> Result<Option<BlockNumber>, StoreError> {
4468 let read = self.backend.begin_read()?;
4469 let Some(key) = read.first_key(STATE_HISTORY)? else {
4470 return Ok(None);
4471 };
4472 let arr = <[u8; 8]>::try_from(key.as_slice()).map_err(|_| {
4473 StoreError::Custom(format!(
4474 "STATE_HISTORY key has unexpected length: {}",
4475 key.len()
4476 ))
4477 })?;
4478 Ok(Some(BlockNumber::from_be_bytes(arr)))
4479 }
4480
4481 pub fn reorg_retention(&self) -> Result<u64, StoreError> {
4486 let cache = self.trie_cache.read().map_err(|_| StoreError::LockError)?;
4487 Ok(cache.commit_threshold() as u64)
4488 }
4489
4490 #[doc(hidden)]
4494 pub fn put_state_history_entry_for_test(
4495 &self,
4496 block_number: BlockNumber,
4497 encoded: &[u8],
4498 ) -> Result<(), StoreError> {
4499 let mut tx = self.backend.begin_write()?;
4500 tx.put(STATE_HISTORY, &block_number.to_be_bytes(), encoded)?;
4501 tx.commit()?;
4502 Ok(())
4503 }
4504
4505 pub fn install_overlay_for_reorg(
4526 &self,
4527 from_block: BlockNumber,
4528 to_block: BlockNumber,
4529 expected_hash: impl Fn(BlockNumber) -> Option<H256>,
4530 ) -> Result<(), StoreError> {
4531 let overlay =
4533 Overlay::from_journal(self.backend.as_ref(), from_block, to_block, expected_hash)
4534 .map_err(|e| StoreError::Custom(format!("overlay construction failed: {e}")))?;
4535
4536 let threshold = {
4537 let current = self.trie_cache.read().map_err(|_| StoreError::LockError)?;
4538 current.commit_threshold()
4539 };
4540 let mut fresh =
4543 TrieLayerCache::new_with_safe_commit(threshold, self.safe_commit_root.clone());
4544 fresh.set_overlay(Arc::new(overlay));
4545
4546 self.rendezvous_persist_worker("install_overlay_for_reorg")?;
4549
4550 let mut guard = self.trie_cache.write().map_err(|_| StoreError::LockError)?;
4551 *guard = Arc::new(fresh);
4552 Ok(())
4553 }
4554
4555 fn rendezvous_persist_worker(&self, caller: &str) -> Result<(), StoreError> {
4568 let (ack_tx, ack_rx) = sync_channel::<Result<(), StoreError>>(1);
4569 self.persist_tx
4570 .send(PersistMessage::Ping(ack_tx))
4571 .map_err(|e| {
4572 StoreError::Custom(format!("{caller}: failed to ping persist worker: {e}"))
4573 })?;
4574 ack_rx.recv().map_err(|e| {
4575 StoreError::Custom(format!("{caller}: persist worker ping ack failed: {e}"))
4576 })??;
4577 Ok(())
4578 }
4579
4580 pub fn reorg_overlay_size_hint(&self) -> Result<(usize, usize), StoreError> {
4585 let guard = self.trie_cache.read().map_err(|_| StoreError::LockError)?;
4586 match guard.overlay() {
4587 Some(ov) => Ok((ov.len(), ov.byte_size())),
4588 None => Ok((0, 0)),
4589 }
4590 }
4591
4592 pub fn set_journal_pruning_paused(&self, paused: bool) {
4596 self.journal_pruning_paused
4597 .store(paused, std::sync::atomic::Ordering::Release);
4598 }
4599
4600 pub fn clear_reorg_overlay(&self) -> Result<(), StoreError> {
4604 let mut guard = self.trie_cache.write().map_err(|_| StoreError::LockError)?;
4605 let mut updated = (**guard).clone();
4606 updated.clear_overlay();
4607 *guard = Arc::new(updated);
4608 Ok(())
4609 }
4610
4611 pub fn abort_reorg(&self) -> Result<(), StoreError> {
4617 self.rendezvous_persist_worker("abort_reorg")?;
4625 let mut guard = self.trie_cache.write().map_err(|_| StoreError::LockError)?;
4626 let threshold = guard.commit_threshold();
4627 *guard = Arc::new(TrieLayerCache::new_with_safe_commit(
4628 threshold,
4629 self.safe_commit_root.clone(),
4630 ));
4631 Ok(())
4632 }
4633
4634 pub fn ancestors(&self, block_hash: BlockHash) -> AncestorIterator {
4637 AncestorIterator {
4638 store: self.clone(),
4639 next_hash: block_hash,
4640 }
4641 }
4642
4643 pub fn is_canonical_sync(&self, block_hash: BlockHash) -> Result<bool, StoreError> {
4645 let Some(block_number) = self.get_block_number_sync(block_hash)? else {
4646 return Ok(false);
4647 };
4648 Ok(self
4649 .get_canonical_block_hash_sync(block_number)?
4650 .is_some_and(|h| h == block_hash))
4651 }
4652
4653 pub fn generate_flatkeyvalue(&self) -> Result<(), StoreError> {
4654 self.flatkeyvalue_control_tx
4655 .send(FKVGeneratorControlMessage::Continue)
4656 .map_err(|_| StoreError::Custom("FlatKeyValue thread disconnected.".to_string()))
4657 }
4658
4659 pub fn create_checkpoint(&self, path: impl AsRef<Path>) -> Result<(), StoreError> {
4660 self.backend.create_checkpoint(path.as_ref())?;
4661 init_metadata_file(path.as_ref())?;
4662 Ok(())
4663 }
4664
4665 pub fn get_store_directory(&self) -> Result<PathBuf, StoreError> {
4666 Ok(self.db_path.clone())
4667 }
4668
4669 async fn load_latest_block_number(&self) -> Result<Option<BlockNumber>, StoreError> {
4671 self.read_chain_data_block_number(ChainDataIndex::LatestBlockNumber)
4672 .await
4673 }
4674
4675 fn load_canonical_block_hash(
4676 &self,
4677 block_number: BlockNumber,
4678 ) -> Result<Option<BlockHash>, StoreError> {
4679 let txn = self.backend.begin_read()?;
4680 txn.get(
4681 CANONICAL_BLOCK_HASHES,
4682 block_number.to_le_bytes().as_slice(),
4683 )?
4684 .map(|bytes| H256::decode(bytes.as_slice()))
4685 .transpose()
4686 .map_err(StoreError::from)
4687 }
4688
4689 fn load_block_header(
4690 &self,
4691 block_number: BlockNumber,
4692 ) -> Result<Option<BlockHeader>, StoreError> {
4693 let Some(block_hash) = self.load_canonical_block_hash(block_number)? else {
4694 return Ok(None);
4695 };
4696 self.load_block_header_by_hash(block_hash)
4697 }
4698
4699 fn load_block_header_by_hash(
4701 &self,
4702 block_hash: BlockHash,
4703 ) -> Result<Option<BlockHeader>, StoreError> {
4704 let txn = self.backend.begin_read()?;
4705 let hash_key = block_hash.encode_to_vec();
4706 let header_value = txn.get(HEADERS, hash_key.as_slice())?;
4707 let mut header = header_value
4708 .map(|bytes| BlockHeaderRLP::from_bytes(bytes).to())
4709 .transpose()
4710 .map_err(StoreError::from)?;
4711 header.as_mut().inspect(|h| {
4712 let _ = h.hash.set(block_hash);
4714 });
4715 Ok(header)
4716 }
4717
4718 pub fn last_written(&self) -> Result<Vec<u8>, StoreError> {
4719 let last_computed_flatkeyvalue = self
4720 .last_computed_flatkeyvalue
4721 .read()
4722 .map_err(|_| StoreError::LockError)?;
4723 Ok(last_computed_flatkeyvalue.clone())
4724 }
4725
4726 pub fn flatkeyvalue_fully_generated(&self) -> Result<bool, StoreError> {
4737 let tx = self.backend.begin_read()?;
4738 let marker = tx.get(MISC_VALUES, "last_written".as_bytes())?;
4739 Ok(Self::flatkeyvalue_generation_complete(marker.as_deref()))
4740 }
4741
4742 fn flatkeyvalue_generation_complete(marker: Option<&[u8]>) -> bool {
4746 marker == Some([0xff].as_slice())
4747 }
4748
4749 fn flatkeyvalue_computed_with_last_written(account: H256, last_written: &[u8]) -> bool {
4750 let account_nibbles = Nibbles::from_bytes(account.as_bytes());
4751 &last_written[0..64] > account_nibbles.as_ref()
4752 }
4753
4754 pub fn advance_flushed_upto(&self, block_number: BlockNumber) -> Result<(), StoreError> {
4767 if self
4768 .read_flushed_upto_opt()?
4769 .is_some_and(|current| current >= block_number)
4770 {
4771 return Ok(());
4772 }
4773 let mut tx = self.backend.begin_write()?;
4774 write_flushed_upto(tx.as_mut(), block_number)?;
4775 tx.commit()?;
4776 mutate_block_buffer(&self.block_data_buffer, |b| {
4777 b.set_flushed_upto(block_number)
4778 })
4779 }
4780
4781 pub fn read_flushed_upto(&self) -> Result<BlockNumber, StoreError> {
4786 Ok(self.read_flushed_upto_opt()?.unwrap_or(0))
4787 }
4788
4789 fn read_flushed_upto_opt(&self) -> Result<Option<BlockNumber>, StoreError> {
4792 let tx = self.backend.begin_read()?;
4793 match tx.get(MISC_VALUES, FLUSHED_UPTO_KEY)? {
4794 Some(bytes) => Ok(Some(decode_flushed_upto(&bytes)?)),
4795 None => Ok(None),
4796 }
4797 }
4798
4799 #[cfg(any(test, feature = "testing"))]
4802 pub fn buffer_block_for_test(&self, block: &Block) {
4803 mutate_block_buffer(&self.block_data_buffer, |b| {
4804 b.insert(block.clone(), vec![], vec![])
4805 })
4806 .expect("block_data_buffer lock poisoned");
4807 }
4808
4809 #[cfg(any(test, feature = "testing"))]
4812 pub fn flush_block_data_for_test(&self) -> Result<(), StoreError> {
4813 flush_block_data(self.backend.as_ref(), &self.block_data_buffer)
4814 }
4815
4816 #[cfg(any(test, feature = "testing"))]
4820 pub fn get_trie_node_for_test(
4821 &self,
4822 is_account: bool,
4823 key: &[u8],
4824 ) -> Result<Option<Vec<u8>>, StoreError> {
4825 let table = if is_account {
4826 ACCOUNT_TRIE_NODES
4827 } else {
4828 STORAGE_TRIE_NODES
4829 };
4830 self.backend.begin_read()?.get(table, key)
4831 }
4832
4833 #[cfg(any(test, feature = "testing"))]
4837 pub fn buffer_block_with_codes_for_test(&self, block: &Block, codes: Vec<(H256, Code)>) {
4838 mutate_block_buffer(&self.block_data_buffer, |b| {
4839 b.insert(block.clone(), vec![], codes)
4840 })
4841 .expect("block_data_buffer lock poisoned");
4842 }
4843
4844 #[cfg(any(test, feature = "testing"))]
4849 pub fn register_pending_root_for_test(&self, root: H256) -> Result<(), StoreError> {
4850 self.pending_trie_roots.register(root)
4851 }
4852
4853 #[cfg(any(test, feature = "testing"))]
4856 pub fn clear_pending_root_for_test(&self, root: H256) {
4857 self.pending_trie_roots.clear(root)
4858 }
4859
4860 async fn anchor_to_durable_head(&self, latest: BlockNumber) -> Result<(), StoreError> {
4879 let marker = self.read_flushed_upto_opt()?;
4880 let start = match marker {
4881 Some(flushed) => flushed.min(latest),
4882 None => latest,
4883 };
4884
4885 let mut head = start;
4886 let latest_block_header = loop {
4887 match self.load_block_header(head)? {
4888 Some(header) => break header,
4889 None if marker.is_none() => return Err(StoreError::MissingLatestBlockNumber),
4892 None if head == 0 => return Err(StoreError::MissingLatestBlockNumber),
4893 None => {
4894 warn!(
4895 "durable head {head}: canonical hash has no on-disk header \
4896 (reorg inside flush window); rewinding"
4897 );
4898 head -= 1;
4899 }
4900 }
4901 };
4902 self.latest_block_header.update(latest_block_header);
4903
4904 let reanchor = head != latest;
4909 let rewrite_marker = marker != Some(head);
4910 if reanchor || rewrite_marker {
4911 let mut tx = self.backend.begin_write()?;
4912 if reanchor {
4913 let latest_key = chain_data_key(ChainDataIndex::LatestBlockNumber);
4916 tx.put(CHAIN_DATA, &latest_key, &head.to_le_bytes())?;
4917 }
4918 if rewrite_marker {
4919 write_flushed_upto(tx.as_mut(), head)?;
4920 }
4921 tx.commit()?;
4922 }
4923 Ok(())
4924 }
4925}
4926
4927pub fn write_flushed_upto(
4931 tx: &mut dyn StorageWriteBatch,
4932 n: BlockNumber,
4933) -> Result<(), StoreError> {
4934 tx.put(MISC_VALUES, FLUSHED_UPTO_KEY, &n.to_le_bytes())
4935}
4936
4937fn decode_flushed_upto(bytes: &[u8]) -> Result<BlockNumber, StoreError> {
4943 let arr: [u8; 8] = bytes
4944 .try_into()
4945 .map_err(|_| StoreError::Custom("Invalid flushed_upto bytes".to_string()))?;
4946 Ok(BlockNumber::from_le_bytes(arr))
4947}
4948
4949fn mutate_block_buffer(
4955 buffer: &Arc<RwLock<Arc<BlockDataBuffer>>>,
4956 f: impl FnOnce(&mut BlockDataBuffer),
4957) -> Result<(), StoreError> {
4958 let mut new_buf = (*buffer.read().map_err(|_| StoreError::LockError)?.clone()).clone();
4959 f(&mut new_buf);
4960 *buffer.write().map_err(|_| StoreError::LockError)? = Arc::new(new_buf);
4961 Ok(())
4962}
4963
4964const DEFAULT_PERSIST_CHANNEL_CAPACITY: usize = 2;
4966
4967struct BlockPersist {
4973 blocks: Vec<(Block, Vec<Receipt>)>,
4974 codes: Vec<(H256, Code)>,
4975 parent_state_root: H256,
4976 child_state_root: H256,
4977 account_updates: TrieNodesUpdate,
4978 storage_updates: Vec<(H256, TrieNodesUpdate)>,
4979 commit_depth: Option<usize>,
4980 wait_for_flush: bool,
4981 block_number: BlockNumber,
4993 block_hash: H256,
4995 ack: std::sync::mpsc::SyncSender<Result<(), StoreError>>,
4996}
4997
4998enum PersistMessage {
5002 Block(Box<BlockPersist>),
5003 Commit(H256),
5009 Ping(std::sync::mpsc::SyncSender<Result<(), StoreError>>),
5010 Shutdown {
5015 ack: std::sync::mpsc::SyncSender<Result<(), StoreError>>,
5016 },
5017}
5018
5019fn write_block_data(
5024 tx: &mut dyn StorageWriteBatch,
5025 number: BlockNumber,
5026 hash: BlockHash,
5027 header: &BlockHeader,
5028 body: &BlockBody,
5029) -> Result<(), StoreError> {
5030 let hash_key = hash.encode_to_vec();
5031 tx.put(
5032 HEADERS,
5033 &hash_key,
5034 BlockHeaderRLP::from(header.clone()).bytes(),
5035 )?;
5036 tx.put(
5037 BODIES,
5038 &hash_key,
5039 BlockBodyRLP::from_bytes(body.encode_to_vec()).bytes(),
5040 )?;
5041 tx.put(BLOCK_NUMBERS, &hash_key, &number.to_le_bytes())?;
5042 for (index, transaction) in body.transactions.iter().enumerate() {
5043 tx.merge(
5044 TRANSACTION_LOCATIONS,
5045 transaction.hash(&NativeCrypto).as_bytes(),
5046 &encode_tx_location_operand(number, hash, index as u64),
5047 )?;
5048 }
5049 Ok(())
5050}
5051
5052fn flush_block_data(
5055 backend: &dyn StorageBackend,
5056 buffer: &Arc<RwLock<Arc<BlockDataBuffer>>>,
5057) -> Result<(), StoreError> {
5058 let snapshot = buffer.read().map_err(|_| StoreError::LockError)?.clone();
5059 let to_flush = snapshot.flushable();
5060 if to_flush.is_empty() {
5061 return Ok(());
5062 }
5063 let hashes: Vec<_> = to_flush.iter().map(|b| b.header.hash()).collect();
5064 let codes = snapshot.codes_for(&hashes);
5065 let mut max_number = snapshot.flushed_upto();
5066
5067 let mut tx = backend.begin_write()?;
5068 for b in &to_flush {
5069 let hash = b.header.hash();
5070 write_block_data(tx.as_mut(), b.number, hash, &b.header, &b.body)?;
5071 for (index, receipt) in b.receipts.iter().enumerate() {
5072 tx.put(
5073 RECEIPTS_V2,
5074 &receipt_key(&hash, index as u64),
5075 &receipt.encode_to_vec(),
5076 )?;
5077 }
5078 max_number = max_number.max(b.number);
5079 }
5080 for (code_hash, code) in codes {
5081 let buf = encode_code(&code);
5082 tx.put(ACCOUNT_CODES, code_hash.as_ref(), &buf)?;
5083 tx.put(
5084 ACCOUNT_CODE_METADATA,
5085 code_hash.as_ref(),
5086 &(code.len() as u64).to_be_bytes(),
5087 )?;
5088 }
5089 write_flushed_upto(tx.as_mut(), max_number)?;
5090 tx.commit()?;
5091
5092 mutate_block_buffer(buffer, |b| b.evict_flushed(max_number))
5094}
5095
5096type TrieNodesUpdate = Vec<(Nibbles, Vec<u8>)>;
5097
5098#[derive(Debug, Default)]
5104struct PendingTrieRoots {
5105 count: AtomicUsize,
5107 roots: Mutex<HashSet<H256>>,
5108 ready: Condvar,
5109}
5110
5111impl PendingTrieRoots {
5112 fn register(&self, root: H256) -> Result<(), StoreError> {
5116 let mut roots = self.roots.lock().map_err(|_| StoreError::LockError)?;
5117 if roots.insert(root) {
5118 self.count.fetch_add(1, Ordering::Release);
5119 }
5120 Ok(())
5121 }
5122
5123 fn clear(&self, root: H256) {
5128 let Ok(mut roots) = self.roots.lock() else {
5129 return;
5130 };
5131 if roots.remove(&root) {
5132 self.count.fetch_sub(1, Ordering::Release);
5133 self.ready.notify_all();
5134 }
5135 }
5136
5137 fn wait_until_ready(&self, root: H256) -> Result<(), StoreError> {
5140 if self.count.load(Ordering::Acquire) == 0 {
5141 return Ok(());
5142 }
5143 let mut roots = self.roots.lock().map_err(|_| StoreError::LockError)?;
5144 while roots.contains(&root) {
5145 roots = self.ready.wait(roots).map_err(|_| StoreError::LockError)?;
5146 }
5147 Ok(())
5148 }
5149}
5150
5151#[allow(clippy::too_many_arguments)]
5155fn apply_trie_phase1(
5156 trie_cache: &Arc<RwLock<Arc<TrieLayerCache>>>,
5157 pending_roots: &PendingTrieRoots,
5158 parent_state_root: H256,
5159 child_state_root: H256,
5160 block_number: BlockNumber,
5161 block_hash: H256,
5162 account_updates: TrieNodesUpdate,
5163 storage_updates: Vec<(H256, TrieNodesUpdate)>,
5164) -> Result<(), StoreError> {
5165 let build: Result<(), StoreError> = (|| {
5166 let new_layer = storage_updates
5167 .into_iter()
5168 .flat_map(|(account_hash, nodes)| {
5169 nodes
5170 .into_iter()
5171 .map(move |(path, node)| (apply_prefix(Some(account_hash), path), node))
5172 })
5173 .chain(account_updates)
5174 .collect();
5175 let trie = trie_cache
5176 .read()
5177 .map_err(|_| StoreError::LockError)?
5178 .clone();
5179 let mut trie_mut = (*trie).clone();
5180 trie_mut.put_batch(
5181 parent_state_root,
5182 child_state_root,
5183 block_number,
5184 block_hash,
5185 new_layer,
5186 );
5187 *trie_cache.write().map_err(|_| StoreError::LockError)? = Arc::new(trie_mut);
5188 Ok(())
5189 })();
5190 pending_roots.clear(child_state_root);
5195 build
5196}
5197
5198fn commit_trie_if_due(
5222 backend: &dyn StorageBackend,
5223 trie_cache: &Arc<RwLock<Arc<TrieLayerCache>>>,
5224 fkv_ctl: &SyncSender<FKVGeneratorControlMessage>,
5225 parent_state_root: H256,
5226 commit_depth: Option<usize>,
5227 is_batch: bool,
5228) -> Result<(), StoreError> {
5229 let trie = trie_cache
5230 .read()
5231 .map_err(|_| StoreError::LockError)?
5232 .clone();
5233 let commitable = match commit_depth {
5235 Some(depth) => trie.get_commitable_by_depth(parent_state_root, depth),
5236 None => trie.get_commitable(parent_state_root),
5237 };
5238 let Some(root) = commitable else {
5239 return Ok(());
5241 };
5242 commit_to_disk(backend, fkv_ctl, trie_cache, &trie, root, is_batch)
5243}
5244
5245fn commit_to_disk(
5255 backend: &dyn StorageBackend,
5256 fkv_ctl: &SyncSender<FKVGeneratorControlMessage>,
5257 trie_cache: &Arc<RwLock<Arc<TrieLayerCache>>>,
5258 trie: &Arc<TrieLayerCache>,
5259 root: H256,
5260 is_batch: bool,
5261) -> Result<(), StoreError> {
5262 if !trie.has_layer(root) {
5266 debug!(
5267 root = ?root,
5268 layers = trie.layer_count(),
5269 is_batch,
5270 "Skipping trie commit: state root has no in-memory layer. Expected when the block \
5271 did not change the state root (empty L2 blocks) or the root was already flushed."
5272 );
5273 return Ok(());
5274 }
5275
5276 let _ = fkv_ctl.send(FKVGeneratorControlMessage::Stop);
5279
5280 let mut trie_mut = (**trie).clone();
5282
5283 let read_view = backend.begin_read()?;
5297 let last_written = read_view
5298 .get(MISC_VALUES, "last_written".as_bytes())?
5299 .unwrap_or_default();
5300
5301 let mut write_tx = backend.begin_write()?;
5302
5303 let overlay_for_reconciliation = if !is_batch {
5313 trie.overlay().cloned()
5314 } else {
5315 None
5316 };
5317
5318 let root = if overlay_for_reconciliation.is_some() {
5328 trie.bottom_layer_root(root)
5329 } else {
5330 root
5331 };
5332
5333 let committed_layers = trie_mut.commit(root).ok_or_else(|| {
5343 StoreError::Custom(format!(
5344 "trie layer for state root {root:?} disappeared between the has_layer check and \
5345 commit (layers={}, is_batch={is_batch}): the TrieLayerCache clone lost the layer \
5346 map, so this block's state was not written",
5347 trie.layer_count(),
5348 ))
5349 })?;
5350
5351 debug_assert!(
5356 overlay_for_reconciliation.is_none() || committed_layers.len() == 1,
5357 "overlay-backed reconciliation must commit exactly one layer (T), got {}",
5358 committed_layers.len()
5359 );
5360
5361 let extra_writes: Vec<(Vec<u8>, Vec<u8>)> = match &overlay_for_reconciliation {
5367 Some(overlay) => {
5368 let layer_keys: rustc_hash::FxHashSet<&Vec<u8>> = committed_layers
5369 .iter()
5370 .flat_map(|l| l.nodes.iter().map(|(k, _)| k))
5371 .collect();
5372 overlay
5373 .iter_all_entries()
5374 .filter(|(_, key, _)| !layer_keys.contains(key))
5375 .map(|(_, key, value)| (key.clone(), value.clone().unwrap_or_default()))
5376 .collect()
5377 }
5378 None => Vec::new(),
5379 };
5380 let reorg_heights = overlay_for_reconciliation
5382 .as_ref()
5383 .map(|ov| (ov.to_block(), ov.from_block()));
5384
5385 let mut overlay: HashMap<Vec<u8>, Option<Vec<u8>>> = HashMap::new();
5395
5396 let mut result = Ok(());
5397 'layers: for layer in &committed_layers {
5398 let mut journal_account_trie: FlatDiff = Vec::new();
5406 let mut journal_storage_trie: FlatDiff = Vec::new();
5407 let mut journal_account_flat: FlatDiff = Vec::new();
5408 let mut journal_storage_flat: FlatDiff = Vec::new();
5409
5410 let is_reconciliation_layer = overlay_for_reconciliation.is_some();
5421 let extra: &[(Vec<u8>, Vec<u8>)] = if is_reconciliation_layer {
5422 &extra_writes
5423 } else {
5424 &[]
5425 };
5426
5427 let pivot_overlay = overlay_for_reconciliation.as_ref();
5435
5436 for (key, value) in layer.nodes.iter().chain(extra.iter()) {
5437 let (is_leaf, is_account) = classify_trie_key(key.len());
5438
5439 if is_leaf && key.as_slice() > last_written.as_slice() {
5444 continue;
5445 }
5446 let table = if is_leaf {
5447 if is_account {
5448 &ACCOUNT_FLATKEYVALUE
5449 } else {
5450 &STORAGE_FLATKEYVALUE
5451 }
5452 } else if is_account {
5453 &ACCOUNT_TRIE_NODES
5454 } else {
5455 &STORAGE_TRIE_NODES
5456 };
5457
5458 let prev_value = if !is_batch {
5462 match overlay.get(key) {
5463 Some(v) => Some(v.clone()),
5464 None => match pivot_overlay
5465 .and_then(|ov| ov.lookup(OverlayCf::classify_by_key_length(key.len()), key))
5466 {
5467 Some(v) => Some(v),
5468 None => match read_view.get(table, key) {
5469 Ok(v) => Some(v),
5470 Err(e) => {
5471 result = Err(e);
5472 break 'layers;
5473 }
5474 },
5475 },
5476 }
5477 } else {
5478 None
5479 };
5480
5481 let new_value = if value.is_empty() {
5482 result = write_tx.delete(table, key);
5483 None
5484 } else {
5485 result = write_tx.put(table, key, value);
5486 Some(value.clone())
5487 };
5488 if result.is_err() {
5489 break 'layers;
5490 }
5491
5492 if let Some(prev) = prev_value {
5495 let bucket = match (is_leaf, is_account) {
5496 (false, true) => &mut journal_account_trie,
5497 (false, false) => &mut journal_storage_trie,
5498 (true, true) => &mut journal_account_flat,
5499 (true, false) => &mut journal_storage_flat,
5500 };
5501 bucket.push((key.clone(), prev));
5502 overlay.insert(key.clone(), new_value);
5505 }
5506 }
5507
5508 if !is_batch && let Some((t, d)) = reorg_heights {
5513 debug_assert_eq!(
5514 layer.block_number, t,
5515 "first new-chain commit must be at the pivot's T height (overlay.to_block)"
5516 );
5517 if layer.block_number == t {
5524 let start = t.to_be_bytes();
5525 let end = d.saturating_add(1).to_be_bytes();
5526 result = write_tx.delete_range(STATE_HISTORY, &start, &end);
5527 if result.is_err() {
5528 break 'layers;
5529 }
5530 } else {
5531 error!(
5532 block_number = layer.block_number,
5533 t,
5534 d,
5535 "deep-reorg reconciliation skipped: committed layer not at pivot height; skipping STATE_HISTORY delete_range to avoid history gaps"
5536 );
5537 }
5538 }
5539
5540 if !is_batch {
5546 let entry = JournalEntry {
5547 block_hash: layer.block_hash,
5548 parent_state_root: layer.parent_state_root,
5549 account_trie_diff: journal_account_trie,
5550 storage_trie_diff: journal_storage_trie,
5551 account_flat_diff: journal_account_flat,
5552 storage_flat_diff: journal_storage_flat,
5553 };
5554 result = write_tx.put(
5555 STATE_HISTORY,
5556 &layer.block_number.to_be_bytes(),
5557 &entry.encode(),
5558 );
5559 if result.is_err() {
5560 break 'layers;
5561 }
5562 }
5563 }
5564
5565 if result.is_ok() {
5566 result = write_tx.commit();
5567 }
5568 let _ = fkv_ctl.send(FKVGeneratorControlMessage::Continue);
5570 result?;
5571
5572 if overlay_for_reconciliation.is_some() {
5575 trie_mut.clear_overlay();
5576 }
5577
5578 *trie_cache.write().map_err(|_| StoreError::LockError)? = Arc::new(trie_mut);
5584 Ok(())
5585}
5586
5587fn flatkeyvalue_generator(
5590 backend: &Arc<dyn StorageBackend>,
5591 last_computed_fkv: &RwLock<Vec<u8>>,
5592 control_rx: &std::sync::mpsc::Receiver<FKVGeneratorControlMessage>,
5593) -> Result<(), StoreError> {
5594 info!("Generation of FlatKeyValue started.");
5595 let initial_last_written = backend
5596 .begin_read()?
5597 .get(MISC_VALUES, "last_written".as_bytes())?
5598 .unwrap_or_default();
5599
5600 if initial_last_written.is_empty() {
5601 backend.clear_table(ACCOUNT_FLATKEYVALUE)?;
5603 backend.clear_table(STORAGE_FLATKEYVALUE)?;
5604 } else if initial_last_written == [0xff] {
5605 info!("FlatKeyValue already generated. Skipping.");
5607 return Ok(());
5608 }
5609
5610 loop {
5611 let read_tx = backend.begin_read()?;
5614 let root = read_tx
5615 .get(ACCOUNT_TRIE_NODES, &[])?
5616 .ok_or(StoreError::MissingLatestBlockNumber)?;
5617 let root: Node = ethrex_trie::Node::decode(&root)?;
5618 let state_root = root.compute_hash(&NativeCrypto).finalize(&NativeCrypto);
5619
5620 let last_written = read_tx
5621 .get(MISC_VALUES, "last_written".as_bytes())?
5622 .unwrap_or_default();
5623 let last_written_account = last_written
5624 .get(0..64)
5625 .map(|v| Nibbles::from_hex(v.to_vec()))
5626 .unwrap_or_default();
5627 let mut last_written_storage = last_written
5628 .get(66..130)
5629 .map(|v| Nibbles::from_hex(v.to_vec()))
5630 .unwrap_or_default();
5631
5632 debug!("Starting FlatKeyValue loop pivot={last_written:?} SR={state_root:x}");
5633
5634 let mut ctr = 0;
5635 let mut write_txn = backend.begin_write()?;
5636 let mut iter = Trie::open(
5637 Box::new(BackendTrieDB::new_for_accounts_with_view(
5638 backend.clone(),
5639 read_tx.clone(),
5640 last_written.clone(),
5641 )?),
5642 state_root,
5643 )
5644 .into_iter();
5645 if last_written_account > Nibbles::default() {
5646 iter.advance(last_written_account.to_bytes())?;
5647 }
5648 let res = iter.try_for_each(|(path, node)| -> Result<(), StoreError> {
5649 let Node::Leaf(node) = node else {
5650 return Ok(());
5651 };
5652 let account_state = AccountState::decode(&node.value)?;
5653 let account_hash = H256::from_slice(&path.to_bytes());
5654 write_txn.put(MISC_VALUES, "last_written".as_bytes(), path.as_ref())?;
5655 write_txn.put(ACCOUNT_FLATKEYVALUE, path.as_ref(), &node.value)?;
5656 ctr += 1;
5657 if ctr > 10_000 {
5658 write_txn.commit()?;
5659 write_txn = backend.begin_write()?;
5660 *last_computed_fkv
5661 .write()
5662 .map_err(|_| StoreError::LockError)? = path.as_ref().to_vec();
5663 ctr = 0;
5664 }
5665
5666 let mut iter_inner = Trie::open(
5667 Box::new(BackendTrieDB::new_for_account_storage_with_view(
5668 backend.clone(),
5669 read_tx.clone(),
5670 account_hash,
5671 path.as_ref().to_vec(),
5672 )?),
5673 account_state.storage_root,
5674 )
5675 .into_iter();
5676 if last_written_storage > Nibbles::default() {
5677 iter_inner.advance(last_written_storage.to_bytes())?;
5678 last_written_storage = Nibbles::default();
5679 }
5680 iter_inner.try_for_each(|(path, node)| -> Result<(), StoreError> {
5681 let Node::Leaf(node) = node else {
5682 return Ok(());
5683 };
5684 let key = apply_prefix(Some(account_hash), path);
5685 write_txn.put(MISC_VALUES, "last_written".as_bytes(), key.as_ref())?;
5686 write_txn.put(STORAGE_FLATKEYVALUE, key.as_ref(), &node.value)?;
5687 ctr += 1;
5688 if ctr > 10_000 {
5689 write_txn.commit()?;
5690 write_txn = backend.begin_write()?;
5691 *last_computed_fkv
5692 .write()
5693 .map_err(|_| StoreError::LockError)? = key.into_vec();
5694 ctr = 0;
5695 }
5696 fkv_check_for_stop_msg(control_rx)?;
5697 Ok(())
5698 })?;
5699 fkv_check_for_stop_msg(control_rx)?;
5700 Ok(())
5701 });
5702 match res {
5703 Err(StoreError::PivotChanged) => {
5704 match control_rx.recv() {
5705 Ok(FKVGeneratorControlMessage::Continue) => {}
5706 Ok(FKVGeneratorControlMessage::Stop) => {
5707 return Err(StoreError::Custom("Unexpected Stop message".to_string()));
5708 }
5709 Err(std::sync::mpsc::RecvError) => {
5711 info!("Store closed, stopping FlatKeyValue generation.");
5712 return Ok(());
5713 }
5714 }
5715 }
5716 Err(err) => return Err(err),
5717 Ok(()) => {
5718 write_txn.put(MISC_VALUES, "last_written".as_bytes(), &[0xff])?;
5719 write_txn.commit()?;
5720 *last_computed_fkv
5721 .write()
5722 .map_err(|_| StoreError::LockError)? = vec![0xff; 131];
5723 info!("FlatKeyValue generation finished.");
5724 return Ok(());
5725 }
5726 };
5727 }
5728}
5729
5730fn fkv_check_for_stop_msg(
5731 control_rx: &std::sync::mpsc::Receiver<FKVGeneratorControlMessage>,
5732) -> Result<(), StoreError> {
5733 match control_rx.try_recv() {
5734 Ok(FKVGeneratorControlMessage::Stop) | Err(TryRecvError::Disconnected) => {
5735 return Err(StoreError::PivotChanged);
5736 }
5737 Ok(FKVGeneratorControlMessage::Continue) => {
5738 return Err(StoreError::Custom(
5739 "Unexpected Continue message".to_string(),
5740 ));
5741 }
5742 Err(TryRecvError::Empty) => {}
5743 }
5744 Ok(())
5745}
5746
5747fn state_trie_locked_backend(
5748 backend: &dyn StorageBackend,
5749 last_written: Vec<u8>,
5750) -> Result<BackendTrieDBLocked, StoreError> {
5751 BackendTrieDBLocked::new(backend, last_written)
5753}
5754
5755pub struct AccountProof {
5756 pub proof: Vec<NodeRLP>,
5757 pub account: AccountState,
5758 pub storage_proof: Vec<StorageSlotProof>,
5759}
5760
5761pub struct StorageSlotProof {
5762 pub proof: Vec<NodeRLP>,
5763 pub key: H256,
5764 pub value: U256,
5765}
5766
5767pub struct AncestorIterator {
5768 store: Store,
5769 next_hash: BlockHash,
5770}
5771
5772impl Iterator for AncestorIterator {
5773 type Item = Result<(BlockHash, BlockHeader), StoreError>;
5774
5775 fn next(&mut self) -> Option<Self::Item> {
5776 let next_hash = self.next_hash;
5777 match self.store.get_block_header_by_hash(next_hash) {
5781 Ok(Some(header)) => {
5782 let ret_hash = self.next_hash;
5783 self.next_hash = header.parent_hash;
5784 Some(Ok((ret_hash, header)))
5785 }
5786 Ok(None) => None,
5787 Err(e) => Some(Err(e)),
5788 }
5789 }
5790}
5791
5792pub fn hash_address(address: &Address) -> Vec<u8> {
5793 keccak_hash(address.to_fixed_bytes()).to_vec()
5794}
5795
5796fn hash_address_fixed(address: &Address) -> H256 {
5797 keccak(address.to_fixed_bytes())
5798}
5799
5800pub fn hash_key(key: &H256) -> Vec<u8> {
5801 keccak_hash(key.to_fixed_bytes()).to_vec()
5802}
5803
5804pub fn hash_key_fixed(key: &H256) -> [u8; 32] {
5805 keccak_hash(key.to_fixed_bytes())
5806}
5807
5808fn chain_data_key(index: ChainDataIndex) -> Vec<u8> {
5809 (index as u8).encode_to_vec()
5810}
5811
5812fn decode_block_number(bytes: Vec<u8>) -> Result<BlockNumber, StoreError> {
5814 let array: [u8; 8] = bytes
5815 .try_into()
5816 .map_err(|_| StoreError::Custom("Invalid BlockNumber bytes".to_string()))?;
5817 Ok(BlockNumber::from_le_bytes(array))
5818}
5819
5820fn snap_state_key(index: SnapStateIndex) -> Vec<u8> {
5821 (index as u8).encode_to_vec()
5822}
5823
5824pub fn receipt_key(block_hash: &BlockHash, index: u64) -> Vec<u8> {
5826 let mut key = Vec::with_capacity(40);
5827 key.extend_from_slice(block_hash.as_bytes());
5828 key.extend_from_slice(&index.to_be_bytes());
5829 key
5830}
5831
5832pub fn encode_code(code: &Code) -> Vec<u8> {
5833 let jumpdests = code.jumpdests();
5834 let mut buf = Vec::with_capacity(6 + code.len() + jumpdests.len());
5835 code.code().encode(&mut buf);
5836 jumpdests.encode(&mut buf);
5837 buf
5838}
5839
5840fn decode_jumpdests(code: &[u8], encoded: &[u8]) -> Result<(Arc<[u8]>, bool), StoreError> {
5851 let (is_list, payload, _) = decode_rlp_item(encoded)?;
5852 if is_list {
5853 return Ok((Code::compute_jumpdests(code), true));
5854 }
5855 Ok((Arc::from(payload), false))
5856}
5857
5858#[derive(Debug, Default, Clone)]
5859struct LatestBlockHeaderCache {
5860 current: Arc<Mutex<Arc<BlockHeader>>>,
5861}
5862
5863impl LatestBlockHeaderCache {
5864 pub fn get(&self) -> Arc<BlockHeader> {
5865 self.current.lock().expect("poisoned mutex").clone()
5866 }
5867
5868 pub fn update(&self, header: BlockHeader) {
5869 let new = Arc::new(header);
5870 *self.current.lock().expect("poisoned mutex") = new;
5871 }
5872}
5873
5874#[derive(Debug, Serialize, Deserialize)]
5875pub struct StoreMetadata {
5876 pub schema_version: u64,
5877}
5878
5879impl StoreMetadata {
5880 pub fn new(schema_version: u64) -> Self {
5881 Self { schema_version }
5882 }
5883}
5884
5885fn read_store_schema_version(path: &Path) -> Result<Option<u64>, StoreError> {
5890 let metadata_path = path.join(STORE_METADATA_FILENAME);
5891 if !metadata_path.exists() {
5892 return Ok(None);
5893 }
5894 if !metadata_path.is_file() {
5895 return Err(StoreError::Custom(
5896 "store schema path exists but is not a file".to_string(),
5897 ));
5898 }
5899 let file_contents = std::fs::read_to_string(metadata_path)?;
5900 let metadata: StoreMetadata = serde_json::from_str(&file_contents)?;
5901 Ok(Some(metadata.schema_version))
5902}
5903
5904fn init_metadata_file(parent_path: &Path) -> Result<(), StoreError> {
5905 std::fs::create_dir_all(parent_path)?;
5906
5907 let metadata_path = parent_path.join(STORE_METADATA_FILENAME);
5908 let metadata = StoreMetadata::new(STORE_SCHEMA_VERSION);
5909 let serialized_metadata = serde_json::to_string_pretty(&metadata)?;
5910 let mut new_file = std::fs::File::create_new(metadata_path)?;
5911 new_file.write_all(serialized_metadata.as_bytes())?;
5912 Ok(())
5913}
5914
5915fn dir_contains_legacy_db(path: &Path) -> Result<bool, StoreError> {
5927 if path.join("CURRENT").is_file() {
5931 return Ok(true);
5932 }
5933 for entry in std::fs::read_dir(path)? {
5937 let entry = entry?;
5938 if !entry.file_type()?.is_file() {
5939 continue;
5940 }
5941 if entry.file_name().to_string_lossy().starts_with("MANIFEST-") {
5942 return Ok(true);
5943 }
5944 }
5945 Ok(false)
5946}
5947
5948pub fn has_valid_db(path: &Path) -> bool {
5952 let metadata_path = path.join(STORE_METADATA_FILENAME);
5953 if !metadata_path.is_file() {
5954 return false;
5955 }
5956 let Ok(contents) = std::fs::read_to_string(&metadata_path) else {
5957 return false;
5958 };
5959 let Ok(metadata) = serde_json::from_str::<StoreMetadata>(&contents) else {
5960 return false;
5961 };
5962 metadata.schema_version >= 1 && metadata.schema_version <= STORE_SCHEMA_VERSION
5963}
5964
5965pub fn read_chain_id_from_db(path: &Path) -> Option<u64> {
5974 if !has_valid_db(path) {
5975 return None;
5976 }
5977 #[cfg(feature = "rocksdb")]
5978 {
5979 let backend = match RocksDBBackend::open(path, MAX_ROCKSDB_BLOCK_CACHE_SIZE_BYTES) {
5982 Ok(backend) => backend,
5983 Err(e) => {
5984 warn!("Failed to open RocksDB at {path:?} to read chain ID: {e}");
5985 return None;
5986 }
5987 };
5988 let read = match backend.begin_read() {
5989 Ok(read) => read,
5990 Err(e) => {
5991 warn!("Failed to begin read transaction at {path:?}: {e}");
5992 return None;
5993 }
5994 };
5995 let key = chain_data_key(ChainDataIndex::ChainConfig);
5996 let bytes = match read.get(CHAIN_DATA, &key) {
5997 Ok(Some(bytes)) => bytes,
5998 Ok(None) => {
5999 warn!("Chain config entry not found in database at {path:?}");
6000 return None;
6001 }
6002 Err(e) => {
6003 warn!("Failed to read chain config from database at {path:?}: {e}");
6004 return None;
6005 }
6006 };
6007 #[derive(serde::Deserialize)]
6012 #[serde(rename_all = "camelCase")]
6013 struct ChainIdOnly {
6014 chain_id: u64,
6015 }
6016 match serde_json::from_slice::<ChainIdOnly>(&bytes) {
6017 Ok(partial) => Some(partial.chain_id),
6018 Err(e) => {
6019 warn!("Failed to deserialize chain ID from database at {path:?}: {e}");
6020 None
6021 }
6022 }
6023 }
6024 #[cfg(not(feature = "rocksdb"))]
6025 {
6026 let _ = path;
6027 None
6028 }
6029}
6030
6031#[cfg(test)]
6032mod account_code_tests {
6033 use super::*;
6034
6035 const JUMPDEST: u8 = 0x5b;
6036 const PUSH1: u8 = 0x60;
6037
6038 #[test]
6044 fn metadata_falls_back_to_the_bytecode_without_a_tokio_runtime() {
6045 use crate::backend::in_memory::InMemoryBackend;
6046
6047 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6048 let dir = tempfile::tempdir().unwrap();
6049 let store = Store::from_backend(
6050 backend.clone(),
6051 dir.path().to_path_buf(),
6052 1,
6053 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6054 )
6055 .unwrap();
6056
6057 let code = jumpdest_dense_code();
6060 let mut tx = backend.begin_write().unwrap();
6061 tx.put(ACCOUNT_CODES, code.hash.as_bytes(), &encode_code(&code))
6062 .unwrap();
6063 tx.commit().unwrap();
6064
6065 let metadata = store
6066 .get_code_metadata(code.hash)
6067 .expect("metadata read must not fail off-runtime");
6068 assert_eq!(metadata.map(|m| m.length), Some(code.len() as u64));
6069 }
6070
6071 fn jumpdest_dense_code() -> Code {
6075 let mut bytecode = vec![JUMPDEST; 24576];
6076 bytecode[0] = 0x00;
6077 Code::from_bytecode_unchecked(bytecode.into(), H256::zero())
6078 }
6079
6080 fn encode_code_legacy(code: &Code) -> Vec<u8> {
6083 let offsets: Vec<u32> = (0..code.len())
6084 .filter(|offset| code.is_valid_jumpdest(*offset))
6085 .map(|offset| offset as u32)
6086 .collect();
6087 let mut buf = Vec::new();
6088 code.code().encode(&mut buf);
6089 offsets.encode(&mut buf);
6090 buf
6091 }
6092
6093 #[test]
6094 fn encoded_value_carries_one_bitmap_bit_per_code_byte() {
6095 let code = jumpdest_dense_code();
6096 let encoded = encode_code(&code);
6097
6098 assert_eq!(encoded.len(), 6 + code.len() + code.len().div_ceil(8));
6099 }
6100
6101 #[test]
6102 fn round_trip_preserves_the_bitmap() {
6103 let code = jumpdest_dense_code();
6104 let encoded = encode_code(&code);
6105
6106 let (bytecode, jumpdests) = decode_bytes(&encoded).unwrap();
6107 assert_eq!(bytecode, code.code());
6108 let (rebuilt, was_legacy) = decode_jumpdests(bytecode, jumpdests).unwrap();
6109 assert_eq!(rebuilt.as_ref(), code.jumpdests());
6110 assert!(!was_legacy, "bitmap format must not be flagged legacy");
6111 }
6112
6113 #[test]
6120 fn legacy_offset_list_values_decode_to_the_same_bitmap() {
6121 for (bytecode, offsets) in [
6122 (vec![0x00, JUMPDEST, 0x00, JUMPDEST], vec![1u32, 3]),
6123 (vec![PUSH1, JUMPDEST, JUMPDEST], vec![2u32]),
6125 (vec![0x00; 32], vec![]),
6126 ] {
6127 let code = Code::from_bytecode_unchecked(bytecode.clone().into(), H256::zero());
6128 let legacy = encode_code_legacy(&code);
6129
6130 let mut expected = vec![0u8; bytecode.len().div_ceil(8)];
6131 for offset in &offsets {
6132 let index = usize::try_from(*offset).expect("offset fits usize");
6133 expected[index / 8] |= 1 << (index % 8);
6134 }
6135 if offsets.is_empty() {
6137 expected.clear();
6138 }
6139
6140 let (decoded_bytecode, jumpdests) = decode_bytes(&legacy).unwrap();
6141 assert_eq!(decoded_bytecode, code.code());
6142 let (rebuilt, was_legacy) = decode_jumpdests(decoded_bytecode, jumpdests).unwrap();
6143 assert_eq!(
6144 rebuilt.as_ref(),
6145 expected.as_slice(),
6146 "rebuilt bitmap disagrees with the legacy offsets for {bytecode:?}"
6147 );
6148 assert!(
6149 was_legacy,
6150 "legacy RLP-list format must be flagged for self-heal"
6151 );
6152 }
6153 }
6154
6155 #[test]
6158 fn repeated_inserts_do_not_inflate_the_accounted_size() {
6159 let code = jumpdest_dense_code();
6160 let mut cache = CodeCache::default();
6161
6162 cache.insert(&code).unwrap();
6163 let after_first = cache.cache_size;
6164 for _ in 0..8 {
6165 cache.insert(&code).unwrap();
6166 }
6167
6168 assert_eq!(cache.cache_size, after_first);
6169 assert_eq!(cache.entries.len(), 1);
6170 }
6171
6172 #[test]
6175 fn accounted_size_covers_the_bytecode_and_bitmap() {
6176 let code = jumpdest_dense_code();
6177 let mut cache = CodeCache::default();
6178 cache.insert(&code).unwrap();
6179
6180 assert!(cache.cache_size >= (code.len() + code.jumpdests().len()) as u64);
6181 }
6182
6183 const TEST_RNG_SEED: u64 = 0x9E37_79B9_7F4A_7C15;
6185
6186 #[test]
6187 fn cache_evicts_down_to_its_budget() {
6188 let mut cache = CodeCache {
6189 max_size: 128 * 1024,
6190 rng: TEST_RNG_SEED,
6191 ..Default::default()
6192 };
6193
6194 for i in 0..16u8 {
6195 let mut bytecode = vec![JUMPDEST; 24576];
6196 bytecode[0] = i;
6197 cache
6198 .insert(&Code::from_bytecode_unchecked(
6199 bytecode.into(),
6200 H256::from_low_u64_be(i.into()),
6201 ))
6202 .unwrap();
6203 }
6204
6205 assert!(cache.cache_size <= cache.max_size);
6206 assert!(cache.entries.len() < 16);
6207 }
6208
6209 fn numbered_code(i: u64, len: usize) -> Code {
6210 let mut bytecode = vec![JUMPDEST; len];
6211 bytecode[..8].copy_from_slice(&i.to_be_bytes());
6212 Code::from_bytecode_unchecked(bytecode.into(), H256::from_low_u64_be(i))
6213 }
6214
6215 #[test]
6219 fn a_cyclic_scan_larger_than_the_budget_still_hits() {
6220 let entry_size = CodeCache::entry_size(&numbered_code(0, 4096));
6221 let mut cache = CodeCache {
6222 max_size: 400 * entry_size,
6223 rng: TEST_RNG_SEED,
6224 ..Default::default()
6225 };
6226 let codes: Vec<Code> = (0..500).map(|i| numbered_code(i, 4096)).collect();
6228
6229 let (mut hits, mut reads) = (0u64, 0u64);
6230 for round in 0..20 {
6231 for code in &codes {
6232 let cached = cache.get(&code.hash).unwrap();
6233 if round >= 5 {
6234 reads += 1;
6235 hits += u64::from(cached.is_some());
6236 }
6237 if cached.is_none() {
6238 cache.insert(code).unwrap();
6239 }
6240 }
6241 }
6242
6243 assert!(cache.cache_size <= cache.max_size);
6244 assert!(
6245 hits * 2 > reads,
6246 "only {hits} of {reads} reads hit once the scan outgrew the budget"
6247 );
6248 }
6249
6250 #[test]
6252 fn eviction_keeps_the_entry_being_inserted() {
6253 let entry_size = CodeCache::entry_size(&numbered_code(0, 4096));
6254 let mut cache = CodeCache {
6255 max_size: 2 * entry_size,
6256 rng: TEST_RNG_SEED,
6257 ..Default::default()
6258 };
6259 for i in 0..64 {
6260 let code = numbered_code(i, 4096);
6261 cache.insert(&code).unwrap();
6262 assert!(cache.get(&code.hash).unwrap().is_some());
6263 assert!(cache.cache_size <= cache.max_size);
6264 }
6265 }
6266
6267 #[test]
6270 fn an_entry_larger_than_the_budget_is_not_cached() {
6271 let small = numbered_code(0, 1024);
6272 let mut cache = CodeCache {
6273 max_size: 4 * CodeCache::entry_size(&small),
6274 rng: TEST_RNG_SEED,
6275 ..Default::default()
6276 };
6277 cache.insert(&small).unwrap();
6278
6279 let large = numbered_code(1, 8192);
6280 assert!(CodeCache::entry_size(&large) > cache.max_size);
6281 cache.insert(&large).unwrap();
6282
6283 assert!(!cache.contains(&large.hash));
6284 assert!(cache.contains(&small.hash));
6285 assert_eq!(cache.cache_size, CodeCache::entry_size(&small));
6286 }
6287
6288 #[test]
6291 fn debug_output_leaves_out_the_cached_code() {
6292 let mut cache = CodeCache::default();
6293 cache.insert(&numbered_code(7, 4096)).unwrap();
6294 let printed = format!("{cache:?}");
6295 assert!(printed.len() < 200, "{printed}");
6296 }
6297
6298 #[test]
6301 fn small_contracts_are_charged_their_entry_overhead() {
6302 let mut cache = CodeCache {
6303 max_size: 160 * 1024,
6304 rng: TEST_RNG_SEED,
6305 ..Default::default()
6306 };
6307 let mut charged = 0;
6308 for i in 0..4096u64 {
6309 let mut bytecode = vec![JUMPDEST; 32];
6310 bytecode[..8].copy_from_slice(&i.to_be_bytes());
6311 let code = Code::from_bytecode_unchecked(bytecode.into(), H256::from_low_u64_be(i));
6312 charged = code.size() as u64 + CODE_CACHE_ENTRY_OVERHEAD;
6313 cache.insert(&code).unwrap();
6314 }
6315
6316 assert!(cache.cache_size <= cache.max_size);
6317 assert_eq!(cache.entries.len() as u64, cache.max_size / charged);
6318 }
6319
6320 #[test]
6323 fn default_cache_uses_the_configured_budget() {
6324 assert_eq!(CodeCache::default().max_size, CODE_CACHE_MAX_SIZE);
6325 }
6326
6327 #[test]
6330 fn store_uses_the_configured_code_cache_budget() {
6331 let config = StoreConfig {
6332 code_cache_size: Some(300 * 1024 * 1024),
6333 ..StoreConfig::with_rocksdb_block_cache_size(MIN_ROCKSDB_BLOCK_CACHE_SIZE_BYTES)
6334 };
6335 let store = Store::new_with_config("", EngineType::InMemory, config).unwrap();
6336 assert_eq!(store.code_cache_budget_bytes(), 300 * 1024 * 1024);
6337
6338 let config = StoreConfig::with_rocksdb_block_cache_size(MIN_ROCKSDB_BLOCK_CACHE_SIZE_BYTES);
6339 let store = Store::new_with_config("", EngineType::InMemory, config).unwrap();
6340 assert_eq!(store.code_cache_budget_bytes(), CODE_CACHE_MAX_SIZE);
6341 }
6342}
6343
6344#[cfg(test)]
6345mod state_history_tests {
6346 use super::*;
6347 use crate::api::tables::STATE_HISTORY;
6348 use crate::backend::in_memory::InMemoryBackend;
6349 use crate::journal::JournalEntry;
6350 use ethrex_common::types::{BlockBody, BlockHeader};
6351 use ethrex_trie::Nibbles;
6352 use std::time::{Duration, Instant};
6353
6354 fn make_block(number: BlockNumber, parent_hash: H256, state_root: H256) -> Block {
6355 let header = BlockHeader {
6356 number,
6357 parent_hash,
6358 state_root,
6359 ..Default::default()
6360 };
6361 Block::new(header, BlockBody::default())
6362 }
6363
6364 fn await_journal_entry(
6368 backend: &Arc<dyn StorageBackend>,
6369 block_number: BlockNumber,
6370 timeout: Duration,
6371 ) -> Option<Vec<u8>> {
6372 let key = block_number.to_be_bytes();
6373 let deadline = Instant::now() + timeout;
6374 loop {
6375 let read = backend.begin_read().ok()?;
6376 if let Ok(Some(v)) = read.get(STATE_HISTORY, &key) {
6377 return Some(v);
6378 }
6379 if Instant::now() >= deadline {
6380 return None;
6381 }
6382 std::thread::sleep(Duration::from_millis(10));
6383 }
6384 }
6385
6386 fn assert_no_journal_entry(backend: &Arc<dyn StorageBackend>, block_number: BlockNumber) {
6391 let window = Duration::from_millis(500);
6392 let key = block_number.to_be_bytes();
6393 let deadline = Instant::now() + window;
6394 loop {
6395 let read = backend.begin_read().expect("read view");
6396 let v = read.get(STATE_HISTORY, &key).expect("get");
6397 assert!(
6398 v.is_none(),
6399 "expected no STATE_HISTORY entry for block {block_number}, got {v:?}"
6400 );
6401 if Instant::now() >= deadline {
6402 return;
6403 }
6404 std::thread::sleep(Duration::from_millis(20));
6405 }
6406 }
6407
6408 #[test]
6414 fn journal_entry_written_per_block_in_regular_mode() {
6415 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6416 let dir = tempfile::tempdir().unwrap();
6417 let store = Store::from_backend(
6418 backend.clone(),
6419 dir.path().to_path_buf(),
6420 1,
6421 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6422 )
6423 .unwrap();
6424
6425 let state_root_1 = H256::repeat_byte(0x11);
6426 let block1 = make_block(1, H256::zero(), state_root_1);
6427 let block1_hash = block1.hash();
6428 store
6429 .store_block_updates(UpdateBatch {
6430 account_updates: vec![(Nibbles::from_raw(&[0x00, 0x01], false), vec![0xab, 0xcd])],
6431 storage_updates: vec![],
6432 blocks: vec![block1],
6433 receipts: vec![],
6434 code_updates: vec![],
6435 commit_depth: None,
6436 wait_for_flush: false,
6437 })
6438 .unwrap();
6439
6440 store.set_safe_commit_root(state_root_1).unwrap();
6443 let state_root_2 = H256::repeat_byte(0x22);
6444 let block2 = make_block(2, block1_hash, state_root_2);
6445 let block2_hash = block2.hash();
6446 store
6447 .store_block_updates(UpdateBatch {
6448 account_updates: vec![(Nibbles::from_raw(&[0x00, 0x02], false), vec![0xef, 0x11])],
6449 storage_updates: vec![],
6450 blocks: vec![block2],
6451 receipts: vec![],
6452 code_updates: vec![],
6453 commit_depth: None,
6454 wait_for_flush: false,
6455 })
6456 .unwrap();
6457
6458 let bytes = await_journal_entry(&backend, 1, Duration::from_secs(2))
6459 .expect("STATE_HISTORY entry for block 1 should appear after block 2 commits it");
6460 let entry = JournalEntry::decode(&bytes).unwrap();
6461 assert_eq!(entry.block_hash, block1_hash);
6462 assert_eq!(entry.parent_state_root, H256::zero());
6463 assert!(!entry.account_trie_diff.is_empty());
6464 let (path, prev) = &entry.account_trie_diff[0];
6465 assert_eq!(prev, &None, "first-time write means previous value is None");
6466 assert!(path.len() < 65);
6467
6468 store.set_safe_commit_root(state_root_2).unwrap();
6471 let state_root_3 = H256::repeat_byte(0x33);
6472 let block3 = make_block(3, block2_hash, state_root_3);
6473 store
6474 .store_block_updates(UpdateBatch {
6475 account_updates: vec![(Nibbles::from_raw(&[0x00, 0x03], false), vec![0x77])],
6476 storage_updates: vec![],
6477 blocks: vec![block3],
6478 receipts: vec![],
6479 code_updates: vec![],
6480 commit_depth: None,
6481 wait_for_flush: false,
6482 })
6483 .unwrap();
6484
6485 let bytes = await_journal_entry(&backend, 2, Duration::from_secs(2))
6486 .expect("STATE_HISTORY entry for block 2 should appear after block 3 commits it");
6487 let entry = JournalEntry::decode(&bytes).unwrap();
6488 assert_eq!(entry.block_hash, block2_hash);
6489 assert_eq!(entry.parent_state_root, state_root_1);
6490 assert!(!entry.account_trie_diff.is_empty());
6491 }
6492
6493 #[test]
6503 fn depth_gated_per_block_commit_journals_with_correct_pre_image() {
6504 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6505 let dir = tempfile::tempdir().unwrap();
6506 let store = Store::from_backend(
6507 backend.clone(),
6508 dir.path().to_path_buf(),
6509 1,
6510 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6511 )
6512 .unwrap();
6513
6514 let shared = Nibbles::from_raw(&[0x0a, 0x0b], false);
6516 let mut prev_hash = H256::zero();
6517 let mut hashes = vec![H256::zero()];
6518 let mut roots = vec![H256::zero()];
6519 for n in 1..=6u64 {
6520 let state_root = H256::repeat_byte(0xc0 | (n as u8));
6521 let block = make_block(n, prev_hash, state_root);
6522 prev_hash = block.hash();
6523 hashes.push(prev_hash);
6524 roots.push(state_root);
6525 store
6526 .store_block_updates(UpdateBatch {
6527 account_updates: vec![(shared.clone(), vec![0xb0 | (n as u8)])],
6528 storage_updates: vec![],
6529 blocks: vec![block],
6530 receipts: vec![],
6531 code_updates: vec![],
6532 commit_depth: Some(3),
6533 wait_for_flush: false,
6534 })
6535 .unwrap();
6536 }
6537
6538 let bytes = await_journal_entry(&backend, 1, Duration::from_secs(2))
6539 .expect("a depth-gated per-block commit must journal");
6540 let entry = JournalEntry::decode(&bytes).unwrap();
6541 assert_eq!(
6542 entry.block_hash, hashes[1],
6543 "entry 1 carries block 1's identity"
6544 );
6545 assert_eq!(entry.parent_state_root, H256::zero());
6546 assert_eq!(
6547 entry.account_trie_diff[0].1, None,
6548 "block 1 first-writes the key, so its pre-image is absence"
6549 );
6550
6551 let bytes =
6552 await_journal_entry(&backend, 2, Duration::from_secs(2)).expect("entry for block 2");
6553 let entry = JournalEntry::decode(&bytes).unwrap();
6554 assert_eq!(
6555 entry.block_hash, hashes[2],
6556 "entry 2 carries block 2's identity"
6557 );
6558 assert_eq!(
6559 entry.parent_state_root, roots[1],
6560 "entry 2's parent_state_root is block 1's state root"
6561 );
6562 assert_eq!(
6563 entry.account_trie_diff[0].1,
6564 Some(vec![0xb0 | 1u8]),
6565 "block 2's pre-image is the value block 1 wrote"
6566 );
6567 }
6568
6569 #[test]
6578 fn journal_skipped_in_batch_mode() {
6579 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6580 let dir = tempfile::tempdir().unwrap();
6581 let store = Store::from_backend(
6582 backend.clone(),
6583 dir.path().to_path_buf(),
6584 1,
6585 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6586 )
6587 .unwrap();
6588
6589 let mut prev_hash = H256::zero();
6590 for n in 1..=5u64 {
6591 let state_root = H256::repeat_byte(0xa0 | (n as u8));
6592 let block = make_block(n, prev_hash, state_root);
6593 prev_hash = block.hash();
6594 store
6595 .store_block_updates(UpdateBatch {
6596 account_updates: vec![(
6597 Nibbles::from_raw(&[n as u8], false),
6598 vec![0xde, 0xad, n as u8],
6599 )],
6600 storage_updates: vec![],
6601 blocks: vec![block],
6602 receipts: vec![],
6603 code_updates: vec![],
6604 commit_depth: Some(BATCH_COMMIT_THRESHOLD),
6605 wait_for_flush: true,
6606 })
6607 .unwrap();
6608 }
6609
6610 for n in 1..=5u64 {
6611 assert_no_journal_entry(&backend, n);
6612 }
6613 }
6614
6615 #[test]
6618 fn journal_storage_updates_appear_in_storage_diff() {
6619 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6620 let dir = tempfile::tempdir().unwrap();
6621 let store = Store::from_backend(
6622 backend.clone(),
6623 dir.path().to_path_buf(),
6624 1,
6625 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6626 )
6627 .unwrap();
6628
6629 let account_hash_a = H256::repeat_byte(0xa0);
6630 let account_hash_b = H256::repeat_byte(0xb0);
6631
6632 let state_root_1 = H256::repeat_byte(0x33);
6633 let block1 = make_block(1, H256::zero(), state_root_1);
6634 let block1_hash = block1.hash();
6635 store
6636 .store_block_updates(UpdateBatch {
6637 account_updates: vec![],
6638 storage_updates: vec![
6639 (
6640 account_hash_a,
6641 vec![(Nibbles::from_raw(&[0x05], false), vec![0x01])],
6642 ),
6643 (
6644 account_hash_b,
6645 vec![(Nibbles::from_raw(&[0x06], false), vec![0x02])],
6646 ),
6647 ],
6648 blocks: vec![block1],
6649 receipts: vec![],
6650 code_updates: vec![],
6651 commit_depth: None,
6652 wait_for_flush: false,
6653 })
6654 .unwrap();
6655
6656 store.set_safe_commit_root(state_root_1).unwrap();
6659 let state_root_2 = H256::repeat_byte(0x44);
6660 let block2 = make_block(2, block1_hash, state_root_2);
6661 store
6662 .store_block_updates(UpdateBatch {
6663 account_updates: vec![(Nibbles::from_raw(&[0xee], false), vec![0xff])],
6664 storage_updates: vec![],
6665 blocks: vec![block2],
6666 receipts: vec![],
6667 code_updates: vec![],
6668 commit_depth: None,
6669 wait_for_flush: false,
6670 })
6671 .unwrap();
6672
6673 let bytes = await_journal_entry(&backend, 1, Duration::from_secs(2))
6674 .expect("STATE_HISTORY entry for block 1");
6675 let entry = JournalEntry::decode(&bytes).unwrap();
6676 assert_eq!(entry.block_hash, block1_hash);
6677 assert_eq!(
6678 entry.storage_trie_diff.len(),
6679 2,
6680 "two distinct account hashes must produce two storage_trie entries"
6681 );
6682 for (_key, prev) in &entry.storage_trie_diff {
6683 assert_eq!(prev, &None, "first-time storage write has None pre-image");
6684 }
6685 assert!(entry.account_trie_diff.is_empty());
6686 }
6687
6688 fn seed_journal_entries(backend: &Arc<dyn StorageBackend>, block_numbers: &[BlockNumber]) {
6689 let mut tx = backend.begin_write().unwrap();
6690 for n in block_numbers {
6691 let entry = JournalEntry {
6692 block_hash: H256::repeat_byte(*n as u8),
6693 parent_state_root: H256::zero(),
6694 account_trie_diff: vec![(vec![*n as u8], None)],
6695 storage_trie_diff: vec![],
6696 account_flat_diff: vec![],
6697 storage_flat_diff: vec![],
6698 };
6699 tx.put(STATE_HISTORY, &n.to_be_bytes(), &entry.encode())
6700 .unwrap();
6701 }
6702 tx.commit().unwrap();
6703 }
6704
6705 fn journal_entry_exists(backend: &Arc<dyn StorageBackend>, block_number: BlockNumber) -> bool {
6706 backend
6707 .begin_read()
6708 .unwrap()
6709 .get(STATE_HISTORY, &block_number.to_be_bytes())
6710 .unwrap()
6711 .is_some()
6712 }
6713
6714 #[tokio::test]
6717 async fn finality_advance_prunes_journal_below_boundary() {
6718 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6719 let dir = tempfile::tempdir().unwrap();
6720 let store = Store::from_backend(
6721 backend.clone(),
6722 dir.path().to_path_buf(),
6723 1,
6724 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6725 )
6726 .unwrap();
6727
6728 seed_journal_entries(&backend, &(1..=10).collect::<Vec<_>>());
6729 for n in 1..=10 {
6730 assert!(journal_entry_exists(&backend, n), "seed entry {n} present");
6731 }
6732
6733 store
6734 .forkchoice_update_inner(vec![], 100, H256::zero(), None, Some(5))
6735 .await
6736 .unwrap();
6737
6738 for n in 1..=5 {
6739 assert!(
6740 !journal_entry_exists(&backend, n),
6741 "entry {n} should have been pruned (<= finalized)"
6742 );
6743 }
6744 for n in 6..=10 {
6745 assert!(
6746 journal_entry_exists(&backend, n),
6747 "entry {n} should remain (> finalized)"
6748 );
6749 }
6750 }
6751
6752 #[tokio::test]
6754 async fn finality_no_op_does_not_prune() {
6755 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6756 let dir = tempfile::tempdir().unwrap();
6757 let store = Store::from_backend(
6758 backend.clone(),
6759 dir.path().to_path_buf(),
6760 1,
6761 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6762 )
6763 .unwrap();
6764
6765 store
6766 .forkchoice_update_inner(vec![], 100, H256::zero(), None, Some(5))
6767 .await
6768 .unwrap();
6769
6770 seed_journal_entries(&backend, &(6..=10).collect::<Vec<_>>());
6771
6772 store
6774 .forkchoice_update_inner(vec![], 100, H256::zero(), None, Some(5))
6775 .await
6776 .unwrap();
6777
6778 for n in 6..=10 {
6779 assert!(
6780 journal_entry_exists(&backend, n),
6781 "entry {n} should still exist after no-op finality update"
6782 );
6783 }
6784
6785 store
6787 .forkchoice_update_inner(vec![], 100, H256::zero(), None, None)
6788 .await
6789 .unwrap();
6790
6791 for n in 6..=10 {
6792 assert!(
6793 journal_entry_exists(&backend, n),
6794 "entry {n} should still exist when finalized is None"
6795 );
6796 }
6797 }
6798
6799 #[tokio::test]
6803 async fn malformed_finalized_returns_error_not_silent_zero() {
6804 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6805 let dir = tempfile::tempdir().unwrap();
6806 let store = Store::from_backend(
6807 backend.clone(),
6808 dir.path().to_path_buf(),
6809 1,
6810 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6811 )
6812 .unwrap();
6813
6814 let mut tx = backend.begin_write().unwrap();
6816 let finalized_key = chain_data_key(ChainDataIndex::FinalizedBlockNumber);
6817 tx.put(CHAIN_DATA, &finalized_key, &[0u8, 0, 0, 0]).unwrap();
6818 tx.commit().unwrap();
6819
6820 seed_journal_entries(&backend, &(1..=5).collect::<Vec<_>>());
6821
6822 let err = store
6823 .forkchoice_update_inner(vec![], 100, H256::zero(), None, Some(3))
6824 .await
6825 .expect_err("malformed finalized must surface as an error");
6826 let msg = format!("{err}");
6827 assert!(
6828 msg.contains("FinalizedBlockNumber has unexpected length"),
6829 "expected length-mismatch error, got: {msg}"
6830 );
6831
6832 for n in 1..=5 {
6834 assert!(
6835 journal_entry_exists(&backend, n),
6836 "entry {n} must NOT be pruned when finalized read failed"
6837 );
6838 }
6839 }
6840
6841 #[tokio::test]
6848 async fn highest_state_history_block_number_finds_max() {
6849 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6850 let dir = tempfile::tempdir().unwrap();
6851 let store = Store::from_backend(
6852 backend.clone(),
6853 dir.path().to_path_buf(),
6854 1,
6855 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6856 )
6857 .unwrap();
6858
6859 assert_eq!(store.highest_state_history_block_number().unwrap(), None);
6860
6861 seed_journal_entries(&backend, &[3, 7, 5, 11, 8]);
6862 assert_eq!(
6863 store.highest_state_history_block_number().unwrap(),
6864 Some(11),
6865 "max over present entries"
6866 );
6867 }
6868
6869 #[tokio::test]
6873 async fn journaled_block_hash_reads_back() {
6874 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6875 let dir = tempfile::tempdir().unwrap();
6876 let store = Store::from_backend(
6877 backend.clone(),
6878 dir.path().to_path_buf(),
6879 1,
6880 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6881 )
6882 .unwrap();
6883
6884 seed_journal_entries(&backend, &[3, 11]);
6885 assert_eq!(
6886 store.get_state_history_block_hash(11).unwrap(),
6887 Some(H256::repeat_byte(11))
6888 );
6889 assert_eq!(store.get_state_history_block_hash(4).unwrap(), None);
6890 }
6891
6892 #[tokio::test]
6895 async fn advance_flushed_upto_only_moves_forward() {
6896 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6897 let dir = tempfile::tempdir().unwrap();
6898 let store = Store::from_backend(
6899 backend,
6900 dir.path().to_path_buf(),
6901 1,
6902 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6903 )
6904 .unwrap();
6905
6906 store.advance_flushed_upto(10).unwrap();
6907 assert_eq!(store.read_flushed_upto().unwrap(), 10);
6908 store.advance_flushed_upto(5).unwrap();
6909 assert_eq!(store.read_flushed_upto().unwrap(), 10);
6910 store.advance_flushed_upto(12).unwrap();
6911 assert_eq!(store.read_flushed_upto().unwrap(), 12);
6912 }
6913
6914 #[tokio::test]
6918 async fn lowest_state_history_block_number_finds_min() {
6919 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6920 let dir = tempfile::tempdir().unwrap();
6921 let store = Store::from_backend(
6922 backend.clone(),
6923 dir.path().to_path_buf(),
6924 1,
6925 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6926 )
6927 .unwrap();
6928
6929 assert_eq!(store.lowest_state_history_block_number().unwrap(), None);
6930
6931 seed_journal_entries(&backend, &[7, 3, 11, 5, 8]);
6932 assert_eq!(
6933 store.lowest_state_history_block_number().unwrap(),
6934 Some(3),
6935 "min over present entries"
6936 );
6937 }
6938
6939 #[tokio::test]
6943 async fn install_overlay_replaces_cache_with_fresh_one() {
6944 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6945 let dir = tempfile::tempdir().unwrap();
6946 let store = Store::from_backend(
6947 backend.clone(),
6948 dir.path().to_path_buf(),
6949 4,
6950 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6951 )
6952 .unwrap();
6953
6954 seed_journal_entries(&backend, &[3, 4]);
6955
6956 {
6958 let mut guard = store.trie_cache.write().unwrap();
6959 let mut updated = (**guard).clone();
6960 updated.put_batch(
6961 H256::zero(),
6962 H256::repeat_byte(0xff),
6963 42,
6964 H256::repeat_byte(0x42),
6965 vec![],
6966 );
6967 *guard = Arc::new(updated);
6968 }
6969 assert!(
6970 store
6971 .is_state_in_layer_cache(H256::repeat_byte(0xff))
6972 .unwrap()
6973 );
6974
6975 store
6976 .install_overlay_for_reorg(4, 3, |_| None)
6977 .expect("install must succeed");
6978
6979 assert!(
6980 !store
6981 .is_state_in_layer_cache(H256::repeat_byte(0xff))
6982 .unwrap(),
6983 "old cache layer must be gone after swap"
6984 );
6985 let cache = store.trie_cache.read().unwrap().clone();
6986 assert!(cache.overlay().is_some(), "overlay must be installed");
6987 assert_eq!(
6988 cache.commit_threshold(),
6989 4,
6990 "fresh cache must inherit threshold"
6991 );
6992 }
6993
6994 #[tokio::test]
6997 async fn install_overlay_failure_preserves_cache() {
6998 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6999 let dir = tempfile::tempdir().unwrap();
7000 let store = Store::from_backend(
7001 backend.clone(),
7002 dir.path().to_path_buf(),
7003 4,
7004 DEFAULT_PERSIST_CHANNEL_CAPACITY,
7005 )
7006 .unwrap();
7007
7008 seed_journal_entries(&backend, &[5]);
7010 {
7011 let mut guard = store.trie_cache.write().unwrap();
7012 let mut updated = (**guard).clone();
7013 updated.put_batch(
7014 H256::zero(),
7015 H256::repeat_byte(0xaa),
7016 7,
7017 H256::repeat_byte(0x77),
7018 vec![],
7019 );
7020 *guard = Arc::new(updated);
7021 }
7022 assert!(
7023 store
7024 .is_state_in_layer_cache(H256::repeat_byte(0xaa))
7025 .unwrap()
7026 );
7027
7028 let err = store
7029 .install_overlay_for_reorg(5, 3, |_| None)
7030 .expect_err("missing entries 3 and 4 must abort");
7031 let msg = format!("{err}");
7032 assert!(
7033 msg.contains("overlay construction failed"),
7034 "error should explain construction failure: {msg}"
7035 );
7036
7037 assert!(
7039 store
7040 .is_state_in_layer_cache(H256::repeat_byte(0xaa))
7041 .unwrap(),
7042 "cache must survive a failed overlay install"
7043 );
7044 let cache = store.trie_cache.read().unwrap().clone();
7045 assert!(cache.overlay().is_none(), "no overlay should be installed");
7046 }
7047
7048 #[tokio::test]
7052 async fn abort_reorg_resets_cache_to_fresh() {
7053 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
7054 let dir = tempfile::tempdir().unwrap();
7055 let store = Store::from_backend(
7056 backend.clone(),
7057 dir.path().to_path_buf(),
7058 4,
7059 DEFAULT_PERSIST_CHANNEL_CAPACITY,
7060 )
7061 .unwrap();
7062
7063 seed_journal_entries(&backend, &[3, 4]);
7064 store.install_overlay_for_reorg(4, 3, |_| None).unwrap();
7065 {
7067 let mut guard = store.trie_cache.write().unwrap();
7068 let mut updated = (**guard).clone();
7069 updated.put_batch(
7070 H256::zero(),
7071 H256::repeat_byte(0xbb),
7072 4,
7073 H256::repeat_byte(0xb4),
7074 vec![],
7075 );
7076 *guard = Arc::new(updated);
7077 }
7078 assert!(
7079 store
7080 .is_state_in_layer_cache(H256::repeat_byte(0xbb))
7081 .unwrap()
7082 );
7083
7084 store.abort_reorg().unwrap();
7085
7086 assert!(
7087 !store
7088 .is_state_in_layer_cache(H256::repeat_byte(0xbb))
7089 .unwrap(),
7090 "partial new-chain layer must be discarded"
7091 );
7092 let cache = store.trie_cache.read().unwrap().clone();
7093 assert!(cache.overlay().is_none(), "overlay must be cleared");
7094 assert_eq!(
7095 cache.commit_threshold(),
7096 4,
7097 "fresh cache must inherit threshold"
7098 );
7099 }
7100
7101 #[tokio::test]
7104 async fn clear_reorg_overlay_removes_overlay_and_is_idempotent() {
7105 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
7106 let dir = tempfile::tempdir().unwrap();
7107 let store = Store::from_backend(
7108 backend.clone(),
7109 dir.path().to_path_buf(),
7110 4,
7111 DEFAULT_PERSIST_CHANNEL_CAPACITY,
7112 )
7113 .unwrap();
7114
7115 store.clear_reorg_overlay().unwrap();
7117 assert!(store.trie_cache.read().unwrap().overlay().is_none());
7118
7119 seed_journal_entries(&backend, &[7]);
7120 store.install_overlay_for_reorg(7, 7, |_| None).unwrap();
7121 assert!(store.trie_cache.read().unwrap().overlay().is_some());
7122
7123 store.clear_reorg_overlay().unwrap();
7124 assert!(store.trie_cache.read().unwrap().overlay().is_none());
7125
7126 store.clear_reorg_overlay().unwrap();
7128 assert!(store.trie_cache.read().unwrap().overlay().is_none());
7129 }
7130
7131 #[tokio::test]
7139 async fn deep_reorg_overlay_disables_flat_kv_fast_paths() {
7140 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
7141
7142 {
7146 let mut tx = backend.begin_write().unwrap();
7147 tx.put(MISC_VALUES, "last_written".as_bytes(), &[0xff])
7148 .unwrap();
7149 tx.commit().unwrap();
7150 }
7151
7152 let dir = tempfile::tempdir().unwrap();
7153 let store = Store::from_backend(
7154 backend.clone(),
7155 dir.path().to_path_buf(),
7156 1,
7157 DEFAULT_PERSIST_CHANNEL_CAPACITY,
7158 )
7159 .unwrap();
7160
7161 let address = Address::from_low_u64_be(0xbeef);
7166 let account_hash = hash_address_fixed(&address);
7167 let slot = H256::from_low_u64_be(1);
7168 let slot_hash = hash_key_fixed(&slot);
7169 let generator_account = AccountState {
7170 nonce: 7,
7171 balance: U256::from(999u64),
7172 storage_root: H256::zero(),
7173 code_hash: H256::zero(),
7174 };
7175 let generator_slot_value = U256::from(42u64);
7176 {
7177 let mut tx = backend.begin_write().unwrap();
7178 tx.put(
7179 ACCOUNT_FLATKEYVALUE,
7180 &Nibbles::from_bytes(account_hash.as_bytes()).into_vec(),
7181 &generator_account.encode_to_vec(),
7182 )
7183 .unwrap();
7184 tx.put(
7185 STORAGE_FLATKEYVALUE,
7186 &apply_prefix(Some(account_hash), Nibbles::from_bytes(&slot_hash)).into_vec(),
7187 &generator_slot_value.encode_to_vec(),
7188 )
7189 .unwrap();
7190 tx.commit().unwrap();
7191 }
7192
7193 let pivot_root = EMPTY_TRIE_HASH;
7198 {
7199 let mut tx = backend.begin_write().unwrap();
7200 for (n, parent_root) in [(10u64, pivot_root), (11, H256::repeat_byte(0x10))] {
7201 let entry = JournalEntry {
7202 block_hash: H256::repeat_byte(n as u8),
7203 parent_state_root: parent_root,
7204 account_trie_diff: vec![],
7205 storage_trie_diff: vec![],
7206 account_flat_diff: vec![],
7207 storage_flat_diff: vec![],
7208 };
7209 tx.put(STATE_HISTORY, &n.to_be_bytes(), &entry.encode())
7210 .unwrap();
7211 }
7212 tx.commit().unwrap();
7213 }
7214
7215 store
7216 .install_overlay_for_reorg(11, 10, |_| None)
7217 .expect("overlay install must succeed");
7218
7219 let accounts = store
7222 .get_account_states_batch_by_root(pivot_root, &[address])
7223 .unwrap();
7224 assert_eq!(
7225 accounts,
7226 vec![None],
7227 "batch account read must not serve the generator's stale flat-KV value"
7228 );
7229
7230 let slot_value = store
7231 .get_storage_at_root(pivot_root, address, slot)
7232 .unwrap();
7233 assert_eq!(
7234 slot_value, None,
7235 "storage read must not serve the generator's stale flat-KV value"
7236 );
7237
7238 let batched_slots = store
7239 .get_storage_values_batch_by_root(
7240 pivot_root,
7241 &[(hash_address_fixed(&address), EMPTY_TRIE_HASH, slot)],
7242 )
7243 .unwrap();
7244 assert_eq!(
7245 batched_slots,
7246 vec![None],
7247 "batch storage read must not serve the generator's stale flat-KV value"
7248 );
7249
7250 let state_trie = store.open_state_trie(pivot_root).unwrap();
7251 assert_eq!(
7252 state_trie.get(account_hash.as_bytes()).unwrap(),
7253 None,
7254 "trie read must not serve the generator's stale flat-KV value"
7255 );
7256
7257 let unserved_root = H256::repeat_byte(0x99);
7260 let accounts = store
7261 .get_account_states_batch_by_root(unserved_root, &[address])
7262 .unwrap();
7263 assert_eq!(
7264 accounts,
7265 vec![Some(generator_account)],
7266 "unserved roots must keep the flat-KV fast path"
7267 );
7268 let slot_value = store
7269 .get_storage_at_root(unserved_root, address, slot)
7270 .unwrap();
7271 assert_eq!(
7272 slot_value,
7273 Some(generator_slot_value),
7274 "unserved roots must keep the flat-KV fast path"
7275 );
7276
7277 let batched_slots = store
7278 .get_storage_values_batch_by_root(
7279 unserved_root,
7280 &[(hash_address_fixed(&address), EMPTY_TRIE_HASH, slot)],
7281 )
7282 .unwrap();
7283 assert_eq!(
7284 batched_slots,
7285 vec![Some(generator_slot_value)],
7286 "unserved roots must keep the flat-KV fast path"
7287 );
7288 }
7289
7290 #[tokio::test]
7298 async fn overlay_backed_commit_only_commits_bottom_layer_per_pass() {
7299 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
7300 let dir = tempfile::tempdir().unwrap();
7301 let store = Store::from_backend(
7302 backend.clone(),
7303 dir.path().to_path_buf(),
7304 4,
7305 DEFAULT_PERSIST_CHANNEL_CAPACITY,
7306 )
7307 .unwrap();
7308
7309 let pivot_root = H256::repeat_byte(0x09);
7312 {
7313 let mut tx = backend.begin_write().unwrap();
7314 for (n, parent_root) in [(10u64, pivot_root), (11, H256::repeat_byte(0x0a))] {
7315 let entry = JournalEntry {
7316 block_hash: H256::repeat_byte(n as u8),
7317 parent_state_root: parent_root,
7318 account_trie_diff: vec![(vec![0x00, n as u8], None)],
7319 storage_trie_diff: vec![],
7320 account_flat_diff: vec![],
7321 storage_flat_diff: vec![],
7322 };
7323 tx.put(STATE_HISTORY, &n.to_be_bytes(), &entry.encode())
7324 .unwrap();
7325 }
7326 tx.commit().unwrap();
7327 }
7328 store.install_overlay_for_reorg(11, 10, |_| None).unwrap();
7329
7330 let roots: Vec<H256> = (10..=12u8).map(H256::repeat_byte).collect();
7332 {
7333 let mut guard = store.trie_cache.write().unwrap();
7334 let mut updated = (**guard).clone();
7335 let mut parent = pivot_root;
7336 for (i, root) in roots.iter().enumerate() {
7337 let n = 10 + i as u64;
7338 updated.put_batch(
7339 parent,
7340 *root,
7341 n,
7342 H256::repeat_byte(n as u8),
7343 vec![(Nibbles::from_raw(&[0x01, n as u8], false), vec![n as u8])],
7344 );
7345 parent = *root;
7346 }
7347 *guard = Arc::new(updated);
7348 }
7349
7350 let trie = store.trie_cache.read().unwrap().clone();
7353 commit_to_disk(
7354 store.backend.as_ref(),
7355 &store.flatkeyvalue_control_tx,
7356 &store.trie_cache,
7357 &trie,
7358 roots[2],
7359 false,
7360 )
7361 .expect("bottom-layer commit must succeed (no debug_assert panic)");
7362
7363 assert!(
7365 !store.is_state_in_layer_cache(roots[0]).unwrap(),
7366 "bottom layer must be committed and pruned"
7367 );
7368 assert!(
7369 store.is_state_in_layer_cache(roots[1]).unwrap()
7370 && store.is_state_in_layer_cache(roots[2]).unwrap(),
7371 "upper layers must remain resident after the bottom-layer-only commit"
7372 );
7373 assert!(
7375 store.trie_cache.read().unwrap().overlay().is_none(),
7376 "reconciliation must clear the overlay"
7377 );
7378 assert!(
7380 journal_entry_exists(&backend, 10),
7381 "bottom layer T must be journaled"
7382 );
7383 assert!(!journal_entry_exists(&backend, 11));
7384 assert!(!journal_entry_exists(&backend, 12));
7385
7386 let entry_bytes = backend
7391 .begin_read()
7392 .unwrap()
7393 .get(STATE_HISTORY, &10u64.to_be_bytes())
7394 .unwrap()
7395 .expect("journal entry for block 10");
7396 let entry = JournalEntry::decode(&entry_bytes).unwrap();
7397 let diff_keys: Vec<&Vec<u8>> = entry.account_trie_diff.iter().map(|(k, _)| k).collect();
7398 assert!(
7399 diff_keys.contains(&&vec![0x00, 10]) && diff_keys.contains(&&vec![0x00, 11]),
7400 "bridge keys from the overlay must be folded into T's journal entry, got {diff_keys:?}"
7401 );
7402
7403 let trie = store.trie_cache.read().unwrap().clone();
7405 commit_to_disk(
7406 store.backend.as_ref(),
7407 &store.flatkeyvalue_control_tx,
7408 &store.trie_cache,
7409 &trie,
7410 roots[2],
7411 false,
7412 )
7413 .unwrap();
7414 assert!(!store.is_state_in_layer_cache(roots[1]).unwrap());
7415 assert!(!store.is_state_in_layer_cache(roots[2]).unwrap());
7416 assert!(journal_entry_exists(&backend, 11));
7417 assert!(journal_entry_exists(&backend, 12));
7418 }
7419
7420 #[tokio::test]
7425 async fn journal_pruning_pauses_during_reorg_and_catches_up_after() {
7426 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
7427 let dir = tempfile::tempdir().unwrap();
7428 let store = Store::from_backend(
7429 backend.clone(),
7430 dir.path().to_path_buf(),
7431 1,
7432 DEFAULT_PERSIST_CHANNEL_CAPACITY,
7433 )
7434 .unwrap();
7435
7436 seed_journal_entries(&backend, &(1..=5).collect::<Vec<_>>());
7437
7438 store.set_journal_pruning_paused(true);
7440 store
7441 .forkchoice_update_inner(vec![], 100, H256::zero(), None, Some(3))
7442 .await
7443 .unwrap();
7444 for n in 1..=5 {
7445 assert!(
7446 journal_entry_exists(&backend, n),
7447 "entry {n} must survive pruning while the reorg pause is held"
7448 );
7449 }
7450
7451 store.set_journal_pruning_paused(false);
7453 store
7454 .forkchoice_update_inner(vec![], 100, H256::zero(), None, Some(4))
7455 .await
7456 .unwrap();
7457 for n in 1..=4 {
7458 assert!(
7459 !journal_entry_exists(&backend, n),
7460 "entry {n} must be pruned once the pause is released"
7461 );
7462 }
7463 assert!(journal_entry_exists(&backend, 5));
7464 }
7465}
7466
7467#[cfg(test)]
7468mod merge_tests {
7469 use super::*;
7470
7471 fn h256(b: u8) -> H256 {
7472 H256::from_low_u64_be(b as u64)
7473 }
7474
7475 fn op(bn: BlockNumber, bh: H256, idx: Index) -> Vec<u8> {
7476 encode_tx_location_operand(bn, bh, idx)
7477 }
7478
7479 fn decode(v: &[u8]) -> Vec<(BlockNumber, BlockHash, Index)> {
7480 <Vec<(BlockNumber, BlockHash, Index)>>::decode(v).unwrap()
7481 }
7482
7483 #[test]
7484 fn single_operand_on_empty_base() {
7485 let out = tx_locations_merge(None, vec![op(100, h256(0x10), 0)]).unwrap();
7486 assert_eq!(decode(&out), vec![(100, h256(0x10), 0)]);
7487 }
7488
7489 #[test]
7490 fn operand_appended_to_existing_base() {
7491 let base = vec![(100u64, h256(0x10), 0u64)].encode_to_vec();
7492 let out = tx_locations_merge(Some(&base), vec![op(101, h256(0x11), 5)]).unwrap();
7493 let mut got = decode(&out);
7494 got.sort();
7495 let mut want = vec![(100, h256(0x10), 0), (101, h256(0x11), 5)];
7496 want.sort();
7497 assert_eq!(got, want);
7498 }
7499
7500 #[test]
7501 fn multiple_operands_combined() {
7502 let out = tx_locations_merge(
7503 None,
7504 vec![
7505 op(100, h256(0x10), 0),
7506 op(100, h256(0x11), 1),
7507 op(101, h256(0x12), 2),
7508 ],
7509 )
7510 .unwrap();
7511 assert_eq!(decode(&out).len(), 3);
7512 }
7513
7514 #[test]
7515 fn same_block_hash_is_deduped() {
7516 let out =
7518 tx_locations_merge(None, vec![op(100, h256(0x10), 0), op(100, h256(0x10), 7)]).unwrap();
7519 assert_eq!(decode(&out), vec![(100, h256(0x10), 7)]);
7520 }
7521
7522 #[test]
7523 fn malformed_operand_aborts_merge() {
7524 let out = tx_locations_merge(None, vec![vec![0xff, 0xff], op(100, h256(0x10), 0)]);
7527 assert!(out.is_none(), "merge must abort on a malformed operand");
7528 }
7529
7530 #[test]
7531 fn malformed_base_value_aborts_merge() {
7532 let out = tx_locations_merge(Some(&[0xff, 0xff]), vec![op(100, h256(0x10), 0)]);
7533 assert!(out.is_none(), "merge must abort on a corrupt base value");
7534 }
7535
7536 #[test]
7543 fn partial_merge_result_is_a_valid_operand() {
7544 let partial =
7546 tx_locations_merge(None, vec![op(100, h256(0x10), 0), op(101, h256(0x11), 1)]).unwrap();
7547
7548 let base = vec![(99u64, h256(0x09), 9u64)].encode_to_vec();
7552 let out = tx_locations_merge(Some(&base), vec![partial]).unwrap();
7553
7554 let mut got = decode(&out);
7555 got.sort();
7556 let mut want = vec![
7557 (99, h256(0x09), 9),
7558 (100, h256(0x10), 0),
7559 (101, h256(0x11), 1),
7560 ];
7561 want.sort();
7562 assert_eq!(
7563 got, want,
7564 "no entries may be lost when re-merging a partial result"
7565 );
7566 }
7567
7568 #[test]
7571 fn operand_encoding_matches_value_encoding() {
7572 let operand = op(100, h256(0x10), 3);
7573 assert_eq!(decode(&operand), vec![(100, h256(0x10), 3)]);
7575 }
7576
7577 #[test]
7579 fn chained_partial_merges() {
7580 let p1 = tx_locations_merge(None, vec![op(1, h256(0x01), 0)]).unwrap();
7581 let p2 = tx_locations_merge(None, vec![p1, op(2, h256(0x02), 0)]).unwrap();
7582 let p3 = tx_locations_merge(None, vec![p2, op(3, h256(0x03), 0)]).unwrap();
7583 let out = tx_locations_merge(None, vec![p3]).unwrap();
7584 assert_eq!(decode(&out).len(), 3);
7585 }
7586}
7587
7588#[cfg(test)]
7589mod backfill_write_tests {
7590 use super::*;
7591 use ethrex_common::types::{EIP1559Transaction, TxType};
7592
7593 fn test_header(number: BlockNumber) -> BlockHeader {
7594 BlockHeader {
7595 number,
7596 ..Default::default()
7597 }
7598 }
7599
7600 fn test_tx(nonce: u64) -> Transaction {
7601 Transaction::EIP1559Transaction(EIP1559Transaction {
7602 nonce,
7603 ..Default::default()
7604 })
7605 }
7606
7607 fn test_receipt() -> Receipt {
7608 Receipt {
7609 tx_type: TxType::EIP1559,
7610 succeeded: true,
7611 cumulative_gas_used: 21_000,
7612 logs: vec![],
7613 payer: None,
7614 frame_receipts: None,
7615 }
7616 }
7617
7618 #[tokio::test]
7622 async fn writes_data_indexes_txs_and_lowers_frontier() {
7623 let store = Store::new("", EngineType::InMemory).expect("in-memory store");
7624 store.update_earliest_block_number(100).await.unwrap();
7625
7626 let header = test_header(99);
7627 let hash = header.hash();
7628 let tx = test_tx(7);
7629 let body = BlockBody {
7630 transactions: vec![tx.clone()],
7631 ommers: vec![],
7632 withdrawals: Some(vec![]),
7633 };
7634 store.add_block_headers(vec![header.clone()]).await.unwrap();
7638 store
7639 .forkchoice_update(vec![(99, hash)], 99, hash, None, None)
7640 .await
7641 .unwrap();
7642
7643 store
7644 .add_backfilled_blocks(
7645 vec![BackfilledBlock {
7646 header,
7647 body,
7648 receipts: vec![test_receipt()],
7649 index_transactions: true,
7650 }],
7651 99,
7652 )
7653 .await
7654 .unwrap();
7655
7656 assert_eq!(
7657 store.get_earliest_block_number().await.unwrap(),
7658 99,
7659 "frontier must be lowered atomically with the data write"
7660 );
7661 assert!(
7662 store.get_block_body_by_hash(hash).await.unwrap().is_some(),
7663 "body must be stored"
7664 );
7665 assert_eq!(
7666 store.get_receipts_for_block(&hash).await.unwrap().len(),
7667 1,
7668 "receipts must be stored"
7669 );
7670 assert_eq!(
7671 store
7672 .get_transaction_location(tx.hash(&NativeCrypto))
7673 .await
7674 .unwrap(),
7675 Some((99, hash, 0)),
7676 "indexed transaction must be locatable"
7677 );
7678 }
7679
7680 #[tokio::test]
7684 async fn skips_tx_index_when_disabled() {
7685 let store = Store::new("", EngineType::InMemory).expect("in-memory store");
7686 store.update_earliest_block_number(100).await.unwrap();
7687
7688 let header = test_header(99);
7689 let hash = header.hash();
7690 let tx = test_tx(7);
7691 let body = BlockBody {
7692 transactions: vec![tx.clone()],
7693 ommers: vec![],
7694 withdrawals: Some(vec![]),
7695 };
7696 store.add_block_headers(vec![header.clone()]).await.unwrap();
7697 store
7698 .forkchoice_update(vec![(99, hash)], 99, hash, None, None)
7699 .await
7700 .unwrap();
7701
7702 store
7703 .add_backfilled_blocks(
7704 vec![BackfilledBlock {
7705 header,
7706 body,
7707 receipts: vec![test_receipt()],
7708 index_transactions: false,
7709 }],
7710 99,
7711 )
7712 .await
7713 .unwrap();
7714
7715 assert!(
7716 store.get_block_body_by_hash(hash).await.unwrap().is_some(),
7717 "body must still be stored"
7718 );
7719 assert_eq!(
7720 store.get_receipts_for_block(&hash).await.unwrap().len(),
7721 1,
7722 "receipts must still be stored"
7723 );
7724 assert_eq!(
7725 store
7726 .get_transaction_location(tx.hash(&NativeCrypto))
7727 .await
7728 .unwrap(),
7729 None,
7730 "transaction index must be skipped when index_transactions is false"
7731 );
7732 }
7733
7734 #[tokio::test]
7738 async fn historical_getters_transition_from_null_to_present() {
7739 let store = Store::new("", EngineType::InMemory).expect("in-memory store");
7740 store.update_earliest_block_number(100).await.unwrap();
7741
7742 let header = test_header(99);
7743 let hash = header.hash();
7744 let tx = test_tx(7);
7745 let tx_hash = tx.hash(&NativeCrypto);
7746 let body = BlockBody {
7747 transactions: vec![tx],
7748 ommers: vec![],
7749 withdrawals: Some(vec![]),
7750 };
7751 store.add_block_headers(vec![header.clone()]).await.unwrap();
7753 store
7754 .forkchoice_update(vec![(99, hash)], 99, hash, None, None)
7755 .await
7756 .unwrap();
7757
7758 assert!(
7760 store.get_block_body(99).await.unwrap().is_none(),
7761 "body absent before backfill"
7762 );
7763 assert!(
7764 store
7765 .get_transaction_by_hash(tx_hash)
7766 .await
7767 .unwrap()
7768 .is_none(),
7769 "tx unlocatable before backfill"
7770 );
7771 assert!(
7772 store.get_receipt(99, 0).await.unwrap().is_none(),
7773 "receipt absent before backfill"
7774 );
7775
7776 store
7777 .add_backfilled_blocks(
7778 vec![BackfilledBlock {
7779 header,
7780 body,
7781 receipts: vec![test_receipt()],
7782 index_transactions: true,
7783 }],
7784 99,
7785 )
7786 .await
7787 .unwrap();
7788
7789 assert!(
7791 store.get_block_body(99).await.unwrap().is_some(),
7792 "body present after backfill"
7793 );
7794 assert!(
7795 store
7796 .get_transaction_by_hash(tx_hash)
7797 .await
7798 .unwrap()
7799 .is_some(),
7800 "tx locatable after backfill"
7801 );
7802 assert!(
7803 store.get_receipt(99, 0).await.unwrap().is_some(),
7804 "receipt present after backfill"
7805 );
7806 }
7807
7808 #[tokio::test]
7811 async fn resume_fills_contiguously_without_gaps() {
7812 let store = Store::new("", EngineType::InMemory).expect("in-memory store");
7813 store.update_earliest_block_number(100).await.unwrap();
7814
7815 let make_batch = |range: std::ops::Range<u64>| -> Vec<BackfilledBlock> {
7816 range
7817 .rev()
7818 .map(|n| BackfilledBlock {
7819 header: test_header(n),
7820 body: BlockBody {
7821 transactions: vec![],
7822 ommers: vec![],
7823 withdrawals: Some(vec![]),
7824 },
7825 receipts: vec![],
7826 index_transactions: false,
7827 })
7828 .collect()
7829 };
7830
7831 store
7833 .add_backfilled_blocks(make_batch(90..100), 90)
7834 .await
7835 .unwrap();
7836 assert_eq!(store.get_earliest_block_number().await.unwrap(), 90);
7837
7838 let resume_from = store.get_earliest_block_number().await.unwrap();
7840 assert_eq!(resume_from, 90, "resume reads the persisted frontier");
7841
7842 store
7844 .add_backfilled_blocks(make_batch(80..90), 80)
7845 .await
7846 .unwrap();
7847 assert_eq!(store.get_earliest_block_number().await.unwrap(), 80);
7848
7849 for n in 80..100 {
7851 assert!(
7852 store
7853 .get_block_body_by_hash(test_header(n).hash())
7854 .await
7855 .unwrap()
7856 .is_some(),
7857 "block {n} must have a body (no gap left by resume)"
7858 );
7859 }
7860 }
7861}
7862
7863#[cfg(test)]
7866#[cfg(feature = "rocksdb")]
7867mod schema_version_guard_tests {
7868 use super::*;
7869
7870 fn write_marker(dir: &Path, schema_version: u64) {
7871 let metadata = StoreMetadata::new(schema_version);
7872 std::fs::write(
7873 dir.join(STORE_METADATA_FILENAME),
7874 serde_json::to_string_pretty(&metadata).unwrap(),
7875 )
7876 .unwrap();
7877 }
7878
7879 fn read_marker(dir: &Path) -> u64 {
7880 read_store_schema_version(dir).unwrap().unwrap()
7881 }
7882
7883 #[test]
7884 fn a_database_ahead_of_the_binary_is_refused_and_left_untouched() {
7885 let dir = tempfile::tempdir().unwrap();
7891 let newer = STORE_SCHEMA_VERSION + 1;
7892 write_marker(dir.path(), newer);
7893
7894 let result =
7895 Store::new_with_config(dir.path(), EngineType::RocksDB, StoreConfig::default());
7896
7897 assert!(
7898 matches!(
7899 result,
7900 Err(StoreError::IncompatibleDBVersion { found, expected })
7901 if found == newer && expected == STORE_SCHEMA_VERSION
7902 ),
7903 "expected IncompatibleDBVersion, got {:?}",
7904 result.err()
7905 );
7906 assert_eq!(read_marker(dir.path()), newer);
7907 }
7908
7909 #[test]
7910 fn a_zero_schema_version_is_incompatible_not_a_failed_migration() {
7911 let dir = tempfile::tempdir().unwrap();
7914 write_marker(dir.path(), 0);
7915
7916 let result =
7917 Store::new_with_config(dir.path(), EngineType::RocksDB, StoreConfig::default());
7918
7919 assert!(
7920 matches!(
7921 result,
7922 Err(StoreError::IncompatibleDBVersion { found: 0, expected })
7923 if expected == STORE_SCHEMA_VERSION
7924 ),
7925 "expected IncompatibleDBVersion, got {:?}",
7926 result.err()
7927 );
7928 }
7929}
7930
7931#[cfg(test)]
7932mod datadir_tests {
7933 use super::*;
7934 use std::fs;
7935
7936 #[test]
7937 fn empty_dir_has_no_existing_db() {
7938 let dir = tempfile::tempdir().unwrap();
7939 assert!(!dir_contains_legacy_db(dir.path()).unwrap());
7940 }
7941
7942 #[test]
7943 fn dir_with_only_unrelated_files_has_no_existing_db() {
7944 let dir = tempfile::tempdir().unwrap();
7947 fs::write(dir.path().join("jwt.hex"), "0xdeadbeef").unwrap();
7948 fs::write(dir.path().join("LOG"), "noise").unwrap();
7949 assert!(!dir_contains_legacy_db(dir.path()).unwrap());
7950 }
7951
7952 #[test]
7953 fn dir_with_rocksdb_markers_has_existing_db() {
7954 let dir = tempfile::tempdir().unwrap();
7956 fs::write(dir.path().join("CURRENT"), "MANIFEST-000001\n").unwrap();
7957 assert!(dir_contains_legacy_db(dir.path()).unwrap());
7958
7959 let dir2 = tempfile::tempdir().unwrap();
7960 fs::write(dir2.path().join("MANIFEST-000007"), "x").unwrap();
7961 assert!(dir_contains_legacy_db(dir2.path()).unwrap());
7962 }
7963
7964 #[test]
7965 fn dir_with_marker_named_subdirectories_has_no_existing_db() {
7966 let dir = tempfile::tempdir().unwrap();
7969 fs::create_dir(dir.path().join("CURRENT")).unwrap();
7970 fs::create_dir(dir.path().join("MANIFEST-000001")).unwrap();
7971 assert!(!dir_contains_legacy_db(dir.path()).unwrap());
7972 }
7973}
7974
7975#[cfg(test)]
7976mod flatkeyvalue_completeness_tests {
7977 use super::*;
7978
7979 #[test]
7984 fn only_the_one_byte_ff_sentinel_counts_as_complete() {
7985 assert!(Store::flatkeyvalue_generation_complete(Some(&[0xff])));
7987
7988 assert!(!Store::flatkeyvalue_generation_complete(None)); assert!(!Store::flatkeyvalue_generation_complete(Some(&[]))); assert!(!Store::flatkeyvalue_generation_complete(Some(&[0u8; 64]))); assert!(!Store::flatkeyvalue_generation_complete(Some(&[0x0a; 64]))); assert!(!Store::flatkeyvalue_generation_complete(Some(&[0xff; 64])));
7996 assert!(!Store::flatkeyvalue_generation_complete(Some(&[
7997 0xff, 0xff
7998 ])));
7999 }
8000}
8001
8002#[cfg(test)]
8003mod cgroup_memory_limit_tests {
8004 use super::*;
8005 use std::fs;
8006
8007 fn cgroup_tree(root: &Path, relative: &str, file: &str, limits: &[(&str, &str)]) {
8010 fs::create_dir_all(root.join(relative)).expect("create cgroup tree");
8011 for (dir, contents) in limits {
8012 fs::write(root.join(dir).join(file), contents).expect("write limit");
8013 }
8014 }
8015
8016 #[test]
8020 fn ancestor_limit_applies() {
8021 let tmp = tempfile::tempdir().expect("tempdir");
8022 let root = tmp.path();
8023 cgroup_tree(
8024 root,
8025 "system.slice/ethrex.service",
8026 "memory.max",
8027 &[("", "max"), ("system.slice", "4294967296")],
8028 );
8029
8030 assert_eq!(
8031 cgroup_limit_up_to_root(root, "/system.slice/ethrex.service", "memory.max"),
8032 Some(4 * 1024 * 1024 * 1024)
8033 );
8034 }
8035
8036 #[test]
8038 fn smallest_limit_in_the_chain_wins() {
8039 let tmp = tempfile::tempdir().expect("tempdir");
8040 let root = tmp.path();
8041 cgroup_tree(
8042 root,
8043 "kubepods/pod123/container",
8044 "memory.max",
8045 &[
8046 ("kubepods", "8589934592"),
8047 ("kubepods/pod123", "2147483648"),
8048 ("kubepods/pod123/container", "4294967296"),
8049 ],
8050 );
8051
8052 assert_eq!(
8053 cgroup_limit_up_to_root(root, "kubepods/pod123/container", "memory.max"),
8054 Some(2 * 1024 * 1024 * 1024)
8055 );
8056 }
8057
8058 #[test]
8061 fn unlimited_chain_yields_no_limit() {
8062 let tmp = tempfile::tempdir().expect("tempdir");
8063 let root = tmp.path();
8064 cgroup_tree(root, "docker/abc", "memory.max", &[("", "max")]);
8065
8066 assert_eq!(
8067 cgroup_limit_up_to_root(root, "docker/abc", "memory.max"),
8068 None
8069 );
8070 assert_eq!(
8073 cgroup_limit_up_to_root(root, "not/here", "memory.max"),
8074 None
8075 );
8076 }
8077
8078 #[test]
8080 fn root_limit_applies_to_the_root_cgroup() {
8081 let tmp = tempfile::tempdir().expect("tempdir");
8082 let root = tmp.path();
8083 cgroup_tree(root, "", "memory.limit_in_bytes", &[("", "536870912")]);
8084
8085 assert_eq!(
8086 cgroup_limit_up_to_root(root, "/", "memory.limit_in_bytes"),
8087 Some(512 * 1024 * 1024)
8088 );
8089 }
8090}