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 lru::LruCache;
45use rayon::prelude::*;
46use rustc_hash::FxBuildHasher;
47use serde::{Deserialize, Serialize};
48use std::{
49 collections::{BTreeMap, HashMap, HashSet, hash_map::Entry},
50 fmt::Debug,
51 io::Write,
52 num::NonZeroUsize,
53 path::{Path, PathBuf},
54 sync::{
55 Arc, Condvar, Mutex, RwLock,
56 atomic::{AtomicUsize, Ordering},
57 mpsc::{SyncSender, TryRecvError, sync_channel},
58 },
59 thread::JoinHandle,
60};
61use tracing::{debug, error, info, warn};
62
63pub const MAX_WITNESSES: u64 = 128;
65
66pub const DB_COMMIT_THRESHOLD: usize = 128;
70const IN_MEMORY_COMMIT_THRESHOLD: usize = 10000;
71
72pub const BATCH_COMMIT_THRESHOLD: usize = 4;
78
79const KEYS_PER_SHARD: usize = 256;
86const MAX_SHARDS: usize = 64;
87
88fn shard_count(n: usize) -> usize {
90 n.div_ceil(KEYS_PER_SHARD).clamp(1, MAX_SHARDS)
91}
92
93pub const MAX_ROCKSDB_BLOCK_CACHE_SIZE_BYTES: usize = 12 * 1024 * 1024 * 1024;
103
104pub const MIN_ROCKSDB_BLOCK_CACHE_SIZE_BYTES: usize = 512 * 1024 * 1024;
112
113pub const ROCKSDB_BLOCK_CACHE_MEMORY_PERCENT: usize = 40;
122
123pub fn default_rocksdb_block_cache_size() -> usize {
135 let physical = physical_memory_bytes();
136 let cgroup = cgroup_memory_limit_bytes();
137 let limit = [physical, cgroup].into_iter().flatten().min();
138 let size = rocksdb_block_cache_size_for(limit);
139 let source = match (physical, cgroup) {
144 (None, None) => "undetected, falling back to the ceiling",
145 (Some(_), None) => "physical memory",
146 (None, Some(_)) => "cgroup limit",
147 (Some(p), Some(c)) => {
148 if c <= p {
149 "cgroup limit"
150 } else {
151 "physical memory"
152 }
153 }
154 };
155 info!(
156 detected_limit_bytes = ?limit,
157 source,
158 cache_bytes = size,
159 "sized RocksDB block cache"
160 );
161 size
162}
163
164pub fn rocksdb_block_cache_size_for(memory_limit: Option<usize>) -> usize {
167 let Some(limit) = memory_limit else {
168 return MAX_ROCKSDB_BLOCK_CACHE_SIZE_BYTES;
169 };
170 (limit.saturating_mul(ROCKSDB_BLOCK_CACHE_MEMORY_PERCENT) / 100).clamp(
171 MIN_ROCKSDB_BLOCK_CACHE_SIZE_BYTES,
172 MAX_ROCKSDB_BLOCK_CACHE_SIZE_BYTES,
173 )
174}
175
176fn physical_memory_bytes() -> Option<usize> {
178 let meminfo = std::fs::read_to_string("/proc/meminfo").ok()?;
179 let kib: usize = meminfo
180 .lines()
181 .find_map(|line| line.strip_prefix("MemTotal:"))?
182 .split_whitespace()
183 .next()?
184 .parse()
185 .ok()?;
186 kib.checked_mul(1024)
187}
188
189fn cgroup_memory_limit_bytes() -> Option<usize> {
201 let cgroups = std::fs::read_to_string("/proc/self/cgroup").ok()?;
202 let v2 = cgroups
203 .lines()
204 .find_map(|line| line.strip_prefix("0::"))
205 .and_then(|relative| {
206 cgroup_limit_up_to_root(Path::new("/sys/fs/cgroup"), relative, "memory.max")
207 });
208 v2.or_else(|| {
209 let relative = cgroups.lines().find_map(|line| {
210 let mut fields = line.splitn(3, ':');
212 let controllers = fields.nth(1)?;
213 let path = fields.next()?;
214 controllers
215 .split(',')
216 .any(|controller| controller == "memory")
217 .then_some(path)
218 })?;
219 cgroup_limit_up_to_root(
220 Path::new("/sys/fs/cgroup/memory"),
221 relative,
222 "memory.limit_in_bytes",
223 )
224 })
225}
226
227fn cgroup_limit_up_to_root(mount: &Path, relative: &str, file: &str) -> Option<usize> {
230 let mut dir = mount.join(relative.trim_start_matches('/'));
231 let mut limit: Option<usize> = None;
232 loop {
233 if let Some(value) = std::fs::read_to_string(dir.join(file))
234 .ok()
235 .and_then(|contents| contents.trim().parse::<usize>().ok())
236 {
237 limit = Some(limit.map_or(value, |current| current.min(value)));
238 }
239 if dir == mount || !dir.pop() {
240 return limit;
241 }
242 }
243}
244
245#[derive(Debug, Clone, Copy)]
250pub struct StoreConfig {
251 pub rocksdb_block_cache_size: usize,
256 pub persist_channel_capacity: usize,
261}
262
263impl StoreConfig {
264 pub fn with_rocksdb_block_cache_size(rocksdb_block_cache_size: usize) -> Self {
269 Self {
270 rocksdb_block_cache_size,
271 persist_channel_capacity: DEFAULT_PERSIST_CHANNEL_CAPACITY,
272 }
273 }
274}
275
276impl Default for StoreConfig {
277 fn default() -> Self {
278 Self::with_rocksdb_block_cache_size(default_rocksdb_block_cache_size())
279 }
280}
281
282#[derive(Debug, PartialEq)]
284enum FKVGeneratorControlMessage {
285 Stop,
286 Continue,
287}
288
289const CODE_CACHE_MAX_SIZE: u64 = 256 * 1024 * 1024;
296
297const CODE_METADATA_CACHE_MAX_ENTRIES: usize = (16 * 1024 * 1024) / 64;
303
304const FLUSHED_UPTO_KEY: &[u8] = b"bodies_flushed_upto";
306
307const BAD_BLOCKS_KEY: &[u8] = b"bad_blocks";
309
310const MAX_BAD_BLOCKS: usize = 16;
312
313#[derive(Debug)]
314struct CodeCache {
315 inner_cache: LruCache<H256, Code, FxBuildHasher>,
316 cache_size: u64,
317 max_size: u64,
318}
319
320impl Default for CodeCache {
321 fn default() -> Self {
322 Self {
323 inner_cache: LruCache::unbounded_with_hasher(FxBuildHasher),
324 cache_size: 0,
325 max_size: CODE_CACHE_MAX_SIZE,
326 }
327 }
328}
329
330impl CodeCache {
331 fn get(&mut self, code_hash: &H256) -> Result<Option<Code>, StoreError> {
332 Ok(self.inner_cache.get(code_hash).cloned())
333 }
334
335 fn insert(&mut self, code: &Code) -> Result<(), StoreError> {
336 if self.inner_cache.get(&code.hash).is_some() {
340 return Ok(());
341 }
342
343 self.cache_size += code.size() as u64;
344 self.inner_cache.put(code.hash, code.clone());
345
346 while self.cache_size > self.max_size {
347 if let Some((_, code)) = self.inner_cache.pop_lru() {
348 self.cache_size -= code.size() as u64;
349 } else {
350 break;
351 }
352 }
353
354 let cache_len = self.inner_cache.len();
355 let current_size = self.cache_size;
356 debug!(
357 "[ACCOUNT CODE CACHE] cache elements (): {cache_len}, total size: {current_size} bytes"
358 );
359 Ok(())
360 }
361}
362
363#[derive(Debug, Clone)]
369pub struct Store {
370 db_path: PathBuf,
372 backend: Arc<dyn StorageBackend>,
374 chain_config: ChainConfig,
376 trie_cache: Arc<RwLock<Arc<TrieLayerCache>>>,
378 flatkeyvalue_control_tx: std::sync::mpsc::SyncSender<FKVGeneratorControlMessage>,
380 block_data_buffer: Arc<RwLock<Arc<BlockDataBuffer>>>,
382 persist_tx: std::sync::mpsc::SyncSender<PersistMessage>,
386 pending_trie_roots: Arc<PendingTrieRoots>,
390 latest_block_header: LatestBlockHeaderCache,
393 last_computed_flatkeyvalue: Arc<RwLock<Vec<u8>>>,
395
396 account_code_cache: Arc<Mutex<CodeCache>>,
401
402 code_metadata_cache: Arc<Mutex<LruCache<H256, CodeMetadata, FxBuildHasher>>>,
408
409 fcu_lock: Arc<tokio::sync::Mutex<()>>,
412
413 safe_commit_root: Arc<RwLock<H256>>,
419
420 journal_pruning_paused: Arc<std::sync::atomic::AtomicBool>,
430
431 background_threads: Arc<ThreadList>,
432}
433
434#[derive(Debug, Default)]
435struct ThreadList {
436 list: Vec<JoinHandle<()>>,
437}
438
439impl Drop for ThreadList {
440 fn drop(&mut self) {
441 for handle in self.list.drain(..) {
442 let _ = handle.join();
443 }
444 }
445}
446
447pub type StorageTrieNodes = Vec<(H256, Vec<(Nibbles, Vec<u8>)>)>;
452type StorageTries = HashMap<Address, (TrieWitness, Trie)>;
453
454#[derive(Debug, Clone, Copy, PartialEq, Eq)]
458pub enum EngineType {
459 InMemory,
461 #[cfg(feature = "rocksdb")]
463 RocksDB,
464}
465
466pub struct UpdateBatch {
471 pub account_updates: Vec<TrieNode>,
473 pub storage_updates: Vec<(H256, Vec<TrieNode>)>,
475 pub blocks: Vec<Block>,
477 pub receipts: Vec<(H256, Vec<Receipt>)>,
479 pub code_updates: Vec<(H256, Code)>,
481 pub commit_depth: Option<usize>,
489 pub wait_for_flush: bool,
500}
501
502pub struct BackfilledBlock {
510 pub header: BlockHeader,
511 pub body: BlockBody,
512 pub receipts: Vec<Receipt>,
513 pub index_transactions: bool,
514}
515
516pub type StorageUpdates = Vec<(H256, Vec<(Nibbles, Vec<u8>)>)>;
518
519pub struct AccountUpdatesList {
524 pub state_trie_hash: H256,
526 pub state_updates: Vec<(Nibbles, Vec<u8>)>,
528 pub storage_updates: StorageUpdates,
530 pub code_updates: Vec<(H256, Code)>,
532}
533
534pub(crate) fn encode_tx_location_operand(
545 block_number: BlockNumber,
546 block_hash: BlockHash,
547 index: Index,
548) -> Vec<u8> {
549 vec![(block_number, block_hash, index)].encode_to_vec()
550}
551
552pub fn tx_locations_merge(
570 existing: Option<&[u8]>,
571 operands: impl IntoIterator<Item = impl AsRef<[u8]>>,
572) -> Option<Vec<u8>> {
573 fn fold_chunk(
577 list: &mut Vec<(BlockNumber, BlockHash, Index)>,
578 bytes: &[u8],
579 what: &str,
580 ) -> bool {
581 match <Vec<(BlockNumber, BlockHash, Index)>>::decode(bytes) {
582 Ok(entries) => {
583 for (bn, bh, idx) in entries {
584 list.retain(|(_, existing_bh, _)| *existing_bh != bh);
585 list.push((bn, bh, idx));
586 }
587 true
588 }
589 Err(e) => {
590 error!(
591 "tx_locations_merge: failed to decode {what} ({} bytes): {e}; \
592 aborting merge to avoid silent data loss",
593 bytes.len()
594 );
595 false
596 }
597 }
598 }
599
600 let mut list: Vec<(BlockNumber, BlockHash, Index)> = Vec::new();
601
602 if let Some(bytes) = existing
604 && !fold_chunk(&mut list, bytes, "existing value")
605 {
606 return None;
607 }
608 for op in operands {
609 if !fold_chunk(&mut list, op.as_ref(), "operand") {
610 return None;
611 }
612 }
613 Some(list.encode_to_vec())
614}
615
616impl Store {
617 pub async fn wait_for_persistence_idle(&self) -> Result<(), StoreError> {
628 let tx = self.persist_tx.clone();
629 tokio::task::spawn_blocking(move || {
630 let (ack_tx, ack_rx) = sync_channel::<Result<(), StoreError>>(1);
631 tx.send(PersistMessage::Ping(ack_tx))
632 .map_err(|e| StoreError::Custom(format!("wait_for_persistence_idle send: {e}")))?;
633 ack_rx
634 .recv()
635 .map_err(|e| StoreError::Custom(format!("wait_for_persistence_idle ack: {e}")))?
636 })
637 .await
638 .map_err(|e| StoreError::Custom(format!("wait_for_persistence_idle join: {e}")))?
639 }
640
641 pub async fn shutdown(&self) -> Result<(), StoreError> {
660 let tx = self.persist_tx.clone();
661 let backend = self.backend.clone();
662 tokio::task::spawn_blocking(move || {
663 let (ack_tx, ack_rx) = sync_channel::<Result<(), StoreError>>(1);
664 tx.send(PersistMessage::Shutdown { ack: ack_tx })
665 .map_err(|e| StoreError::Custom(format!("shutdown send: {e}")))?;
666 ack_rx
667 .recv()
668 .map_err(|e| StoreError::Custom(format!("shutdown ack: {e}")))??;
669 backend.flush()
672 })
673 .await
674 .map_err(|e| StoreError::Custom(format!("shutdown join: {e}")))?
675 }
676
677 pub async fn add_block(&self, block: Block) -> Result<(), StoreError> {
680 self.add_blocks(vec![block]).await
681 }
682
683 pub async fn add_blocks(&self, blocks: Vec<Block>) -> Result<(), StoreError> {
686 let db = self.backend.clone();
687 tokio::task::spawn_blocking(move || {
688 let mut tx = db.begin_write()?;
689
690 for block in blocks {
691 write_block_data(
692 tx.as_mut(),
693 block.header.number,
694 block.hash(),
695 &block.header,
696 &block.body,
697 )?;
698 }
699
700 tx.commit()
701 })
702 .await
703 .map_err(|e| StoreError::Custom(format!("Task panicked: {}", e)))?
704 }
705
706 pub async fn add_block_header(
708 &self,
709 block_hash: BlockHash,
710 block_header: BlockHeader,
711 ) -> Result<(), StoreError> {
712 let hash_key = block_hash.encode_to_vec();
713 let header_value = BlockHeaderRLP::from(block_header).into_vec();
714 self.write_async(HEADERS, hash_key, header_value).await
715 }
716
717 pub async fn add_block_headers(
719 &self,
720 block_headers: Vec<BlockHeader>,
721 ) -> Result<(), StoreError> {
722 let mut txn = self.backend.begin_write()?;
723
724 for header in block_headers {
725 let block_hash = header.hash();
726 let block_number = header.number;
727 let hash_key = block_hash.encode_to_vec();
728 let header_value = BlockHeaderRLP::from(header).into_vec();
729
730 txn.put(HEADERS, &hash_key, &header_value)?;
731
732 let number_key = block_number.to_le_bytes().to_vec();
733 txn.put(BLOCK_NUMBERS, &hash_key, &number_key)?;
734 }
735 txn.commit()?;
736 Ok(())
737 }
738
739 pub fn get_block_header(
741 &self,
742 block_number: BlockNumber,
743 ) -> Result<Option<BlockHeader>, StoreError> {
744 let latest = self.latest_block_header.get();
745 if block_number == latest.number {
746 return Ok(Some((*latest).clone()));
747 }
748 let Some(block_hash) = self.get_canonical_block_hash_sync(block_number)? else {
753 return Ok(None);
754 };
755 self.get_block_header_by_hash(block_hash)
756 }
757
758 pub async fn add_block_body(
760 &self,
761 block_hash: BlockHash,
762 block_body: BlockBody,
763 ) -> Result<(), StoreError> {
764 let hash_key = block_hash.encode_to_vec();
765 let body_value = BlockBodyRLP::from(block_body).into_vec();
766 self.write_async(BODIES, hash_key, body_value).await
767 }
768
769 pub async fn add_backfilled_blocks(
787 &self,
788 blocks: Vec<BackfilledBlock>,
789 new_earliest: BlockNumber,
790 ) -> Result<(), StoreError> {
791 let backend = self.backend.clone();
792 tokio::task::spawn_blocking(move || {
793 let mut txn = backend.begin_write()?;
794 for block in &blocks {
795 let hash = block.header.hash();
796 let hash_key = hash.encode_to_vec();
797 txn.put(
798 BODIES,
799 &hash_key,
800 &BlockBodyRLP::from(block.body.clone()).into_vec(),
801 )?;
802 for (index, receipt) in block.receipts.iter().enumerate() {
803 txn.put(
804 RECEIPTS_V2,
805 &receipt_key(&hash, index as u64),
806 &receipt.encode_storage(),
807 )?;
808 }
809 if block.index_transactions {
810 for (index, transaction) in block.body.transactions.iter().enumerate() {
811 txn.merge(
812 TRANSACTION_LOCATIONS,
813 transaction.hash(&NativeCrypto).as_bytes(),
814 &encode_tx_location_operand(block.header.number, hash, index as u64),
815 )?;
816 }
817 }
818 }
819 txn.put(
820 CHAIN_DATA,
821 &chain_data_key(ChainDataIndex::EarliestBlockNumber),
822 &new_earliest.to_le_bytes(),
823 )?;
824 txn.commit()
825 })
826 .await
827 .map_err(|e| StoreError::Custom(format!("Backfill write task panicked: {e}")))?
828 }
829
830 pub fn rocksdb_stats(&self) -> Option<crate::api::RocksDbStats> {
834 self.backend.db_stats()
835 }
836
837 pub async fn get_block_body(
839 &self,
840 block_number: BlockNumber,
841 ) -> Result<Option<BlockBody>, StoreError> {
842 let Some(block_hash) = self.get_canonical_block_hash_sync(block_number)? else {
843 return Ok(None);
844 };
845
846 self.get_block_body_by_hash(block_hash).await
847 }
848
849 pub async fn remove_block(&self, block_number: BlockNumber) -> Result<(), StoreError> {
851 let Some(hash) = self.get_canonical_block_hash_sync(block_number)? else {
852 return Ok(());
853 };
854
855 let backend = self.backend.clone();
856 tokio::task::spawn_blocking(move || {
857 let hash_key = hash.encode_to_vec();
858
859 let mut txn = backend.begin_write()?;
860 txn.delete(
861 CANONICAL_BLOCK_HASHES,
862 block_number.to_le_bytes().as_slice(),
863 )?;
864 txn.delete(BODIES, &hash_key)?;
865 txn.delete(HEADERS, &hash_key)?;
866 txn.delete(BLOCK_NUMBERS, &hash_key)?;
867 txn.commit()
868 })
869 .await
870 .map_err(|e| StoreError::Custom(format!("Task panicked: {}", e)))?
871 }
872
873 pub async fn get_block_bodies(
875 &self,
876 from: BlockNumber,
877 to: BlockNumber,
878 ) -> Result<Vec<Option<BlockBody>>, StoreError> {
879 let buffer = self.buffer()?;
881 let backend = self.backend.clone();
882 tokio::task::spawn_blocking(move || {
883 let numbers: Vec<BlockNumber> = (from..=to).collect();
884 let mut block_bodies = Vec::new();
885
886 let txn = backend.begin_read()?;
887 for number in numbers {
888 let Some(hash) = txn
889 .get(CANONICAL_BLOCK_HASHES, number.to_le_bytes().as_slice())?
890 .map(|bytes| H256::decode(bytes.as_slice()))
891 .transpose()?
892 else {
893 block_bodies.push(None);
894 continue;
895 };
896 if let Some(body) = buffer.get_body(&hash) {
899 block_bodies.push(Some(body));
900 continue;
901 }
902 let hash_key = hash.encode_to_vec();
903 let block_body_opt = txn
904 .get(BODIES, &hash_key)?
905 .map(|bytes| BlockBodyRLP::from_bytes(bytes).to())
906 .transpose()
907 .map_err(StoreError::from)?;
908
909 block_bodies.push(block_body_opt);
910 }
911
912 Ok(block_bodies)
913 })
914 .await
915 .map_err(|e| StoreError::Custom(format!("Task panicked: {}", e)))?
916 }
917
918 pub async fn get_block_bodies_by_hash(
920 &self,
921 hashes: Vec<BlockHash>,
922 ) -> Result<Vec<BlockBody>, StoreError> {
923 let buffer = self.buffer()?;
924 let backend = self.backend.clone();
925 tokio::task::spawn_blocking(move || {
927 let txn = backend.begin_read()?;
928 let mut block_bodies = Vec::new();
929 for hash in hashes {
930 if let Some(body) = buffer.get_body(&hash) {
933 block_bodies.push(body);
934 continue;
935 }
936 let hash_key = hash.encode_to_vec();
937
938 let Some(block_body) = txn
939 .get(BODIES, &hash_key)?
940 .map(|bytes| BlockBodyRLP::from_bytes(bytes).to())
941 .transpose()
942 .map_err(StoreError::from)?
943 else {
944 return Err(StoreError::Custom(format!(
945 "Block body not found for hash: {hash}"
946 )));
947 };
948 block_bodies.push(block_body);
949 }
950 Ok(block_bodies)
951 })
952 .await
953 .map_err(|e| StoreError::Custom(format!("Task panicked: {}", e)))?
954 }
955
956 pub async fn get_block_body_by_hash(
958 &self,
959 block_hash: BlockHash,
960 ) -> Result<Option<BlockBody>, StoreError> {
961 if let Some(b) = self.buffer()?.get_body(&block_hash) {
962 return Ok(Some(b));
963 }
964 self.read_async(BODIES, block_hash.encode_to_vec())
965 .await?
966 .map(|bytes| BlockBodyRLP::from_bytes(bytes).to())
967 .transpose()
968 .map_err(StoreError::from)
969 }
970
971 pub fn get_block_header_by_hash(
972 &self,
973 block_hash: BlockHash,
974 ) -> Result<Option<BlockHeader>, StoreError> {
975 let latest = self.latest_block_header.get();
976 if block_hash == latest.hash() {
977 return Ok(Some((*latest).clone()));
978 }
979 if let Some(h) = self.buffer()?.get_header(&block_hash) {
980 return Ok(Some(h));
981 }
982 self.load_block_header_by_hash(block_hash)
983 }
984
985 pub fn add_pending_block(&self, block: Block) -> Result<(), StoreError> {
986 let block_hash = block.hash();
987 let block_value = BlockRLP::from(block).into_vec();
988 self.write(PENDING_BLOCKS, block_hash.as_bytes().to_vec(), block_value)
989 }
990
991 pub async fn get_pending_block(
992 &self,
993 block_hash: BlockHash,
994 ) -> Result<Option<Block>, StoreError> {
995 self.read_async(PENDING_BLOCKS, block_hash.as_bytes().to_vec())
996 .await?
997 .map(|bytes| BlockRLP::from_bytes(bytes).to())
998 .transpose()
999 .map_err(StoreError::from)
1000 }
1001
1002 pub async fn add_block_number(
1004 &self,
1005 block_hash: BlockHash,
1006 block_number: BlockNumber,
1007 ) -> Result<(), StoreError> {
1008 let number_value = block_number.to_le_bytes().to_vec();
1009 self.write_async(BLOCK_NUMBERS, block_hash.encode_to_vec(), number_value)
1010 .await
1011 }
1012
1013 pub async fn get_block_number(
1015 &self,
1016 block_hash: BlockHash,
1017 ) -> Result<Option<BlockNumber>, StoreError> {
1018 if let Some(n) = self.buffer()?.get_number(&block_hash) {
1019 return Ok(Some(n));
1020 }
1021 self.read_async(BLOCK_NUMBERS, block_hash.encode_to_vec())
1022 .await?
1023 .map(decode_block_number)
1024 .transpose()
1025 }
1026
1027 pub async fn add_transaction_location(
1029 &self,
1030 transaction_hash: H256,
1031 block_number: BlockNumber,
1032 block_hash: BlockHash,
1033 index: Index,
1034 ) -> Result<(), StoreError> {
1035 self.add_transaction_locations(vec![(transaction_hash, block_number, block_hash, index)])
1036 .await
1037 }
1038
1039 pub async fn add_transaction_locations(
1041 &self,
1042 locations: Vec<(H256, BlockNumber, BlockHash, Index)>,
1043 ) -> Result<(), StoreError> {
1044 let db = self.backend.clone();
1045 tokio::task::spawn_blocking(move || {
1046 let mut tx = db.begin_write()?;
1047 for (tx_hash, block_number, block_hash, index) in locations {
1048 tx.merge(
1049 TRANSACTION_LOCATIONS,
1050 tx_hash.as_bytes(),
1051 &encode_tx_location_operand(block_number, block_hash, index),
1052 )?;
1053 }
1054 tx.commit()
1055 })
1056 .await
1057 .map_err(|e| StoreError::Custom(format!("Task panicked: {}", e)))?
1058 }
1059
1060 pub async fn get_transaction_location(
1062 &self,
1063 transaction_hash: H256,
1064 ) -> Result<Option<(BlockNumber, BlockHash, Index)>, StoreError> {
1065 let buffered = self.buffer()?.get_tx_locations(&transaction_hash);
1066 let db = self.backend.clone();
1067 tokio::task::spawn_blocking(move || {
1068 let tx = db.begin_read()?;
1069 let mut locations = buffered;
1070 if let Some(bytes) = tx.get(TRANSACTION_LOCATIONS, transaction_hash.as_bytes())? {
1071 locations.extend(<Vec<(BlockNumber, BlockHash, Index)>>::decode(&bytes)?);
1072 }
1073 for (block_number, block_hash, index) in locations {
1074 let canonical_hash = tx
1075 .get(
1076 CANONICAL_BLOCK_HASHES,
1077 block_number.to_le_bytes().as_slice(),
1078 )?
1079 .map(|bytes| H256::decode(bytes.as_slice()))
1080 .transpose()?;
1081 if canonical_hash == Some(block_hash) {
1082 return Ok(Some((block_number, block_hash, index)));
1083 }
1084 }
1085 Ok(None)
1086 })
1087 .await
1088 .map_err(|e| StoreError::Custom(format!("Task panicked: {}", e)))?
1089 }
1090
1091 pub async fn add_receipt(
1093 &self,
1094 block_hash: BlockHash,
1095 index: Index,
1096 receipt: Receipt,
1097 ) -> Result<(), StoreError> {
1098 let key = receipt_key(&block_hash, index);
1099 let value = receipt.encode_storage();
1103 self.write_async(RECEIPTS_V2, key, value).await
1104 }
1105
1106 pub async fn add_receipts(
1108 &self,
1109 block_hash: BlockHash,
1110 receipts: Vec<Receipt>,
1111 ) -> Result<(), StoreError> {
1112 let batch_items: Vec<_> = receipts
1113 .into_iter()
1114 .enumerate()
1115 .map(|(index, receipt)| {
1116 let key = receipt_key(&block_hash, index as u64);
1117 let value = receipt.encode_storage();
1118 (key, value)
1119 })
1120 .collect();
1121 self.write_batch_async(RECEIPTS_V2, batch_items).await
1122 }
1123
1124 pub async fn get_receipt(
1126 &self,
1127 block_number: BlockNumber,
1128 index: Index,
1129 ) -> Result<Option<Receipt>, StoreError> {
1130 let Some(block_hash) = self.get_canonical_block_hash(block_number).await? else {
1132 return Ok(None);
1133 };
1134 self.get_receipt_by_block_hash(block_hash, index).await
1135 }
1136
1137 async fn get_receipt_by_block_hash(
1139 &self,
1140 block_hash: BlockHash,
1141 index: Index,
1142 ) -> Result<Option<Receipt>, StoreError> {
1143 if let Some(r) = self.buffer()?.get_receipt(&block_hash, index) {
1144 return Ok(Some(r));
1145 }
1146 let key = receipt_key(&block_hash, index);
1147 self.read_async(RECEIPTS_V2, key)
1148 .await?
1149 .map(|bytes| Receipt::decode_storage(bytes.as_slice()))
1150 .transpose()
1151 .map_err(StoreError::from)
1152 }
1153
1154 pub fn code_cache_budget_bytes(&self) -> u64 {
1161 self.account_code_cache
1162 .lock()
1163 .map(|cache| cache.max_size)
1164 .unwrap_or(0)
1165 }
1166
1167 pub fn get_account_code(&self, code_hash: H256) -> Result<Option<Code>, StoreError> {
1174 if let Some(code) = self.buffer()?.get_code(&code_hash) {
1175 return Ok(Some(code));
1176 }
1177 if let Some(code) = self
1179 .account_code_cache
1180 .lock()
1181 .map_err(|_| StoreError::LockError)?
1182 .get(&code_hash)?
1183 {
1184 return Ok(Some(code));
1185 }
1186
1187 let Some(bytes) = self
1188 .backend
1189 .begin_read()?
1190 .get(ACCOUNT_CODES, code_hash.as_bytes())?
1191 else {
1192 return Ok(None);
1193 };
1194 let (bytecode_slice, jumpdests) = decode_bytes(&bytes)?;
1195 let (jumpdests, was_legacy) = decode_jumpdests(bytecode_slice, jumpdests)?;
1196 let code = Code::from_parts_unchecked(code_hash, bytecode_slice, jumpdests);
1197
1198 if was_legacy && let Ok(handle) = tokio::runtime::Handle::try_current() {
1205 let buf = encode_code(&code);
1206 let hash_key = code_hash.0.to_vec();
1207 let backend = self.backend.clone();
1208 handle.spawn(async move {
1209 if let Err(e) = async {
1210 let mut tx = backend.begin_write()?;
1211 tx.put(ACCOUNT_CODES, &hash_key, &buf)?;
1212 tx.commit()
1213 }
1214 .await
1215 {
1216 tracing::warn!("Failed to rewrite legacy jumpdests during backfill: {e}");
1217 }
1218 });
1219 }
1220
1221 self.account_code_cache
1223 .lock()
1224 .map_err(|_| StoreError::LockError)?
1225 .insert(&code)?;
1226
1227 Ok(Some(code))
1228 }
1229
1230 pub fn get_account_codes_batch(
1241 &self,
1242 code_hashes: &[H256],
1243 ) -> Result<Vec<Option<Code>>, StoreError> {
1244 let mut out: Vec<Option<Code>> = vec![None; code_hashes.len()];
1245 let mut pending: HashMap<H256, Vec<usize>> = HashMap::new();
1247
1248 {
1249 let buffer = self.buffer()?;
1250 let mut cache = self
1251 .account_code_cache
1252 .lock()
1253 .map_err(|_| StoreError::LockError)?;
1254 for (i, hash) in code_hashes.iter().enumerate() {
1255 if let Some(code) = buffer.get_code(hash) {
1256 out[i] = Some(code);
1257 } else if let Some(code) = cache.get(hash)? {
1258 out[i] = Some(code);
1259 } else {
1260 pending.entry(*hash).or_default().push(i);
1261 }
1262 }
1263 }
1264
1265 if pending.is_empty() {
1266 return Ok(out);
1267 }
1268
1269 let mut missing: Vec<H256> = pending.keys().copied().collect();
1270 missing.sort_unstable();
1271
1272 const KEYS_PER_SHARD: usize = 256;
1294 const MIN_SHARD_CAP: usize = 64;
1295 let parallelism = std::thread::available_parallelism().map_or(8, |p| p.get());
1296 let max_shards = parallelism.saturating_mul(2).max(MIN_SHARD_CAP);
1297 let shards = missing.len().div_ceil(KEYS_PER_SHARD).min(max_shards);
1298 let read_view = self.backend.begin_read()?;
1299 let decode = |hash: &H256, value: Option<Vec<u8>>| -> Result<Option<Code>, StoreError> {
1302 let Some(bytes) = value else { return Ok(None) };
1303 let (bytecode_slice, jumpdests) = decode_bytes(&bytes)?;
1304 let (jumpdests, _was_legacy) = decode_jumpdests(bytecode_slice, jumpdests)?;
1307 Ok(Some(Code::from_parts_unchecked(
1308 *hash,
1309 bytecode_slice,
1310 jumpdests,
1311 )))
1312 };
1313 let decoded: Vec<Result<Option<Code>, StoreError>> = if shards > parallelism {
1314 let chunk = missing.len().div_ceil(shards);
1315 let rv = read_view.as_ref();
1316 let read_shard = |hashes: &[H256]| -> Vec<Result<Option<Code>, StoreError>> {
1317 let keys: Vec<&[u8]> = hashes.iter().map(|h| h.as_bytes()).collect();
1318 rv.multi_get(ACCOUNT_CODES, &keys)
1319 .into_iter()
1320 .zip(hashes.iter())
1321 .map(|(value, hash)| decode(hash, value?))
1322 .collect()
1323 };
1324 std::thread::scope(|scope| {
1325 let handles: Vec<_> = missing
1326 .chunks(chunk)
1327 .map(|ck| (ck, scope.spawn(move || read_shard(ck))))
1328 .collect();
1329 handles
1330 .into_iter()
1331 .flat_map(|(shard, handle)| {
1332 handle.join().unwrap_or_else(|_| {
1337 shard
1338 .iter()
1339 .map(|_| {
1340 Err(StoreError::Custom(
1341 "account code shard panicked".to_string(),
1342 ))
1343 })
1344 .collect()
1345 })
1346 })
1347 .collect()
1348 })
1349 } else {
1350 let rv = read_view.as_ref();
1353 missing
1354 .par_iter()
1355 .map(|hash| decode(hash, rv.get(ACCOUNT_CODES, hash.as_bytes())?))
1356 .collect()
1357 };
1358
1359 let mut fetched: Vec<Code> = Vec::new();
1360 for (hash, code) in missing.iter().zip(decoded.into_iter()) {
1361 let Some(code) = code? else { continue };
1362 for &i in pending.get(hash).into_iter().flatten() {
1363 out[i] = Some(code.clone());
1364 }
1365 fetched.push(code);
1366 }
1367
1368 if !fetched.is_empty() {
1369 let mut cache = self
1370 .account_code_cache
1371 .lock()
1372 .map_err(|_| StoreError::LockError)?;
1373 for code in &fetched {
1374 cache.insert(code)?;
1375 }
1376 }
1377
1378 Ok(out)
1379 }
1380
1381 pub fn code_exists(&self, code_hash: H256) -> Result<bool, StoreError> {
1386 if self.buffer()?.get_code(&code_hash).is_some() {
1390 return Ok(true);
1391 }
1392 if self
1394 .account_code_cache
1395 .lock()
1396 .map_err(|_| StoreError::LockError)?
1397 .get(&code_hash)?
1398 .is_some()
1399 {
1400 return Ok(true);
1401 }
1402 Ok(self
1404 .backend
1405 .begin_read()?
1406 .get(ACCOUNT_CODES, code_hash.as_bytes())?
1407 .is_some())
1408 }
1409
1410 pub fn get_code_metadata(&self, code_hash: H256) -> Result<Option<CodeMetadata>, StoreError> {
1415 use ethrex_common::constants::EMPTY_KECCAK_HASH;
1416
1417 if code_hash == *EMPTY_KECCAK_HASH {
1419 return Ok(Some(CodeMetadata { length: 0 }));
1420 }
1421
1422 if let Some(metadata) = self
1424 .code_metadata_cache
1425 .lock()
1426 .map_err(|_| StoreError::LockError)?
1427 .get(&code_hash)
1428 .copied()
1429 {
1430 return Ok(Some(metadata));
1431 }
1432
1433 let metadata = if let Some(bytes) = self
1435 .backend
1436 .begin_read()?
1437 .get(ACCOUNT_CODE_METADATA, code_hash.as_bytes())?
1438 {
1439 let length =
1440 u64::from_be_bytes(bytes.try_into().map_err(|_| {
1441 StoreError::Custom("Invalid metadata length encoding".to_string())
1442 })?);
1443 CodeMetadata { length }
1444 } else {
1445 let Some(code) = self.get_account_code(code_hash)? else {
1447 return Ok(None);
1448 };
1449 let metadata = CodeMetadata {
1450 length: code.len() as u64,
1451 };
1452
1453 if let Ok(handle) = tokio::runtime::Handle::try_current() {
1461 let metadata_buf = metadata.length.to_be_bytes().to_vec();
1462 let hash_key = code_hash.0.to_vec();
1463 let backend = self.backend.clone();
1464 handle.spawn(async move {
1465 if let Err(e) = async {
1466 let mut tx = backend.begin_write()?;
1467 tx.put(ACCOUNT_CODE_METADATA, &hash_key, &metadata_buf)?;
1468 tx.commit()
1469 }
1470 .await
1471 {
1472 tracing::warn!("Failed to write code metadata during backfill: {}", e);
1473 }
1474 });
1475 }
1476
1477 metadata
1478 };
1479
1480 self.code_metadata_cache
1482 .lock()
1483 .map_err(|_| StoreError::LockError)?
1484 .put(code_hash, metadata);
1485
1486 Ok(Some(metadata))
1487 }
1488
1489 pub async fn add_account_code(&self, code: Code) -> Result<(), StoreError> {
1491 let hash_key = code.hash.0.to_vec();
1492 let buf = encode_code(&code);
1493 let metadata_buf = (code.len() as u64).to_be_bytes();
1494
1495 let backend = self.backend.clone();
1497 tokio::task::spawn_blocking(move || {
1498 let mut tx = backend.begin_write()?;
1499 tx.put(ACCOUNT_CODES, &hash_key, &buf)?;
1500 tx.put(ACCOUNT_CODE_METADATA, &hash_key, &metadata_buf)?;
1501 tx.commit()
1502 })
1503 .await
1504 .map_err(|e| StoreError::Custom(format!("Task panicked: {}", e)))?
1505 }
1506
1507 pub async fn clear_snap_state(&self) -> Result<(), StoreError> {
1509 let db = self.backend.clone();
1510 tokio::task::spawn_blocking(move || db.clear_table(SNAP_STATE))
1511 .await
1512 .map_err(|e| StoreError::Custom(format!("Task panicked: {}", e)))?
1513 }
1514
1515 pub async fn get_transaction_by_hash(
1516 &self,
1517 transaction_hash: H256,
1518 ) -> Result<Option<Transaction>, StoreError> {
1519 let (_block_number, block_hash, index) =
1520 match self.get_transaction_location(transaction_hash).await? {
1521 Some(location) => location,
1522 None => return Ok(None),
1523 };
1524 self.get_transaction_by_location(block_hash, index).await
1525 }
1526
1527 pub async fn get_transaction_by_location(
1528 &self,
1529 block_hash: H256,
1530 index: u64,
1531 ) -> Result<Option<Transaction>, StoreError> {
1532 let block_body = match self.get_block_body_by_hash(block_hash).await? {
1533 Some(body) => body,
1534 None => return Ok(None),
1535 };
1536 let index: usize = index.try_into()?;
1537 Ok(block_body.transactions.get(index).cloned())
1538 }
1539
1540 pub async fn get_block_by_hash(
1541 &self,
1542 block_hash: BlockHash,
1543 ) -> Result<Option<Block>, StoreError> {
1544 let header = match self.get_block_header_by_hash(block_hash)? {
1545 Some(header) => header,
1546 None => return Ok(None),
1547 };
1548 let body = match self.get_block_body_by_hash(block_hash).await? {
1549 Some(body) => body,
1550 None => return Ok(None),
1551 };
1552 Ok(Some(Block::new(header, body)))
1553 }
1554
1555 pub async fn get_block_by_number(
1556 &self,
1557 block_number: BlockNumber,
1558 ) -> Result<Option<Block>, StoreError> {
1559 let Some(block_hash) = self.get_canonical_block_hash(block_number).await? else {
1560 return Ok(None);
1561 };
1562 self.get_block_by_hash(block_hash).await
1563 }
1564
1565 pub async fn get_canonical_block_hash(
1567 &self,
1568 block_number: BlockNumber,
1569 ) -> Result<Option<BlockHash>, StoreError> {
1570 let last = self.latest_block_header.get();
1571 if last.number == block_number {
1572 return Ok(Some(last.hash()));
1573 }
1574 let backend = self.backend.clone();
1575 tokio::task::spawn_blocking(move || {
1576 backend
1577 .begin_read()?
1578 .get(
1579 CANONICAL_BLOCK_HASHES,
1580 block_number.to_le_bytes().as_slice(),
1581 )?
1582 .map(|bytes| H256::decode(bytes.as_slice()))
1583 .transpose()
1584 .map_err(StoreError::from)
1585 })
1586 .await
1587 .map_err(|e| StoreError::Custom(format!("Task panicked: {}", e)))?
1588 }
1589
1590 pub async fn set_chain_config(&mut self, chain_config: &ChainConfig) -> Result<(), StoreError> {
1593 self.chain_config = *chain_config;
1594 let key = chain_data_key(ChainDataIndex::ChainConfig);
1595 let value = serde_json::to_string(chain_config)
1596 .map_err(|_| StoreError::Custom("Failed to serialize chain config".to_string()))?
1597 .into_bytes();
1598 self.write_async(CHAIN_DATA, key, value).await
1599 }
1600
1601 pub async fn update_earliest_block_number(
1603 &self,
1604 block_number: BlockNumber,
1605 ) -> Result<(), StoreError> {
1606 let key = chain_data_key(ChainDataIndex::EarliestBlockNumber);
1607 let value = block_number.to_le_bytes().to_vec();
1608 self.write_async(CHAIN_DATA, key, value).await
1609 }
1610
1611 async fn read_chain_data_block_number(
1613 &self,
1614 index: ChainDataIndex,
1615 ) -> Result<Option<BlockNumber>, StoreError> {
1616 self.read_async(CHAIN_DATA, chain_data_key(index))
1617 .await?
1618 .map(decode_block_number)
1619 .transpose()
1620 }
1621
1622 pub async fn get_earliest_block_number(&self) -> Result<BlockNumber, StoreError> {
1624 self.read_chain_data_block_number(ChainDataIndex::EarliestBlockNumber)
1625 .await?
1626 .ok_or(StoreError::MissingEarliestBlockNumber)
1627 }
1628
1629 pub async fn get_finalized_block_number(&self) -> Result<Option<BlockNumber>, StoreError> {
1631 self.read_chain_data_block_number(ChainDataIndex::FinalizedBlockNumber)
1632 .await
1633 }
1634
1635 pub async fn get_safe_block_number(&self) -> Result<Option<BlockNumber>, StoreError> {
1637 self.read_chain_data_block_number(ChainDataIndex::SafeBlockNumber)
1638 .await
1639 }
1640
1641 pub fn get_latest_block_number(&self) -> Result<BlockNumber, StoreError> {
1643 Ok(self.latest_block_header.get().number)
1644 }
1645
1646 pub async fn update_pending_block_number(
1648 &self,
1649 block_number: BlockNumber,
1650 ) -> Result<(), StoreError> {
1651 let key = chain_data_key(ChainDataIndex::PendingBlockNumber);
1652 let value = block_number.to_le_bytes().to_vec();
1653 self.write_async(CHAIN_DATA, key, value).await
1654 }
1655
1656 pub async fn get_pending_block_number(&self) -> Result<Option<BlockNumber>, StoreError> {
1658 self.read_chain_data_block_number(ChainDataIndex::PendingBlockNumber)
1659 .await
1660 }
1661
1662 async fn forkchoice_update_inner(
1677 &self,
1678 new_canonical_blocks: Vec<(BlockNumber, BlockHash)>,
1679 head_number: BlockNumber,
1680 head_hash: BlockHash,
1681 safe: Option<BlockNumber>,
1682 finalized: Option<BlockNumber>,
1683 ) -> Result<(), StoreError> {
1684 let latest = self.load_latest_block_number().await?.unwrap_or(0);
1685 let db = self.backend.clone();
1686 let journal_pruning_paused = self.journal_pruning_paused.clone();
1687 tokio::task::spawn_blocking(move || {
1688 let mut txn = db.begin_write()?;
1689
1690 for (block_number, block_hash) in new_canonical_blocks {
1691 let head_key = block_number.to_le_bytes();
1692 let head_value = block_hash.encode_to_vec();
1693 txn.put(CANONICAL_BLOCK_HASHES, &head_key, &head_value)?;
1694 }
1695
1696 for number in (head_number + 1)..=(latest) {
1703 txn.delete(CANONICAL_BLOCK_HASHES, number.to_le_bytes().as_slice())?;
1704 }
1705
1706 let head_key = head_number.to_le_bytes();
1708 let head_value = head_hash.encode_to_vec();
1709 txn.put(CANONICAL_BLOCK_HASHES, &head_key, &head_value)?;
1710
1711 let latest_key = chain_data_key(ChainDataIndex::LatestBlockNumber);
1713 txn.put(CHAIN_DATA, &latest_key, &head_number.to_le_bytes())?;
1714
1715 if let Some(safe) = safe {
1716 let safe_key = chain_data_key(ChainDataIndex::SafeBlockNumber);
1717 txn.put(CHAIN_DATA, &safe_key, &safe.to_le_bytes())?;
1718 }
1719
1720 if let Some(finalized) = finalized {
1721 let finalized_key = chain_data_key(ChainDataIndex::FinalizedBlockNumber);
1722
1723 let prev_finalized = match db.begin_read()?.get(CHAIN_DATA, &finalized_key)? {
1733 Some(bytes) => {
1734 let arr: [u8; 8] = bytes.as_slice().try_into().map_err(|_| {
1735 StoreError::Custom(format!(
1736 "FinalizedBlockNumber has unexpected length {} (want 8)",
1737 bytes.len()
1738 ))
1739 })?;
1740 BlockNumber::from_le_bytes(arr)
1741 }
1742 None => 0,
1743 };
1744
1745 txn.put(CHAIN_DATA, &finalized_key, &finalized.to_le_bytes())?;
1746
1747 if finalized > prev_finalized
1759 && !journal_pruning_paused.load(std::sync::atomic::Ordering::Acquire)
1760 {
1761 let start = 0u64.to_be_bytes();
1762 let end = finalized.saturating_add(1).to_be_bytes();
1763 txn.delete_range(STATE_HISTORY, &start, &end)?;
1764 }
1765 }
1766
1767 txn.commit()
1768 })
1769 .await
1770 .map_err(|e| StoreError::Custom(format!("Task panicked: {}", e)))?
1771 }
1772
1773 pub async fn get_receipts_for_block(
1774 &self,
1775 block_hash: &BlockHash,
1776 ) -> Result<Vec<Receipt>, StoreError> {
1777 self.get_receipts_for_block_from_index(block_hash, 0, None)
1778 .await
1779 }
1780
1781 pub async fn get_receipts_for_block_from_index(
1790 &self,
1791 block_hash: &BlockHash,
1792 start_index: u64,
1793 max_count: Option<usize>,
1794 ) -> Result<Vec<Receipt>, StoreError> {
1795 if let Some(all) = self.buffer()?.get_receipts(block_hash) {
1796 let start = start_index as usize;
1797 let slice = all.into_iter().skip(start);
1798 return Ok(match max_count {
1799 Some(max) => slice.take(max).collect(),
1800 None => slice.collect(),
1801 });
1802 }
1803 let backend = self.backend.clone();
1804 let block_hash = *block_hash;
1805
1806 tokio::task::spawn_blocking(move || {
1807 let txn = backend.begin_read()?;
1808 let prefix = block_hash.as_bytes().to_vec();
1809 let mut seek_key = prefix.clone();
1812 seek_key.extend_from_slice(&start_index.to_be_bytes());
1813 let iter = txn.prefix_iterator(RECEIPTS_V2, &seek_key)?;
1814 let mut receipts = Vec::new();
1815 for result in iter {
1816 let (k, v) = result?;
1817 if !k.starts_with(&prefix) {
1818 break;
1819 }
1820 if k.len() != 40 {
1821 continue;
1822 }
1823 receipts.push(Receipt::decode_storage(v.as_ref())?);
1824 if let Some(max) = max_count
1825 && receipts.len() >= max
1826 {
1827 break;
1828 }
1829 }
1830 Ok(receipts)
1831 })
1832 .await
1833 .map_err(|e| StoreError::Custom(format!("Task panicked: {e}")))?
1834 }
1835
1836 pub async fn set_header_download_checkpoint(
1840 &self,
1841 block_hash: BlockHash,
1842 ) -> Result<(), StoreError> {
1843 let key = snap_state_key(SnapStateIndex::HeaderDownloadCheckpoint);
1844 let value = block_hash.encode_to_vec();
1845 self.write_async(SNAP_STATE, key, value).await
1846 }
1847
1848 pub async fn get_header_download_checkpoint(&self) -> Result<Option<BlockHash>, StoreError> {
1850 let key = snap_state_key(SnapStateIndex::HeaderDownloadCheckpoint);
1851 self.backend
1852 .begin_read()?
1853 .get(SNAP_STATE, &key)?
1854 .map(|bytes| H256::decode(bytes.as_slice()))
1855 .transpose()
1856 .map_err(StoreError::from)
1857 }
1858
1859 pub async fn set_latest_valid_ancestor(
1865 &self,
1866 bad_block: BlockHash,
1867 latest_valid: BlockHash,
1868 ) -> Result<(), StoreError> {
1869 let value = latest_valid.encode_to_vec();
1870 self.write_async(INVALID_CHAINS, bad_block.as_bytes().to_vec(), value)
1871 .await
1872 }
1873
1874 pub async fn get_latest_valid_ancestor(
1877 &self,
1878 block: BlockHash,
1879 ) -> Result<Option<BlockHash>, StoreError> {
1880 self.read_async(INVALID_CHAINS, block.as_bytes().to_vec())
1881 .await?
1882 .map(|bytes| H256::decode(bytes.as_slice()))
1883 .transpose()
1884 .map_err(StoreError::from)
1885 }
1886
1887 pub async fn add_bad_block(&self, block: Block) -> Result<(), StoreError> {
1892 let mut bad_blocks = self.get_bad_blocks().await?;
1893 let block_number = block.header.number;
1894 let block_hash = block.hash();
1895 if bad_blocks
1896 .iter()
1897 .any(|b| b.header.number == block_number && b.hash() == block_hash)
1898 {
1899 return Ok(());
1900 }
1901 bad_blocks.push(block);
1902 bad_blocks.sort_by(|a, b| b.header.number.cmp(&a.header.number));
1903 bad_blocks.truncate(MAX_BAD_BLOCKS);
1904 self.write_async(
1905 BAD_BLOCKS,
1906 BAD_BLOCKS_KEY.to_vec(),
1907 bad_blocks.encode_to_vec(),
1908 )
1909 .await
1910 }
1911
1912 pub async fn get_bad_blocks(&self) -> Result<Vec<Block>, StoreError> {
1915 match self.read_async(BAD_BLOCKS, BAD_BLOCKS_KEY.to_vec()).await? {
1916 Some(bytes) => Vec::<Block>::decode(&bytes).map_err(StoreError::from),
1917 None => Ok(Vec::new()),
1918 }
1919 }
1920
1921 pub fn get_block_number_sync(
1923 &self,
1924 block_hash: BlockHash,
1925 ) -> Result<Option<BlockNumber>, StoreError> {
1926 if let Some(n) = self.buffer()?.get_number(&block_hash) {
1927 return Ok(Some(n));
1928 }
1929 let txn = self.backend.begin_read()?;
1930 txn.get(BLOCK_NUMBERS, &block_hash.encode_to_vec())?
1931 .map(decode_block_number)
1932 .transpose()
1933 }
1934
1935 pub fn get_canonical_block_hash_sync(
1937 &self,
1938 block_number: BlockNumber,
1939 ) -> Result<Option<BlockHash>, StoreError> {
1940 let last = self.latest_block_header.get();
1941 if last.number == block_number {
1942 return Ok(Some(last.hash()));
1943 }
1944 let txn = self.backend.begin_read()?;
1945 txn.get(
1946 CANONICAL_BLOCK_HASHES,
1947 block_number.to_le_bytes().as_slice(),
1948 )?
1949 .map(|bytes| H256::decode(bytes.as_slice()))
1950 .transpose()
1951 .map_err(StoreError::from)
1952 }
1953
1954 pub async fn write_storage_trie_nodes_batch(
1957 &self,
1958 storage_trie_nodes: StorageUpdates,
1959 ) -> Result<(), StoreError> {
1960 let mut txn = self.backend.begin_write()?;
1961 tokio::task::spawn_blocking(move || {
1962 for (address_hash, nodes) in storage_trie_nodes {
1963 for (node_path, node_data) in nodes {
1964 let key = apply_prefix(Some(address_hash), node_path);
1965 if node_data.is_empty() {
1966 txn.delete(STORAGE_TRIE_NODES, key.as_ref())?;
1967 } else {
1968 txn.put(STORAGE_TRIE_NODES, key.as_ref(), &node_data)?;
1969 }
1970 }
1971 }
1972 txn.commit()
1973 })
1974 .await
1975 .map_err(|e| StoreError::Custom(format!("Task panicked: {}", e)))?
1976 }
1977
1978 pub async fn write_account_code_batch(
1981 &self,
1982 account_codes: Vec<(H256, Code)>,
1983 ) -> Result<(), StoreError> {
1984 let mut code_batch_items = Vec::new();
1985 let mut metadata_batch_items = Vec::new();
1986
1987 for (code_hash, code) in account_codes {
1988 let buf = encode_code(&code);
1989 let metadata_buf = (code.len() as u64).to_be_bytes().to_vec();
1990 code_batch_items.push((code_hash.as_bytes().to_vec(), buf));
1991 metadata_batch_items.push((code_hash.as_bytes().to_vec(), metadata_buf));
1992 }
1993
1994 self.write_batch_async(ACCOUNT_CODES, code_batch_items)
1996 .await?;
1997 self.write_batch_async(ACCOUNT_CODE_METADATA, metadata_batch_items)
1998 .await
1999 }
2000
2001 fn buffer(&self) -> Result<Arc<BlockDataBuffer>, StoreError> {
2003 Ok(self
2004 .block_data_buffer
2005 .read()
2006 .map_err(|_| StoreError::LockError)?
2007 .clone())
2008 }
2009
2010 pub fn write(
2016 &self,
2017 table: &'static str,
2018 key: Vec<u8>,
2019 value: Vec<u8>,
2020 ) -> Result<(), StoreError> {
2021 let backend = self.backend.clone();
2022 let mut txn = backend.begin_write()?;
2023 txn.put(table, &key, &value)?;
2024 txn.commit()
2025 }
2026
2027 async fn write_async(
2030 &self,
2031 table: &'static str,
2032 key: Vec<u8>,
2033 value: Vec<u8>,
2034 ) -> Result<(), StoreError> {
2035 let backend = self.backend.clone();
2036
2037 tokio::task::spawn_blocking(move || {
2038 let mut txn = backend.begin_write()?;
2039 txn.put(table, &key, &value)?;
2040 txn.commit()
2041 })
2042 .await
2043 .map_err(|e| StoreError::Custom(format!("Task panicked: {}", e)))?
2044 }
2045
2046 pub async fn read_async(
2049 &self,
2050 table: &'static str,
2051 key: Vec<u8>,
2052 ) -> Result<Option<Vec<u8>>, StoreError> {
2053 let backend = self.backend.clone();
2054
2055 tokio::task::spawn_blocking(move || {
2056 let txn = backend.begin_read()?;
2057 txn.get(table, &key)
2058 })
2059 .await
2060 .map_err(|e| StoreError::Custom(format!("Task panicked: {}", e)))?
2061 }
2062
2063 pub fn read(&self, table: &'static str, key: Vec<u8>) -> Result<Option<Vec<u8>>, StoreError> {
2066 let backend = self.backend.clone();
2067 let txn = backend.begin_read()?;
2068 txn.get(table, &key)
2069 }
2070
2071 pub async fn write_batch_async(
2075 &self,
2076 table: &'static str,
2077 batch_ops: Vec<(Vec<u8>, Vec<u8>)>,
2078 ) -> Result<(), StoreError> {
2079 let backend = self.backend.clone();
2080
2081 tokio::task::spawn_blocking(move || {
2082 let mut txn = backend.begin_write()?;
2083 txn.put_batch(table, batch_ops)?;
2084 txn.commit()
2085 })
2086 .await
2087 .map_err(|e| StoreError::Custom(format!("Task panicked: {}", e)))?
2088 }
2089
2090 pub fn write_batch(
2092 &self,
2093 table: &'static str,
2094 batch_ops: Vec<(Vec<u8>, Vec<u8>)>,
2095 ) -> Result<(), StoreError> {
2096 let backend = self.backend.clone();
2097 let mut txn = backend.begin_write()?;
2098 txn.put_batch(table, batch_ops)?;
2099 txn.commit()
2100 }
2101
2102 pub async fn add_fullsync_batch(&self, headers: Vec<BlockHeader>) -> Result<(), StoreError> {
2103 self.write_batch_async(
2104 FULLSYNC_HEADERS,
2105 headers
2106 .into_iter()
2107 .map(|header| (header.number.to_le_bytes().to_vec(), header.encode_to_vec()))
2108 .collect(),
2109 )
2110 .await
2111 }
2112
2113 pub async fn read_fullsync_batch(
2114 &self,
2115 start: BlockNumber,
2116 limit: u64,
2117 ) -> Result<Vec<Option<BlockHeader>>, StoreError> {
2118 let mut res = vec![];
2119 let read_tx = self.backend.begin_read()?;
2120 for key in start..start + limit {
2122 let header_opt = read_tx
2123 .get(FULLSYNC_HEADERS, &key.to_le_bytes())?
2124 .map(|header| BlockHeader::decode(&header))
2125 .transpose()?;
2126 res.push(header_opt);
2127 }
2128 Ok(res)
2129 }
2130
2131 pub async fn clear_fullsync_headers(&self) -> Result<(), StoreError> {
2132 self.backend.clear_table(FULLSYNC_HEADERS)
2133 }
2134
2135 pub fn delete(&self, table: &'static str, key: Vec<u8>) -> Result<(), StoreError> {
2137 let mut txn = self.backend.begin_write()?;
2138 txn.delete(table, &key)?;
2139 txn.commit()
2140 }
2141
2142 pub fn store_block_updates(&self, update_batch: UpdateBatch) -> Result<(), StoreError> {
2143 self.apply_updates(update_batch)
2144 }
2145
2146 fn batch_state_roots(
2153 &self,
2154 update_batch: &UpdateBatch,
2155 ) -> Result<(H256, H256, BlockNumber, H256), StoreError> {
2156 let parent_state_root = self
2157 .get_block_header_by_hash(
2158 update_batch
2159 .blocks
2160 .first()
2161 .ok_or(StoreError::UpdateBatchNoBlocks)?
2162 .header
2163 .parent_hash,
2164 )?
2165 .map(|header| header.state_root)
2166 .unwrap_or_default();
2167 let last_block = update_batch
2168 .blocks
2169 .last()
2170 .ok_or(StoreError::UpdateBatchNoBlocks)?;
2171 let last_state_root = last_block.header.state_root;
2172 let last_block_number = last_block.header.number;
2173 let last_block_hash = last_block.hash();
2174 Ok((
2175 parent_state_root,
2176 last_state_root,
2177 last_block_number,
2178 last_block_hash,
2179 ))
2180 }
2181
2182 fn apply_updates(&self, update_batch: UpdateBatch) -> Result<(), StoreError> {
2186 let (parent_state_root, last_state_root, last_block_number, last_block_hash) =
2187 self.batch_state_roots(&update_batch)?;
2188
2189 let UpdateBatch {
2190 account_updates,
2191 storage_updates,
2192 blocks,
2193 receipts,
2194 code_updates,
2195 commit_depth,
2196 wait_for_flush,
2197 } = update_batch;
2198
2199 self.pending_trie_roots.register(last_state_root)?;
2203
2204 let blocks_with_receipts: Vec<(Block, Vec<Receipt>)> = if blocks.len() == 1 {
2207 let block = blocks.into_iter().next().expect("len == 1");
2208 let hash = block.hash();
2209 let r = receipts
2210 .into_iter()
2211 .find(|(h, _)| *h == hash)
2212 .map(|(_, r)| r)
2213 .unwrap_or_default();
2214 vec![(block, r)]
2215 } else {
2216 let mut receipts_by_hash: std::collections::HashMap<BlockHash, Vec<Receipt>> =
2217 receipts.into_iter().collect();
2218 blocks
2219 .into_iter()
2220 .map(|b| {
2221 let r = receipts_by_hash.remove(&b.hash()).unwrap_or_default();
2222 (b, r)
2223 })
2224 .collect()
2225 };
2226
2227 let (ack_tx, ack_rx) = sync_channel(1);
2232 self.persist_tx
2233 .send(PersistMessage::Block(Box::new(BlockPersist {
2234 blocks: blocks_with_receipts,
2235 codes: code_updates,
2236 parent_state_root,
2237 child_state_root: last_state_root,
2238 account_updates,
2239 storage_updates,
2240 commit_depth,
2241 wait_for_flush,
2242 block_number: last_block_number,
2243 block_hash: last_block_hash,
2244 ack: ack_tx,
2245 })))
2246 .map_err(|e| StoreError::Custom(format!("failed to send block persist: {e}")))?;
2247 ack_rx
2248 .recv()
2249 .map_err(|e| StoreError::Custom(format!("block persist ack failed: {e}")))??;
2250
2251 Ok(())
2252 }
2253
2254 pub fn new(path: impl AsRef<Path>, engine_type: EngineType) -> Result<Self, StoreError> {
2259 Self::new_with_config(path, engine_type, StoreConfig::default())
2260 }
2261
2262 pub fn new_with_config(
2264 path: impl AsRef<Path>,
2265 engine_type: EngineType,
2266 #[cfg_attr(not(feature = "rocksdb"), allow(unused_variables))] config: StoreConfig,
2268 ) -> Result<Self, StoreError> {
2269 let db_path = path.as_ref().to_path_buf();
2270
2271 if engine_type != EngineType::InMemory {
2272 let version = read_store_schema_version(&db_path)?;
2273
2274 match version {
2275 None if db_path.exists() && dir_contains_legacy_db(&db_path)? => {
2276 return Err(StoreError::NotFoundDBVersion);
2278 }
2279 None => {
2280 init_metadata_file(&db_path)?;
2286 }
2287 Some(v) if v < 1 => {
2291 return Err(StoreError::IncompatibleDBVersion {
2293 found: v,
2294 expected: STORE_SCHEMA_VERSION,
2295 });
2296 }
2297 Some(v) if v > STORE_SCHEMA_VERSION => {
2298 return Err(StoreError::IncompatibleDBVersion {
2301 found: v,
2302 expected: STORE_SCHEMA_VERSION,
2303 });
2304 }
2305 #[cfg(feature = "rocksdb")]
2306 Some(v) if v < STORE_SCHEMA_VERSION => {
2307 let rocksdb = Arc::new(RocksDBBackend::open(
2311 &path,
2312 config.rocksdb_block_cache_size,
2313 )?);
2314 crate::migrations::run_pending_migrations(rocksdb.as_ref(), &db_path, v)?;
2315 rocksdb.drop_obsolete_cfs(&path);
2316 let backend: Arc<dyn crate::api::StorageBackend> = rocksdb;
2317 return Self::from_backend(
2318 backend,
2319 db_path,
2320 DB_COMMIT_THRESHOLD,
2321 config.persist_channel_capacity,
2322 );
2323 }
2324 Some(_) => {
2325 }
2330 }
2331 }
2332
2333 match engine_type {
2334 #[cfg(feature = "rocksdb")]
2335 EngineType::RocksDB => {
2336 let rocksdb = RocksDBBackend::open(&path, config.rocksdb_block_cache_size)?;
2337 rocksdb.drop_obsolete_cfs(&path);
2338 let backend: Arc<dyn StorageBackend> = Arc::new(rocksdb);
2339 Self::from_backend(
2340 backend,
2341 db_path,
2342 DB_COMMIT_THRESHOLD,
2343 config.persist_channel_capacity,
2344 )
2345 }
2346 EngineType::InMemory => {
2347 let backend = Arc::new(InMemoryBackend::open()?);
2348 Self::from_backend(
2349 backend,
2350 db_path,
2351 IN_MEMORY_COMMIT_THRESHOLD,
2352 config.persist_channel_capacity,
2353 )
2354 }
2355 }
2356 }
2357
2358 fn from_backend(
2359 backend: Arc<dyn StorageBackend>,
2360 db_path: PathBuf,
2361 commit_threshold: usize,
2362 persist_channel_capacity: usize,
2363 ) -> Result<Self, StoreError> {
2364 debug!("Initializing Store with {commit_threshold} in-memory diff-layers");
2365 let (fkv_tx, fkv_rx) = std::sync::mpsc::sync_channel(0);
2366 let persist_cap = persist_channel_capacity.max(1); let (persist_tx, persist_rx) = std::sync::mpsc::sync_channel(persist_cap);
2368
2369 let (last_written, initial_flushed_upto) = {
2370 let tx = backend.begin_read()?;
2371 let last_written = tx
2372 .get(MISC_VALUES, "last_written".as_bytes())?
2373 .unwrap_or_else(|| vec![0u8; 64]);
2374 let last_written = if last_written == [0xff] {
2375 vec![0xff; 64]
2376 } else {
2377 last_written
2378 };
2379 let initial_flushed_upto = match tx.get(MISC_VALUES, FLUSHED_UPTO_KEY)? {
2380 Some(bytes) => decode_flushed_upto(&bytes)?,
2381 None => 0,
2382 };
2383 (last_written, initial_flushed_upto)
2384 };
2385 let mut initial_buffer = BlockDataBuffer::new();
2386 initial_buffer.set_flushed_upto(initial_flushed_upto);
2387
2388 let mut background_threads = Vec::new();
2389 let safe_commit_root = Arc::new(RwLock::new(H256::zero()));
2390 let mut store = Self {
2391 db_path,
2392 backend,
2393 chain_config: Default::default(),
2394 latest_block_header: Default::default(),
2395 trie_cache: Arc::new(RwLock::new(Arc::new(TrieLayerCache::new_with_safe_commit(
2396 commit_threshold,
2397 safe_commit_root.clone(),
2398 )))),
2399 flatkeyvalue_control_tx: fkv_tx,
2400 block_data_buffer: Arc::new(RwLock::new(Arc::new(initial_buffer))),
2401 persist_tx,
2402 pending_trie_roots: Arc::new(PendingTrieRoots::default()),
2403 last_computed_flatkeyvalue: Arc::new(RwLock::new(last_written)),
2404 account_code_cache: Arc::new(Mutex::new(CodeCache::default())),
2405 code_metadata_cache: Arc::new(Mutex::new(LruCache::with_hasher(
2406 NonZeroUsize::new(CODE_METADATA_CACHE_MAX_ENTRIES).unwrap_or(NonZeroUsize::MIN),
2407 FxBuildHasher,
2408 ))),
2409 fcu_lock: Arc::new(tokio::sync::Mutex::new(())),
2410 safe_commit_root,
2411 journal_pruning_paused: Arc::new(std::sync::atomic::AtomicBool::new(false)),
2412 background_threads: Default::default(),
2413 };
2414 let backend_clone = store.backend.clone();
2415 let last_computed_fkv = store.last_computed_flatkeyvalue.clone();
2416 background_threads.push(std::thread::spawn(move || {
2417 let rx = fkv_rx;
2418 loop {
2420 match rx.recv() {
2421 Ok(FKVGeneratorControlMessage::Continue) => break,
2422 Ok(FKVGeneratorControlMessage::Stop) => {}
2423 Err(std::sync::mpsc::RecvError) => {
2424 debug!("Closing FlatKeyValue generator.");
2425 return;
2426 }
2427 }
2428 }
2429
2430 let _ = flatkeyvalue_generator(&backend_clone, &last_computed_fkv, &rx)
2431 .inspect_err(|err| error!("Error while generating FlatKeyValue: {err}"));
2432 }));
2433 let persist_backend = store.backend.clone();
2436 let persist_buffer = store.block_data_buffer.clone();
2437 let persist_trie_cache = store.trie_cache.clone();
2438 let persist_pending_roots = store.pending_trie_roots.clone();
2439 let persist_fkv_ctl = store.flatkeyvalue_control_tx.clone();
2440 background_threads.push(std::thread::spawn(move || {
2441 let rx = persist_rx;
2442 let mut last_flush_result: Result<(), StoreError> = Ok(());
2445 loop {
2446 match rx.recv() {
2447 Ok(PersistMessage::Block(bp)) => {
2448 let bp = *bp;
2449 let staged = mutate_block_buffer(&persist_buffer, move |b| {
2452 let mut codes = Some(bp.codes);
2453 for (block, receipts) in bp.blocks {
2454 b.insert(block, receipts, codes.take().unwrap_or_default());
2455 }
2456 });
2457 if let Err(e) = staged {
2458 persist_pending_roots.clear(bp.child_state_root);
2463 let _ = bp.ack.send(Err(e));
2464 continue;
2465 }
2466 if !bp.wait_for_flush {
2475 let _ = bp
2476 .ack
2477 .send(std::mem::replace(&mut last_flush_result, Ok(())));
2478 }
2479 if let Err(err) = apply_trie_phase1(
2481 &persist_trie_cache,
2482 &persist_pending_roots,
2483 bp.parent_state_root,
2484 bp.child_state_root,
2485 bp.block_number,
2486 bp.block_hash,
2487 bp.account_updates,
2488 bp.storage_updates,
2489 ) {
2490 error!("persist worker trie phase-1 failed: {err}");
2491 if bp.wait_for_flush {
2492 let _ = bp.ack.send(Err(err));
2493 } else {
2494 last_flush_result = Err(err);
2495 }
2496 continue;
2497 }
2498 let flushed = flush_block_data(persist_backend.as_ref(), &persist_buffer)
2500 .inspect_err(|err| error!("flush_block_data failed: {err}"))
2501 .and_then(|_| {
2502 commit_trie_if_due(
2503 persist_backend.as_ref(),
2504 &persist_trie_cache,
2505 &persist_fkv_ctl,
2506 bp.parent_state_root,
2507 bp.commit_depth,
2508 bp.wait_for_flush,
2509 )
2510 });
2511 if bp.wait_for_flush {
2514 let prior = std::mem::replace(&mut last_flush_result, Ok(()));
2515 let _ = bp.ack.send(prior.and(flushed));
2516 } else {
2517 last_flush_result = flushed;
2518 }
2519 }
2520 Ok(PersistMessage::Commit(root)) => match persist_trie_cache.read() {
2521 Ok(guard) => {
2522 let trie = guard.clone();
2523 drop(guard);
2524 let _ = commit_to_disk(
2527 persist_backend.as_ref(),
2528 &persist_fkv_ctl,
2529 &persist_trie_cache,
2530 &trie,
2531 root,
2532 false,
2533 )
2534 .inspect_err(|err| error!("commit_to_disk failed: {err}"));
2535 }
2536 Err(_) => error!("trie cache lock poisoned during commit"),
2537 },
2538 Ok(PersistMessage::Ping(ack)) => {
2539 let _ = ack.send(std::mem::replace(&mut last_flush_result, Ok(())));
2543 }
2544 Ok(PersistMessage::Shutdown { ack }) => {
2545 let result = flush_block_data(persist_backend.as_ref(), &persist_buffer);
2552 let prior = std::mem::replace(&mut last_flush_result, Ok(()));
2553 let _ = ack.send(prior.and(result));
2554 return;
2556 }
2557 Err(_) => return,
2558 }
2559 }
2560 }));
2561 store.background_threads = Arc::new(ThreadList {
2562 list: background_threads,
2563 });
2564 Ok(store)
2565 }
2566
2567 pub async fn new_from_genesis(
2570 store_path: &Path,
2571 engine_type: EngineType,
2572 genesis_path: &str,
2573 ) -> Result<Self, StoreError> {
2574 Self::new_from_genesis_with_config(
2575 store_path,
2576 engine_type,
2577 genesis_path,
2578 StoreConfig::default(),
2579 )
2580 .await
2581 }
2582
2583 pub async fn new_from_genesis_with_config(
2586 store_path: &Path,
2587 engine_type: EngineType,
2588 genesis_path: &str,
2589 config: StoreConfig,
2590 ) -> Result<Self, StoreError> {
2591 let file = std::fs::File::open(genesis_path)
2592 .map_err(|error| StoreError::Custom(format!("Failed to open genesis file: {error}")))?;
2593 let reader = std::io::BufReader::new(file);
2594 let genesis: Genesis = serde_json::from_reader(reader)
2595 .map_err(|e| StoreError::Custom(format!("Failed to deserialize genesis file: {e}")))?;
2596 let mut store = Self::new_with_config(store_path, engine_type, config)?;
2597 store.add_initial_state(genesis).await?;
2598 Ok(store)
2599 }
2600
2601 pub async fn get_account_info(
2602 &self,
2603 block_number: BlockNumber,
2604 address: Address,
2605 ) -> Result<Option<AccountInfo>, StoreError> {
2606 match self.get_canonical_block_hash(block_number).await? {
2607 Some(block_hash) => self.get_account_info_by_hash(block_hash, address),
2608 None => Ok(None),
2609 }
2610 }
2611
2612 pub fn get_account_info_by_hash(
2613 &self,
2614 block_hash: BlockHash,
2615 address: Address,
2616 ) -> Result<Option<AccountInfo>, StoreError> {
2617 let Some(state_trie) = self.state_trie(block_hash)? else {
2618 return Ok(None);
2619 };
2620 let hashed_address = hash_address_fixed(&address);
2621
2622 let Some(encoded_state) = state_trie.get(hashed_address.as_bytes())? else {
2623 return Ok(None);
2624 };
2625
2626 let account_state = AccountState::decode(&encoded_state)?;
2627 Ok(Some(AccountInfo {
2628 code_hash: account_state.code_hash,
2629 balance: account_state.balance,
2630 nonce: account_state.nonce,
2631 }))
2632 }
2633
2634 pub fn get_account_state_by_acc_hash(
2635 &self,
2636 block_hash: BlockHash,
2637 account_hash: H256,
2638 ) -> Result<Option<AccountState>, StoreError> {
2639 let Some(state_trie) = self.state_trie(block_hash)? else {
2640 return Ok(None);
2641 };
2642 let Some(encoded_state) = state_trie.get(account_hash.as_bytes())? else {
2643 return Ok(None);
2644 };
2645 let account_state = AccountState::decode(&encoded_state)?;
2646 Ok(Some(account_state))
2647 }
2648
2649 pub async fn get_fork_id(&self) -> Result<ForkId, StoreError> {
2650 let chain_config = self.get_chain_config();
2651 let genesis_header = self
2652 .load_block_header(0)?
2653 .ok_or(StoreError::MissingEarliestBlockNumber)?;
2654 let block_header = self.latest_block_header.get();
2655
2656 Ok(ForkId::new(
2657 chain_config,
2658 genesis_header,
2659 block_header.timestamp,
2660 block_header.number,
2661 ))
2662 }
2663
2664 pub async fn get_code_by_account_address(
2665 &self,
2666 block_number: BlockNumber,
2667 address: Address,
2668 ) -> Result<Option<Code>, StoreError> {
2669 let Some(block_hash) = self.get_canonical_block_hash(block_number).await? else {
2670 return Ok(None);
2671 };
2672 let Some(state_trie) = self.state_trie(block_hash)? else {
2673 return Ok(None);
2674 };
2675 let hashed_address = hash_address_fixed(&address);
2676 let Some(encoded_state) = state_trie.get(hashed_address.as_bytes())? else {
2677 return Ok(None);
2678 };
2679 let account_state = AccountState::decode(&encoded_state)?;
2680 self.get_account_code(account_state.code_hash)
2681 }
2682
2683 pub async fn get_nonce_by_account_address(
2684 &self,
2685 block_number: BlockNumber,
2686 address: Address,
2687 ) -> Result<Option<u64>, StoreError> {
2688 let Some(block_hash) = self.get_canonical_block_hash(block_number).await? else {
2689 return Ok(None);
2690 };
2691 let Some(state_trie) = self.state_trie(block_hash)? else {
2692 return Ok(None);
2693 };
2694 let hashed_address = hash_address_fixed(&address);
2695 let Some(encoded_state) = state_trie.get(hashed_address.as_bytes())? else {
2696 return Ok(None);
2697 };
2698 let account_state = AccountState::decode(&encoded_state)?;
2699 Ok(Some(account_state.nonce))
2700 }
2701
2702 pub fn apply_account_updates_batch(
2705 &self,
2706 block_hash: BlockHash,
2707 account_updates: &[AccountUpdate],
2708 ) -> Result<Option<AccountUpdatesList>, StoreError> {
2709 let Some(mut state_trie) = self.state_trie(block_hash)? else {
2710 return Ok(None);
2711 };
2712
2713 Ok(Some(self.apply_account_updates_from_trie_batch(
2714 &mut state_trie,
2715 account_updates,
2716 )?))
2717 }
2718
2719 pub fn apply_account_updates_from_trie_batch<'a>(
2720 &self,
2721 state_trie: &mut Trie,
2722 account_updates: impl IntoIterator<Item = &'a AccountUpdate>,
2723 ) -> Result<AccountUpdatesList, StoreError> {
2724 let mut ret_storage_updates = Vec::new();
2725 let mut code_updates = Vec::new();
2726 let state_root = state_trie.hash_no_commit(&NativeCrypto);
2727 for update in account_updates {
2728 let hashed_address = hash_address_fixed(&update.address);
2729 if update.removed {
2730 state_trie.remove(hashed_address.as_bytes())?;
2732 continue;
2733 }
2734 let mut account_state = match state_trie.get(hashed_address.as_bytes())? {
2737 Some(encoded_state) => AccountState::decode(&encoded_state)?,
2738 None => AccountState::default(),
2739 };
2740 if update.removed_storage {
2741 account_state.storage_root = EMPTY_TRIE_HASH;
2742 }
2743 if let Some(info) = &update.info {
2744 account_state.nonce = info.nonce;
2745 account_state.balance = info.balance;
2746 account_state.code_hash = info.code_hash;
2747 if let Some(code) = &update.code {
2749 code_updates.push((info.code_hash, code.clone()));
2750 }
2751 }
2752 if !update.added_storage.is_empty() {
2754 let mut storage_trie =
2755 self.open_storage_trie(hashed_address, state_root, account_state.storage_root)?;
2756 for (storage_key, storage_value) in &update.added_storage {
2757 let hashed_key = hash_key(storage_key);
2758 if storage_value.is_zero() {
2759 storage_trie.remove(&hashed_key)?;
2760 } else {
2761 storage_trie.insert(hashed_key, storage_value.encode_to_vec())?;
2762 }
2763 }
2764 let (storage_hash, storage_updates) =
2765 storage_trie.collect_changes_since_last_hash(&NativeCrypto);
2766 account_state.storage_root = storage_hash;
2767 ret_storage_updates.push((hashed_address, storage_updates));
2768 }
2769 state_trie.insert(
2770 hashed_address.as_bytes().to_vec(),
2771 account_state.encode_to_vec(),
2772 )?;
2773 }
2774 let (state_trie_hash, state_updates) =
2775 state_trie.collect_changes_since_last_hash(&NativeCrypto);
2776
2777 Ok(AccountUpdatesList {
2778 state_trie_hash,
2779 state_updates,
2780 storage_updates: ret_storage_updates,
2781 code_updates,
2782 })
2783 }
2784
2785 pub fn apply_account_updates_from_trie_with_witness(
2788 &self,
2789 mut state_trie: Trie,
2790 account_updates: &[AccountUpdate],
2791 mut storage_tries: StorageTries,
2792 ) -> Result<(StorageTries, AccountUpdatesList), StoreError> {
2793 let mut ret_storage_updates = Vec::new();
2794
2795 let mut code_updates = Vec::new();
2796
2797 let state_root = state_trie.hash_no_commit(&NativeCrypto);
2798
2799 for update in account_updates.iter() {
2800 let hashed_address = hash_address(&update.address);
2801
2802 if update.removed {
2803 state_trie.remove(&hashed_address)?;
2805
2806 continue;
2807 }
2808
2809 let mut account_state = match state_trie.get(&hashed_address)? {
2812 Some(encoded_state) => AccountState::decode(&encoded_state)?,
2813 None => AccountState::default(),
2814 };
2815
2816 if update.removed_storage {
2817 account_state.storage_root = EMPTY_TRIE_HASH;
2818 }
2819
2820 if let Some(info) = &update.info {
2821 account_state.nonce = info.nonce;
2822
2823 account_state.balance = info.balance;
2824
2825 account_state.code_hash = info.code_hash;
2826
2827 if let Some(code) = &update.code {
2829 code_updates.push((info.code_hash, code.clone()));
2830 }
2831 }
2832
2833 if !update.added_storage.is_empty() {
2835 let (_witness, storage_trie) = match storage_tries.entry(update.address) {
2836 Entry::Occupied(value) => value.into_mut(),
2837 Entry::Vacant(vacant) => {
2838 let trie = self.open_storage_trie(
2839 H256::from_slice(&hashed_address),
2840 state_root,
2841 account_state.storage_root,
2842 )?;
2843 vacant.insert(TrieLogger::open_trie(trie))
2844 }
2845 };
2846
2847 for (storage_key, storage_value) in &update.added_storage {
2848 let hashed_key = hash_key(storage_key);
2849
2850 if storage_value.is_zero() {
2851 storage_trie.remove(&hashed_key)?;
2852 } else {
2853 storage_trie.insert(hashed_key, storage_value.encode_to_vec())?;
2854 }
2855 }
2856
2857 let (storage_hash, storage_updates) =
2858 storage_trie.collect_changes_since_last_hash(&NativeCrypto);
2859
2860 account_state.storage_root = storage_hash;
2861
2862 ret_storage_updates.push((H256::from_slice(&hashed_address), storage_updates));
2863 }
2864
2865 state_trie.insert(hashed_address, account_state.encode_to_vec())?;
2866 }
2867
2868 let (state_trie_hash, state_updates) =
2869 state_trie.collect_changes_since_last_hash(&NativeCrypto);
2870
2871 let account_updates_list = AccountUpdatesList {
2872 state_trie_hash,
2873 state_updates,
2874 storage_updates: ret_storage_updates,
2875 code_updates,
2876 };
2877
2878 Ok((storage_tries, account_updates_list))
2879 }
2880
2881 pub async fn setup_genesis_state_trie(
2883 &self,
2884 genesis_accounts: BTreeMap<Address, GenesisAccount>,
2885 ) -> Result<H256, StoreError> {
2886 let mut storage_trie_nodes = vec![];
2887 let mut genesis_state_trie = self.open_direct_state_trie(EMPTY_TRIE_HASH)?;
2888 for (address, account) in genesis_accounts {
2889 let hashed_address = hash_address(&address);
2890 let h256_hashed_address = H256::from_slice(&hashed_address);
2891
2892 let code = Code::from_bytecode(account.code, &NativeCrypto);
2894 let code_hash = code.hash;
2895 self.add_account_code(code).await?;
2896
2897 let mut storage_trie =
2899 self.open_direct_storage_trie(h256_hashed_address, EMPTY_TRIE_HASH)?;
2900 for (storage_key, storage_value) in account.storage {
2901 if !storage_value.is_zero() {
2902 let hashed_key = hash_key(&H256(storage_key.to_big_endian()));
2903 storage_trie.insert(hashed_key, storage_value.encode_to_vec())?;
2904 }
2905 }
2906
2907 let (storage_root, storage_nodes) =
2908 storage_trie.collect_changes_since_last_hash(&NativeCrypto);
2909
2910 storage_trie_nodes.extend(
2911 storage_nodes
2912 .into_iter()
2913 .map(|(path, n)| (apply_prefix(Some(h256_hashed_address), path).into_vec(), n)),
2914 );
2915
2916 let account_state = AccountState {
2918 nonce: account.nonce,
2919 balance: account.balance,
2920 storage_root,
2921 code_hash,
2922 };
2923 genesis_state_trie.insert(hashed_address, account_state.encode_to_vec())?;
2924 }
2925
2926 let (state_root, account_trie_nodes) =
2927 genesis_state_trie.collect_changes_since_last_hash(&NativeCrypto);
2928 let account_trie_nodes = account_trie_nodes
2929 .into_iter()
2930 .map(|(path, n)| (apply_prefix(None, path).into_vec(), n))
2931 .collect::<Vec<_>>();
2932
2933 let mut tx = self.backend.begin_write()?;
2934 tx.put_batch(ACCOUNT_TRIE_NODES, account_trie_nodes)?;
2935 tx.put_batch(STORAGE_TRIE_NODES, storage_trie_nodes)?;
2936 tx.commit()?;
2937
2938 Ok(state_root)
2939 }
2940
2941 fn make_witness_key(block_number: u64, block_hash: &BlockHash) -> Vec<u8> {
2943 let mut composite_key = Vec::with_capacity(8 + 32);
2944 composite_key.extend_from_slice(&block_number.to_be_bytes());
2945 composite_key.extend_from_slice(block_hash.as_bytes());
2946 composite_key
2947 }
2948
2949 pub fn store_witness(
2955 &self,
2956 block_hash: BlockHash,
2957 block_number: u64,
2958 witness: ExecutionWitness,
2959 ) -> Result<(), StoreError> {
2960 let rpc_witness = RpcExecutionWitness::try_from(witness)?;
2962 let key = Self::make_witness_key(block_number, &block_hash);
2963 let value = serde_json::to_vec(&rpc_witness)?;
2964 self.write(EXECUTION_WITNESSES, key, value)?;
2965 self.cleanup_old_witnesses(block_number)
2967 }
2968
2969 fn cleanup_old_witnesses(&self, latest_block_number: u64) -> Result<(), StoreError> {
2970 if latest_block_number <= MAX_WITNESSES {
2972 return Ok(());
2973 }
2974
2975 let threshold = latest_block_number - MAX_WITNESSES;
2976
2977 if let Some(oldest_block_number) = self.get_oldest_witness_number()? {
2978 let prefix = oldest_block_number.to_be_bytes();
2979 let mut to_delete = Vec::new();
2980
2981 {
2982 let read_txn = self.backend.begin_read()?;
2983 let iter = read_txn.prefix_iterator(EXECUTION_WITNESSES, &prefix)?;
2984
2985 for item in iter {
2987 let (key, _value) = item?;
2988 let mut block_number_bytes = [0u8; 8];
2989 block_number_bytes.copy_from_slice(&key[0..8]);
2990 let block_number = u64::from_be_bytes(block_number_bytes);
2991 if block_number > threshold {
2992 break;
2993 }
2994 to_delete.push(key.to_vec());
2995 }
2996 }
2997
2998 for key in to_delete {
2999 self.delete(EXECUTION_WITNESSES, key)?;
3000 }
3001 };
3002
3003 self.update_oldest_witness_number(threshold + 1)?;
3004
3005 Ok(())
3006 }
3007
3008 fn update_oldest_witness_number(&self, oldest_block_number: u64) -> Result<(), StoreError> {
3009 self.write(
3010 MISC_VALUES,
3011 b"oldest_witness_block_number".to_vec(),
3012 oldest_block_number.to_le_bytes().to_vec(),
3013 )?;
3014 Ok(())
3015 }
3016
3017 fn get_oldest_witness_number(&self) -> Result<Option<u64>, StoreError> {
3018 let Some(value) = self.read(MISC_VALUES, b"oldest_witness_block_number".to_vec())? else {
3019 return Ok(None);
3020 };
3021
3022 let array: [u8; 8] = value.as_slice().try_into().map_err(|_| {
3023 StoreError::Custom("Invalid oldest witness block number bytes".to_string())
3024 })?;
3025 Ok(Some(u64::from_le_bytes(array)))
3026 }
3027
3028 pub fn get_witness_json_bytes(
3034 &self,
3035 block_number: u64,
3036 block_hash: BlockHash,
3037 ) -> Result<Option<Vec<u8>>, StoreError> {
3038 let key = Self::make_witness_key(block_number, &block_hash);
3039 self.read(EXECUTION_WITNESSES, key)
3040 }
3041
3042 pub fn get_witness_by_number_and_hash(
3047 &self,
3048 block_number: u64,
3049 block_hash: BlockHash,
3050 ) -> Result<Option<RpcExecutionWitness>, StoreError> {
3051 let key = Self::make_witness_key(block_number, &block_hash);
3052 match self.read(EXECUTION_WITNESSES, key)? {
3053 Some(value) => {
3054 let witness: RpcExecutionWitness = serde_json::from_slice(&value)?;
3055 Ok(Some(witness))
3056 }
3057 None => Ok(None),
3058 }
3059 }
3060
3061 pub fn store_block_access_list(
3063 &self,
3064 block_hash: BlockHash,
3065 bal: &BlockAccessList,
3066 ) -> Result<(), StoreError> {
3067 let mut value = vec![];
3068 bal.encode(&mut value);
3069 self.store_block_access_list_encoded(block_hash, value)
3070 }
3071
3072 pub fn store_block_access_list_encoded(
3075 &self,
3076 block_hash: BlockHash,
3077 encoded: Vec<u8>,
3078 ) -> Result<(), StoreError> {
3079 self.write(BLOCK_ACCESS_LISTS, block_hash.as_bytes().to_vec(), encoded)
3080 }
3081
3082 pub fn get_block_access_list(
3084 &self,
3085 block_hash: BlockHash,
3086 ) -> Result<Option<BlockAccessList>, StoreError> {
3087 let key = block_hash.as_bytes().to_vec();
3088 match self.read(BLOCK_ACCESS_LISTS, key)? {
3089 Some(value) => {
3090 let bal = BlockAccessList::decode(&value)
3091 .map_err(|e| StoreError::Custom(format!("Failed to decode BAL: {e}")))?;
3092 Ok(Some(bal))
3093 }
3094 None => Ok(None),
3095 }
3096 }
3097
3098 pub fn iter_block_access_lists_by_hashes(
3103 &self,
3104 hashes: &[BlockHash],
3105 ) -> Result<Vec<Option<BlockAccessList>>, StoreError> {
3106 let mut out = Vec::with_capacity(hashes.len());
3107 for hash in hashes {
3108 out.push(self.get_block_access_list(*hash)?);
3109 }
3110 Ok(out)
3111 }
3112
3113 pub async fn add_initial_state(&mut self, genesis: Genesis) -> Result<(), StoreError> {
3114 self.add_initial_state_inner(genesis, false).await
3115 }
3116
3117 pub async fn add_initial_state_skip_validation(
3128 &mut self,
3129 genesis: Genesis,
3130 ) -> Result<(), StoreError> {
3131 self.add_initial_state_inner(genesis, true).await
3132 }
3133
3134 async fn add_initial_state_inner(
3135 &mut self,
3136 genesis: Genesis,
3137 skip_genesis_validation: bool,
3138 ) -> Result<(), StoreError> {
3139 debug!("Storing initial state from genesis");
3140
3141 let genesis_block = genesis.get_block();
3143 let genesis_block_number = genesis_block.header.number;
3144
3145 let genesis_hash = genesis_block.hash();
3146
3147 let stored_genesis_header = self.load_block_header(genesis_block_number)?;
3148
3149 self.set_chain_config(&genesis.config).await?;
3156
3157 if let Some(latest) = self.load_latest_block_number().await? {
3162 self.anchor_to_durable_head(latest).await?;
3163 }
3164
3165 match stored_genesis_header {
3166 Some(header) if skip_genesis_validation => {
3167 info!(
3168 stored_genesis = %header.hash(),
3169 "Skipping genesis state validation; trusting the genesis header and state already stored in the datadir"
3170 );
3171 return Ok(());
3172 }
3173 Some(header) if header.hash() == genesis_hash => {
3174 info!("Received genesis file matching a previously stored one, nothing to do");
3175 return Ok(());
3176 }
3177 Some(_) => {
3178 error!(
3179 "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."
3180 );
3181 return Err(StoreError::IncompatibleChainConfig);
3182 }
3183 None => {
3184 self.add_block_header(genesis_hash, genesis_block.header.clone())
3185 .await?
3186 }
3187 }
3188 let genesis_state_root = self.setup_genesis_state_trie(genesis.alloc).await?;
3191 debug_assert_eq!(genesis_state_root, genesis_block.header.state_root);
3192
3193 info!(hash = %genesis_hash, "Storing genesis block");
3195
3196 self.add_block(genesis_block).await?;
3197 self.update_earliest_block_number(genesis_block_number)
3198 .await?;
3199 self.forkchoice_update(vec![], genesis_block_number, genesis_hash, None, None)
3200 .await?;
3201 Ok(())
3202 }
3203
3204 pub async fn load_initial_state(&self) -> Result<(), StoreError> {
3205 info!("Loading initial state from DB");
3206 let Some(latest) = self.load_latest_block_number().await? else {
3207 return Err(StoreError::MissingLatestBlockNumber);
3208 };
3209 self.anchor_to_durable_head(latest).await?;
3213 Ok(())
3214 }
3215
3216 pub fn get_storage_at(
3217 &self,
3218 block_number: BlockNumber,
3219 address: Address,
3220 storage_key: H256,
3221 ) -> Result<Option<U256>, StoreError> {
3222 match self.get_block_header(block_number)? {
3223 Some(header) => self.get_storage_at_root(header.state_root, address, storage_key),
3224 None => Ok(None),
3225 }
3226 }
3227
3228 pub fn get_storage_at_root(
3229 &self,
3230 state_root: H256,
3231 address: Address,
3232 storage_key: H256,
3233 ) -> Result<Option<U256>, StoreError> {
3234 let account_hash = hash_address_fixed(&address);
3235
3236 let read_view = self.backend.begin_read()?;
3238 let cache = self.gated_snapshot(state_root)?;
3239 let last_written = self.last_written()?;
3240 let use_fkv = Self::flatkeyvalue_computed_with_last_written(account_hash, &last_written)
3247 && !cache.overlay_serves(state_root);
3248
3249 let storage_root = if use_fkv {
3250 EMPTY_TRIE_HASH
3252 } else {
3253 let state_trie = self.open_state_trie_shared(
3254 state_root,
3255 read_view.clone(),
3256 cache.clone(),
3257 last_written.clone(),
3258 )?;
3259 let Some(encoded_account) = state_trie.get(account_hash.as_bytes())? else {
3260 return Ok(None);
3261 };
3262 let account = AccountState::decode(&encoded_account)?;
3263 account.storage_root
3264 };
3265 let storage_trie = self.open_storage_trie_shared(
3266 account_hash,
3267 state_root,
3268 storage_root,
3269 read_view,
3270 cache,
3271 last_written,
3272 )?;
3273
3274 let hashed_key = hash_key_fixed(&storage_key);
3275 storage_trie
3276 .get(&hashed_key)?
3277 .map(|rlp| U256::decode(&rlp).map_err(StoreError::RLPDecode))
3278 .transpose()
3279 }
3280
3281 pub fn get_storage_at_root_with_known_storage_root(
3286 &self,
3287 state_root: H256,
3288 account_hash: H256,
3289 storage_root: H256,
3290 storage_key: H256,
3291 ) -> Result<Option<U256>, StoreError> {
3292 let read_view = self.backend.begin_read()?;
3293 let cache = self.gated_snapshot(state_root)?;
3294 let last_written = self.last_written()?;
3295 let storage_root =
3302 if Self::flatkeyvalue_computed_with_last_written(account_hash, &last_written)
3303 && !cache.overlay_serves(state_root)
3304 {
3305 EMPTY_TRIE_HASH
3306 } else {
3307 storage_root
3308 };
3309 let storage_trie = self.open_storage_trie_shared(
3310 account_hash,
3311 state_root,
3312 storage_root,
3313 read_view,
3314 cache,
3315 last_written,
3316 )?;
3317
3318 let hashed_key = hash_key_fixed(&storage_key);
3319 storage_trie
3320 .get(&hashed_key)?
3321 .map(|rlp| U256::decode(&rlp).map_err(StoreError::RLPDecode))
3322 .transpose()
3323 }
3324
3325 pub fn get_chain_config(&self) -> ChainConfig {
3326 self.chain_config
3327 }
3328
3329 pub async fn get_latest_canonical_block_hash(&self) -> Result<Option<BlockHash>, StoreError> {
3330 Ok(Some(self.latest_block_header.get().hash()))
3331 }
3332
3333 pub async fn forkchoice_update(
3338 &self,
3339 new_canonical_blocks: Vec<(BlockNumber, BlockHash)>,
3340 head_number: BlockNumber,
3341 head_hash: BlockHash,
3342 safe: Option<BlockNumber>,
3343 finalized: Option<BlockNumber>,
3344 ) -> Result<(), StoreError> {
3345 let _guard = self.fcu_lock.lock().await;
3351
3352 let previous_head = self.latest_block_header.get();
3357 let new_head = self
3358 .get_block_header_by_hash(head_hash)?
3359 .ok_or_else(|| StoreError::MissingLatestBlockNumber)?;
3360 self.latest_block_header.update(new_head);
3361 if let Err(err) = self
3362 .forkchoice_update_inner(
3363 new_canonical_blocks,
3364 head_number,
3365 head_hash,
3366 safe,
3367 finalized,
3368 )
3369 .await
3370 {
3371 self.latest_block_header.update((*previous_head).clone());
3372 return Err(err);
3373 }
3374
3375 if let Some(root) = self.compute_safe_commit_root(head_number)? {
3381 if self.set_safe_commit_root(root)? {
3386 let tx = self.persist_tx.clone();
3387 let _ = tokio::task::spawn_blocking(move || tx.send(PersistMessage::Commit(root)))
3388 .await;
3389 }
3390 }
3391
3392 Ok(())
3393 }
3394
3395 pub(crate) fn set_safe_commit_root(&self, root: H256) -> Result<bool, StoreError> {
3403 let mut guard = self
3404 .safe_commit_root
3405 .write()
3406 .map_err(|_| StoreError::LockError)?;
3407 let changed = *guard != root;
3408 *guard = root;
3409 Ok(changed)
3410 }
3411
3412 pub(crate) fn compute_safe_commit_root(
3421 &self,
3422 head: BlockNumber,
3423 ) -> Result<Option<H256>, StoreError> {
3424 let commit_threshold = self
3425 .trie_cache
3426 .read()
3427 .map_err(|_| StoreError::LockError)?
3428 .commit_threshold;
3429 let Some(target) = head.checked_sub(commit_threshold as u64) else {
3430 return Ok(None);
3431 };
3432 let Some(hash) = self.get_canonical_block_hash_sync(target)? else {
3433 return Ok(None);
3434 };
3435 let Some(header) = self.get_block_header_by_hash(hash)? else {
3436 return Ok(None);
3437 };
3438 Ok(Some(header.state_root))
3439 }
3440
3441 pub fn state_trie(&self, block_hash: BlockHash) -> Result<Option<Trie>, StoreError> {
3443 let Some(header) = self.get_block_header_by_hash(block_hash)? else {
3444 return Ok(None);
3445 };
3446 Ok(Some(self.open_state_trie(header.state_root)?))
3447 }
3448
3449 pub fn storage_trie(
3451 &self,
3452 block_hash: BlockHash,
3453 address: Address,
3454 ) -> Result<Option<Trie>, StoreError> {
3455 let Some(header) = self.get_block_header_by_hash(block_hash)? else {
3456 return Ok(None);
3457 };
3458 let Some(state_trie) = self.state_trie(block_hash)? else {
3460 return Ok(None);
3461 };
3462 let hashed_address = hash_address_fixed(&address);
3463 let Some(encoded_account) = state_trie.get(hashed_address.as_bytes())? else {
3464 return Ok(None);
3465 };
3466 let account = AccountState::decode(&encoded_account)?;
3467 let storage_root = account.storage_root;
3469 Ok(Some(self.open_storage_trie(
3470 hashed_address,
3471 header.state_root,
3472 storage_root,
3473 )?))
3474 }
3475
3476 pub async fn get_account_state(
3477 &self,
3478 block_number: BlockNumber,
3479 address: Address,
3480 ) -> Result<Option<AccountState>, StoreError> {
3481 let Some(block_hash) = self.get_canonical_block_hash(block_number).await? else {
3482 return Ok(None);
3483 };
3484 let Some(state_trie) = self.state_trie(block_hash)? else {
3485 return Ok(None);
3486 };
3487 self.get_account_state_from_trie(&state_trie, address)
3488 }
3489
3490 pub fn get_account_state_by_root(
3491 &self,
3492 state_root: H256,
3493 address: Address,
3494 ) -> Result<Option<AccountState>, StoreError> {
3495 let state_trie = self.open_state_trie(state_root)?;
3496 self.get_account_state_from_trie(&state_trie, address)
3497 }
3498
3499 pub fn get_account_state_from_trie(
3500 &self,
3501 state_trie: &Trie,
3502 address: Address,
3503 ) -> Result<Option<AccountState>, StoreError> {
3504 let hashed_address = hash_address_fixed(&address);
3505 let Some(encoded_state) = state_trie.get(hashed_address.as_bytes())? else {
3506 return Ok(None);
3507 };
3508 Ok(Some(AccountState::decode(&encoded_state)?))
3509 }
3510
3511 pub fn get_account_states_batch_by_root(
3520 &self,
3521 state_root: H256,
3522 addresses: &[Address],
3523 ) -> Result<Vec<Option<AccountState>>, StoreError> {
3524 if addresses.is_empty() {
3525 return Ok(Vec::new());
3526 }
3527
3528 let last_written = self.last_written()?;
3529 let trie_cache = self
3530 .trie_cache
3531 .read()
3532 .map_err(|_| StoreError::LockError)?
3533 .clone();
3534
3535 let mut results: Vec<Option<AccountState>> = vec![None; addresses.len()];
3536 let leaf_paths: Vec<Vec<u8>> = addresses
3538 .iter()
3539 .map(|addr| {
3540 let hashed = hash_address_fixed(addr);
3541 Nibbles::from_bytes(hashed.as_bytes()).into_vec()
3542 })
3543 .collect();
3544
3545 let mut fkv_indices: Vec<usize> = Vec::new();
3546 let mut trie_indices: Vec<usize> = Vec::new();
3547
3548 let overlay_active = trie_cache.overlay_serves(state_root);
3564 let fkv_cursor: &[u8] = last_written.as_slice();
3565 for (i, path) in leaf_paths.iter().enumerate() {
3566 if let Some(value) = trie_cache.get(state_root, path.as_slice()) {
3567 if !value.is_empty() {
3568 results[i] = Some(AccountState::decode(&value)?);
3569 }
3570 continue;
3571 }
3572 if !overlay_active && fkv_cursor >= path.as_slice() {
3573 fkv_indices.push(i);
3574 } else {
3575 trie_indices.push(i);
3576 }
3577 }
3578
3579 if !fkv_indices.is_empty() {
3580 fkv_indices.sort_unstable_by(|&a, &b| leaf_paths[a].cmp(&leaf_paths[b]));
3587 let read_view = self.backend.begin_read()?;
3588 let keys: Vec<&[u8]> = fkv_indices
3589 .iter()
3590 .map(|&i| leaf_paths[i].as_slice())
3591 .collect();
3592 let shards = shard_count(keys.len());
3593 let raw: Vec<Result<Option<Vec<u8>>, StoreError>> = if shards <= 1 {
3594 read_view.multi_get(ACCOUNT_FLATKEYVALUE, &keys)
3595 } else {
3596 let chunk = keys.len().div_ceil(shards);
3597 let rv = read_view.as_ref();
3598 std::thread::scope(|scope| {
3599 let handles: Vec<_> = keys
3600 .chunks(chunk)
3601 .map(|ck| scope.spawn(move || rv.multi_get(ACCOUNT_FLATKEYVALUE, ck)))
3602 .collect();
3603 handles
3604 .into_iter()
3605 .flat_map(|h| {
3611 h.join().unwrap_or_else(|_| {
3612 vec![Err(StoreError::Custom(
3613 "account prefetch shard panicked".into(),
3614 ))]
3615 })
3616 })
3617 .collect()
3618 })
3619 };
3620 for (slot, res) in fkv_indices.iter().zip(raw.into_iter()) {
3621 let Some(encoded) = res? else { continue };
3622 if encoded.is_empty() {
3623 continue;
3624 }
3625 results[*slot] = Some(AccountState::decode(&encoded)?);
3626 }
3627 }
3628
3629 if !trie_indices.is_empty() {
3630 let state_trie = self.open_state_trie(state_root)?;
3636 let fetched: Result<Vec<(usize, Option<AccountState>)>, StoreError> = trie_indices
3637 .par_iter()
3638 .map(|&i| {
3639 self.get_account_state_from_trie(&state_trie, addresses[i])
3640 .map(|s| (i, s))
3641 })
3642 .collect();
3643 for (i, s) in fetched? {
3644 results[i] = s;
3645 }
3646 }
3647
3648 Ok(results)
3649 }
3650
3651 pub fn get_storage_values_batch_by_root(
3671 &self,
3672 state_root: H256,
3673 slots: &[(H256, H256, H256)],
3674 ) -> Result<Vec<Option<U256>>, StoreError> {
3675 if slots.is_empty() {
3676 return Ok(Vec::new());
3677 }
3678
3679 let last_written = self.last_written()?;
3680 let trie_cache = self
3681 .trie_cache
3682 .read()
3683 .map_err(|_| StoreError::LockError)?
3684 .clone();
3685
3686 let mut results: Vec<Option<U256>> = vec![None; slots.len()];
3687 let leaf_paths: Vec<Vec<u8>> = slots
3693 .iter()
3694 .map(|(account_hash, _storage_root, slot)| {
3695 let hashed_slot = hash_key_fixed(slot);
3696 apply_prefix(Some(*account_hash), Nibbles::from_bytes(&hashed_slot)).into_vec()
3697 })
3698 .collect();
3699
3700 let mut fkv_indices: Vec<usize> = Vec::new();
3701 let mut trie_indices: Vec<usize> = Vec::new();
3702
3703 let overlay_active = trie_cache.overlay_serves(state_root);
3717 let fkv_cursor: &[u8] = last_written.as_slice();
3718 for (i, path) in leaf_paths.iter().enumerate() {
3719 if let Some(value) = trie_cache.get(state_root, path.as_slice()) {
3724 if !value.is_empty() {
3725 results[i] = Some(U256::decode(&value).map_err(StoreError::RLPDecode)?);
3726 }
3727 continue;
3728 }
3729 if !overlay_active && fkv_cursor >= path.as_slice() {
3730 fkv_indices.push(i);
3731 } else {
3732 trie_indices.push(i);
3733 }
3734 }
3735
3736 if !fkv_indices.is_empty() {
3737 fkv_indices.sort_unstable_by(|&a, &b| leaf_paths[a].cmp(&leaf_paths[b]));
3742 let read_view = self.backend.begin_read()?;
3743 let keys: Vec<&[u8]> = fkv_indices
3744 .iter()
3745 .map(|&i| leaf_paths[i].as_slice())
3746 .collect();
3747 let shards = shard_count(keys.len());
3760 let raw: Vec<Result<Option<Vec<u8>>, StoreError>> = if shards <= 1 {
3761 read_view.multi_get(STORAGE_FLATKEYVALUE, &keys)
3762 } else {
3763 let chunk = keys.len().div_ceil(shards);
3764 let rv = read_view.as_ref();
3765 std::thread::scope(|scope| {
3766 let handles: Vec<_> = keys
3767 .chunks(chunk)
3768 .map(|ck| scope.spawn(move || rv.multi_get(STORAGE_FLATKEYVALUE, ck)))
3769 .collect();
3770 handles
3771 .into_iter()
3772 .flat_map(|h| {
3778 h.join().unwrap_or_else(|_| {
3779 vec![Err(StoreError::Custom(
3780 "storage prefetch shard panicked".into(),
3781 ))]
3782 })
3783 })
3784 .collect()
3785 })
3786 };
3787 for (slot_idx, res) in fkv_indices.iter().zip(raw.into_iter()) {
3788 let Some(encoded) = res? else { continue };
3789 if encoded.is_empty() {
3790 continue;
3791 }
3792 results[*slot_idx] = Some(U256::decode(&encoded).map_err(StoreError::RLPDecode)?);
3793 }
3794 }
3795
3796 if !trie_indices.is_empty() {
3797 let fetched: Result<Vec<(usize, Option<U256>)>, StoreError> = trie_indices
3805 .par_iter()
3806 .map(|&i| {
3807 let (account_hash, storage_root, slot) = slots[i];
3808 self.get_storage_at_root_with_known_storage_root(
3809 state_root,
3810 account_hash,
3811 storage_root,
3812 slot,
3813 )
3814 .map(|v| (i, v))
3815 })
3816 .collect();
3817 for (i, v) in fetched? {
3818 results[i] = v;
3819 }
3820 }
3821
3822 Ok(results)
3823 }
3824
3825 pub fn prefetch_trie_nodes(
3852 &self,
3853 storage_slots: &[(Address, H256)],
3854 accounts: &[Address],
3855 ) -> Result<usize, StoreError> {
3856 const MAX_PREFETCH_DEPTH: usize = 8;
3861
3862 let mut storage_keys: Vec<Vec<u8>> =
3865 Vec::with_capacity(storage_slots.len() * (MAX_PREFETCH_DEPTH + 1));
3866 for (address, slot) in storage_slots {
3867 let hashed_address = hash_address_fixed(address);
3868 let hashed_slot = hash_key_fixed(slot);
3869 let slot_nibbles = Nibbles::from_bytes(&hashed_slot);
3870 let max_d = MAX_PREFETCH_DEPTH.min(slot_nibbles.len());
3871 for d in 0..=max_d {
3872 let prefix = apply_prefix(Some(hashed_address), slot_nibbles.slice(0, d));
3873 storage_keys.push(prefix.into_vec());
3874 }
3875 }
3876
3877 let mut account_keys: Vec<Vec<u8>> =
3879 Vec::with_capacity(accounts.len() * (MAX_PREFETCH_DEPTH + 1));
3880 for address in accounts {
3881 let hashed_address = hash_address_fixed(address);
3882 let addr_nibbles = Nibbles::from_bytes(hashed_address.as_bytes());
3883 let max_d = MAX_PREFETCH_DEPTH.min(addr_nibbles.len());
3884 for d in 0..=max_d {
3885 account_keys.push(addr_nibbles.slice(0, d).into_vec());
3886 }
3887 }
3888
3889 let hits = self.warm_trie_node_keys(STORAGE_TRIE_NODES, storage_keys)?
3890 + self.warm_trie_node_keys(ACCOUNT_TRIE_NODES, account_keys)?;
3891 Ok(hits)
3892 }
3893
3894 fn warm_trie_node_keys(
3901 &self,
3902 table: &'static str,
3903 mut keys: Vec<Vec<u8>>,
3904 ) -> Result<usize, StoreError> {
3905 if keys.is_empty() {
3906 return Ok(0);
3907 }
3908 keys.sort_unstable();
3909 keys.dedup();
3910
3911 let shards = shard_count(keys.len());
3912 let read_view = self.backend.begin_read()?;
3913 let count_hits = |res: Vec<Result<Option<Vec<u8>>, StoreError>>| {
3914 res.into_iter().filter(|r| matches!(r, Ok(Some(_)))).count()
3915 };
3916 if shards <= 1 {
3917 let refs: Vec<&[u8]> = keys.iter().map(|k| k.as_slice()).collect();
3918 return Ok(count_hits(read_view.multi_get(table, &refs)));
3919 }
3920 let chunk = keys.len().div_ceil(shards);
3921 let rv = read_view.as_ref();
3922 let hits = std::thread::scope(|scope| {
3923 let handles: Vec<_> = keys
3924 .chunks(chunk)
3925 .map(|ck| {
3926 scope.spawn(move || {
3927 let refs: Vec<&[u8]> = ck.iter().map(|k| k.as_slice()).collect();
3928 count_hits(rv.multi_get(table, &refs))
3930 })
3931 })
3932 .collect();
3933 handles
3934 .into_iter()
3935 .map(|h| h.join().unwrap_or(0))
3940 .sum::<usize>()
3941 });
3942 Ok(hits)
3943 }
3944
3945 pub async fn get_account_proof(
3950 &self,
3951 state_root: H256,
3952 address: Address,
3953 storage_keys: &[H256],
3954 ) -> Result<Option<AccountProof>, StoreError> {
3955 let state_trie = self.open_state_trie(state_root)?;
3960 let address_path = hash_address_fixed(&address);
3961 let proof = state_trie.get_proof(address_path.as_bytes())?;
3962 let account_opt = state_trie
3963 .get(address_path.as_bytes())?
3964 .map(|encoded_state| AccountState::decode(&encoded_state))
3965 .transpose()?;
3966
3967 let mut storage_proof = Vec::with_capacity(storage_keys.len());
3968
3969 if let Some(account) = &account_opt {
3970 let storage_trie =
3971 self.open_storage_trie(address_path, state_root, account.storage_root)?;
3972
3973 for key in storage_keys {
3974 let hashed_key = hash_key(key);
3975 let proof = storage_trie.get_proof(&hashed_key)?;
3976 let value = storage_trie
3977 .get(&hashed_key)?
3978 .map(|rlp| U256::decode(&rlp).map_err(StoreError::RLPDecode))
3979 .transpose()?
3980 .unwrap_or_default();
3981
3982 let slot_proof = StorageSlotProof {
3983 proof,
3984 key: *key,
3985 value,
3986 };
3987 storage_proof.push(slot_proof);
3988 }
3989 } else {
3990 storage_proof.extend(storage_keys.iter().map(|key| StorageSlotProof {
3991 proof: Vec::new(),
3992 key: *key,
3993 value: U256::zero(),
3994 }));
3995 }
3996 let account = account_opt.unwrap_or_default();
3997 let account_proof = AccountProof {
3998 proof,
3999 account,
4000 storage_proof,
4001 };
4002 Ok(Some(account_proof))
4003 }
4004
4005 pub fn iter_accounts_from(
4008 &self,
4009 state_root: H256,
4010 starting_address: H256,
4011 ) -> Result<impl Iterator<Item = (H256, AccountState)>, StoreError> {
4012 let mut iter = self.open_locked_state_trie(state_root)?.into_iter();
4013 iter.advance(starting_address.0.to_vec())?;
4014 Ok(iter.content().map_while(|(path, value)| {
4015 Some((H256::from_slice(&path), AccountState::decode(&value).ok()?))
4016 }))
4017 }
4018
4019 pub fn iter_accounts(
4022 &self,
4023 state_root: H256,
4024 ) -> Result<impl Iterator<Item = (H256, AccountState)>, StoreError> {
4025 self.iter_accounts_from(state_root, H256::zero())
4026 }
4027
4028 pub fn iter_storage_from(
4031 &self,
4032 state_root: H256,
4033 hashed_address: H256,
4034 starting_slot: H256,
4035 ) -> Result<Option<impl Iterator<Item = (H256, U256)>>, StoreError> {
4036 let state_trie = self.open_locked_state_trie(state_root)?;
4037 let Some(account_rlp) = state_trie.get(hashed_address.as_bytes())? else {
4038 return Ok(None);
4039 };
4040 let storage_root = AccountState::decode(&account_rlp)?.storage_root;
4041 let mut iter = self
4042 .open_locked_storage_trie(hashed_address, state_root, storage_root)?
4043 .into_iter();
4044 iter.advance(starting_slot.0.to_vec())?;
4045 Ok(Some(iter.content().map_while(|(path, value)| {
4046 Some((H256::from_slice(&path), U256::decode(&value).ok()?))
4047 })))
4048 }
4049
4050 pub fn iter_storage(
4053 &self,
4054 state_root: H256,
4055 hashed_address: H256,
4056 ) -> Result<Option<impl Iterator<Item = (H256, U256)>>, StoreError> {
4057 self.iter_storage_from(state_root, hashed_address, H256::zero())
4058 }
4059
4060 pub fn get_account_range_proof(
4061 &self,
4062 state_root: H256,
4063 starting_hash: H256,
4064 last_hash: Option<H256>,
4065 ) -> Result<Vec<Vec<u8>>, StoreError> {
4066 let state_trie = self.open_state_trie(state_root)?;
4067 let mut proof = state_trie.get_proof(starting_hash.as_bytes())?;
4068 if let Some(last_hash) = last_hash {
4069 proof.extend_from_slice(&state_trie.get_proof(last_hash.as_bytes())?);
4070 }
4071 Ok(proof)
4072 }
4073
4074 pub fn get_storage_range_proof(
4075 &self,
4076 state_root: H256,
4077 hashed_address: H256,
4078 starting_hash: H256,
4079 last_hash: Option<H256>,
4080 ) -> Result<Option<Vec<Vec<u8>>>, StoreError> {
4081 let state_trie = self.open_state_trie(state_root)?;
4082 let Some(account_rlp) = state_trie.get(hashed_address.as_bytes())? else {
4083 return Ok(None);
4084 };
4085 let storage_root = AccountState::decode(&account_rlp)?.storage_root;
4086 let storage_trie = self.open_storage_trie(hashed_address, state_root, storage_root)?;
4087 let mut proof = storage_trie.get_proof(starting_hash.as_bytes())?;
4088 if let Some(last_hash) = last_hash {
4089 proof.extend_from_slice(&storage_trie.get_proof(last_hash.as_bytes())?);
4090 }
4091 Ok(Some(proof))
4092 }
4093
4094 pub fn get_trie_nodes(
4101 &self,
4102 state_root: H256,
4103 paths: Vec<Vec<u8>>,
4104 byte_limit: u64,
4105 ) -> Result<Vec<Vec<u8>>, StoreError> {
4106 let Some(account_path) = paths.first() else {
4107 return Ok(vec![]);
4108 };
4109 let state_trie = self.open_state_trie(state_root)?;
4110 if paths.len() == 1 {
4112 let node = state_trie.get_node(account_path)?;
4114 return Ok(vec![node]);
4115 }
4116 let Some(account_state) = state_trie
4118 .get(account_path)?
4119 .map(|ref rlp| AccountState::decode(rlp))
4120 .transpose()?
4121 else {
4122 return Ok(vec![]);
4123 };
4124 let Ok(hashed_address) = account_path.clone().try_into().map(H256) else {
4126 return Ok(vec![]);
4127 };
4128 let storage_trie =
4129 self.open_storage_trie(hashed_address, state_root, account_state.storage_root)?;
4130 let mut nodes = vec![];
4132 let mut bytes_used = 0;
4133 for path in paths.iter().skip(1) {
4137 if bytes_used >= byte_limit {
4138 break;
4139 }
4140 let node = storage_trie.get_node(path)?;
4141 bytes_used += node.len() as u64;
4142 nodes.push(node);
4143 }
4144 Ok(nodes)
4145 }
4146
4147 fn gated_snapshot(&self, state_root: H256) -> Result<Arc<TrieLayerCache>, StoreError> {
4156 self.pending_trie_roots.wait_until_ready(state_root)?;
4157 Ok(self
4158 .trie_cache
4159 .read()
4160 .map_err(|_| StoreError::LockError)?
4161 .clone())
4162 }
4163
4164 pub fn open_state_trie(&self, state_root: H256) -> Result<Trie, StoreError> {
4168 let trie_db = TrieWrapper::new(
4169 state_root,
4170 self.gated_snapshot(state_root)?,
4171 Box::new(BackendTrieDB::new_for_accounts(
4172 self.backend.clone(),
4173 self.last_written()?,
4174 )?),
4175 None,
4176 );
4177 Ok(Trie::open(Box::new(trie_db), state_root))
4178 }
4179
4180 pub fn open_direct_state_trie(&self, state_root: H256) -> Result<Trie, StoreError> {
4184 Ok(Trie::open(
4185 Box::new(BackendTrieDB::new_for_accounts(
4186 self.backend.clone(),
4187 self.last_written()?,
4188 )?),
4189 state_root,
4190 ))
4191 }
4192
4193 pub fn open_locked_state_trie(&self, state_root: H256) -> Result<Trie, StoreError> {
4197 let trie_db = TrieWrapper::new(
4198 state_root,
4199 self.gated_snapshot(state_root)?,
4200 Box::new(state_trie_locked_backend(
4201 self.backend.as_ref(),
4202 self.last_written()?,
4203 )?),
4204 None,
4205 );
4206 Ok(Trie::open(Box::new(trie_db), state_root))
4207 }
4208
4209 pub fn open_storage_trie(
4212 &self,
4213 account_hash: H256,
4214 state_root: H256,
4215 storage_root: H256,
4216 ) -> Result<Trie, StoreError> {
4217 let trie_db = TrieWrapper::new(
4218 state_root,
4219 self.gated_snapshot(state_root)?,
4220 Box::new(BackendTrieDB::new_for_storages(
4221 self.backend.clone(),
4222 self.last_written()?,
4223 )?),
4224 Some(account_hash),
4225 );
4226 Ok(Trie::open(Box::new(trie_db), storage_root))
4227 }
4228
4229 fn open_state_trie_shared(
4233 &self,
4234 state_root: H256,
4235 read_view: Arc<dyn StorageReadView>,
4236 cache: Arc<TrieLayerCache>,
4237 last_written: Vec<u8>,
4238 ) -> Result<Trie, StoreError> {
4239 let trie_db = TrieWrapper::new(
4240 state_root,
4241 cache,
4242 Box::new(BackendTrieDB::new_for_accounts_with_view(
4243 self.backend.clone(),
4244 read_view,
4245 last_written,
4246 )?),
4247 None,
4248 );
4249 Ok(Trie::open(Box::new(trie_db), state_root))
4250 }
4251
4252 fn open_storage_trie_shared(
4254 &self,
4255 account_hash: H256,
4256 state_root: H256,
4257 storage_root: H256,
4258 read_view: Arc<dyn StorageReadView>,
4259 cache: Arc<TrieLayerCache>,
4260 last_written: Vec<u8>,
4261 ) -> Result<Trie, StoreError> {
4262 let trie_db = TrieWrapper::new(
4263 state_root,
4264 cache,
4265 Box::new(BackendTrieDB::new_for_storages_with_view(
4266 self.backend.clone(),
4267 read_view,
4268 last_written,
4269 )?),
4270 Some(account_hash),
4271 );
4272 Ok(Trie::open(Box::new(trie_db), storage_root))
4273 }
4274
4275 pub fn open_direct_storage_trie(
4278 &self,
4279 account_hash: H256,
4280 storage_root: H256,
4281 ) -> Result<Trie, StoreError> {
4282 Ok(Trie::open(
4283 Box::new(BackendTrieDB::new_for_account_storage(
4284 self.backend.clone(),
4285 account_hash,
4286 self.last_written()?,
4287 )?),
4288 storage_root,
4289 ))
4290 }
4291
4292 pub fn open_locked_storage_trie(
4295 &self,
4296 account_hash: H256,
4297 state_root: H256,
4298 storage_root: H256,
4299 ) -> Result<Trie, StoreError> {
4300 let trie_db = TrieWrapper::new(
4301 state_root,
4302 self.gated_snapshot(state_root)?,
4303 Box::new(state_trie_locked_backend(
4304 self.backend.as_ref(),
4305 self.last_written()?,
4306 )?),
4307 Some(account_hash),
4308 );
4309 Ok(Trie::open(Box::new(trie_db), storage_root))
4310 }
4311
4312 pub fn has_state_root(&self, state_root: H256) -> Result<bool, StoreError> {
4313 if state_root == EMPTY_TRIE_HASH {
4315 return Ok(true);
4316 }
4317 let trie = self.open_state_trie(state_root)?;
4318 let Some(root) = trie.db().get(Nibbles::default())? else {
4320 return Ok(false);
4321 };
4322 let root_hash = ethrex_trie::Node::decode(&root)?
4323 .compute_hash(&NativeCrypto)
4324 .finalize(&NativeCrypto);
4325 Ok(state_root == root_hash)
4326 }
4327
4328 pub fn is_state_in_layer_cache(&self, state_root: H256) -> Result<bool, StoreError> {
4337 let trie = self
4338 .trie_cache
4339 .read()
4340 .map_err(|_| StoreError::LockError)?
4341 .clone();
4342 Ok(trie.contains(state_root))
4343 }
4344
4345 pub fn highest_state_history_block_number(&self) -> Result<Option<BlockNumber>, StoreError> {
4351 let read = self.backend.begin_read()?;
4352 let Some(key) = read.last_key(STATE_HISTORY)? else {
4353 return Ok(None);
4354 };
4355 let arr = <[u8; 8]>::try_from(key.as_slice()).map_err(|_| {
4356 StoreError::Custom(format!(
4357 "STATE_HISTORY key has unexpected length: {}",
4358 key.len()
4359 ))
4360 })?;
4361 Ok(Some(BlockNumber::from_be_bytes(arr)))
4362 }
4363
4364 pub fn get_state_history_block_hash(
4371 &self,
4372 block_number: BlockNumber,
4373 ) -> Result<Option<BlockHash>, StoreError> {
4374 let read = self.backend.begin_read()?;
4375 let Some(bytes) = read.get(STATE_HISTORY, &block_number.to_be_bytes())? else {
4376 return Ok(None);
4377 };
4378 JournalEntry::decode_block_hash(&bytes)
4379 .map(Some)
4380 .map_err(|e| StoreError::Custom(format!("STATE_HISTORY entry {block_number}: {e}")))
4381 }
4382
4383 pub fn lowest_state_history_block_number(&self) -> Result<Option<BlockNumber>, StoreError> {
4388 let read = self.backend.begin_read()?;
4389 let Some(key) = read.first_key(STATE_HISTORY)? else {
4390 return Ok(None);
4391 };
4392 let arr = <[u8; 8]>::try_from(key.as_slice()).map_err(|_| {
4393 StoreError::Custom(format!(
4394 "STATE_HISTORY key has unexpected length: {}",
4395 key.len()
4396 ))
4397 })?;
4398 Ok(Some(BlockNumber::from_be_bytes(arr)))
4399 }
4400
4401 pub fn reorg_retention(&self) -> Result<u64, StoreError> {
4406 let cache = self.trie_cache.read().map_err(|_| StoreError::LockError)?;
4407 Ok(cache.commit_threshold() as u64)
4408 }
4409
4410 #[doc(hidden)]
4414 pub fn put_state_history_entry_for_test(
4415 &self,
4416 block_number: BlockNumber,
4417 encoded: &[u8],
4418 ) -> Result<(), StoreError> {
4419 let mut tx = self.backend.begin_write()?;
4420 tx.put(STATE_HISTORY, &block_number.to_be_bytes(), encoded)?;
4421 tx.commit()?;
4422 Ok(())
4423 }
4424
4425 pub fn install_overlay_for_reorg(
4446 &self,
4447 from_block: BlockNumber,
4448 to_block: BlockNumber,
4449 expected_hash: impl Fn(BlockNumber) -> Option<H256>,
4450 ) -> Result<(), StoreError> {
4451 let overlay =
4453 Overlay::from_journal(self.backend.as_ref(), from_block, to_block, expected_hash)
4454 .map_err(|e| StoreError::Custom(format!("overlay construction failed: {e}")))?;
4455
4456 let threshold = {
4457 let current = self.trie_cache.read().map_err(|_| StoreError::LockError)?;
4458 current.commit_threshold()
4459 };
4460 let mut fresh =
4463 TrieLayerCache::new_with_safe_commit(threshold, self.safe_commit_root.clone());
4464 fresh.set_overlay(Arc::new(overlay));
4465
4466 self.rendezvous_persist_worker("install_overlay_for_reorg")?;
4469
4470 let mut guard = self.trie_cache.write().map_err(|_| StoreError::LockError)?;
4471 *guard = Arc::new(fresh);
4472 Ok(())
4473 }
4474
4475 fn rendezvous_persist_worker(&self, caller: &str) -> Result<(), StoreError> {
4488 let (ack_tx, ack_rx) = sync_channel::<Result<(), StoreError>>(1);
4489 self.persist_tx
4490 .send(PersistMessage::Ping(ack_tx))
4491 .map_err(|e| {
4492 StoreError::Custom(format!("{caller}: failed to ping persist worker: {e}"))
4493 })?;
4494 ack_rx.recv().map_err(|e| {
4495 StoreError::Custom(format!("{caller}: persist worker ping ack failed: {e}"))
4496 })??;
4497 Ok(())
4498 }
4499
4500 pub fn reorg_overlay_size_hint(&self) -> Result<(usize, usize), StoreError> {
4505 let guard = self.trie_cache.read().map_err(|_| StoreError::LockError)?;
4506 match guard.overlay() {
4507 Some(ov) => Ok((ov.len(), ov.byte_size())),
4508 None => Ok((0, 0)),
4509 }
4510 }
4511
4512 pub fn set_journal_pruning_paused(&self, paused: bool) {
4516 self.journal_pruning_paused
4517 .store(paused, std::sync::atomic::Ordering::Release);
4518 }
4519
4520 pub fn clear_reorg_overlay(&self) -> Result<(), StoreError> {
4524 let mut guard = self.trie_cache.write().map_err(|_| StoreError::LockError)?;
4525 let mut updated = (**guard).clone();
4526 updated.clear_overlay();
4527 *guard = Arc::new(updated);
4528 Ok(())
4529 }
4530
4531 pub fn abort_reorg(&self) -> Result<(), StoreError> {
4537 self.rendezvous_persist_worker("abort_reorg")?;
4545 let mut guard = self.trie_cache.write().map_err(|_| StoreError::LockError)?;
4546 let threshold = guard.commit_threshold();
4547 *guard = Arc::new(TrieLayerCache::new_with_safe_commit(
4548 threshold,
4549 self.safe_commit_root.clone(),
4550 ));
4551 Ok(())
4552 }
4553
4554 pub fn ancestors(&self, block_hash: BlockHash) -> AncestorIterator {
4557 AncestorIterator {
4558 store: self.clone(),
4559 next_hash: block_hash,
4560 }
4561 }
4562
4563 pub fn is_canonical_sync(&self, block_hash: BlockHash) -> Result<bool, StoreError> {
4565 let Some(block_number) = self.get_block_number_sync(block_hash)? else {
4566 return Ok(false);
4567 };
4568 Ok(self
4569 .get_canonical_block_hash_sync(block_number)?
4570 .is_some_and(|h| h == block_hash))
4571 }
4572
4573 pub fn generate_flatkeyvalue(&self) -> Result<(), StoreError> {
4574 self.flatkeyvalue_control_tx
4575 .send(FKVGeneratorControlMessage::Continue)
4576 .map_err(|_| StoreError::Custom("FlatKeyValue thread disconnected.".to_string()))
4577 }
4578
4579 pub fn create_checkpoint(&self, path: impl AsRef<Path>) -> Result<(), StoreError> {
4580 self.backend.create_checkpoint(path.as_ref())?;
4581 init_metadata_file(path.as_ref())?;
4582 Ok(())
4583 }
4584
4585 pub fn get_store_directory(&self) -> Result<PathBuf, StoreError> {
4586 Ok(self.db_path.clone())
4587 }
4588
4589 async fn load_latest_block_number(&self) -> Result<Option<BlockNumber>, StoreError> {
4591 self.read_chain_data_block_number(ChainDataIndex::LatestBlockNumber)
4592 .await
4593 }
4594
4595 fn load_canonical_block_hash(
4596 &self,
4597 block_number: BlockNumber,
4598 ) -> Result<Option<BlockHash>, StoreError> {
4599 let txn = self.backend.begin_read()?;
4600 txn.get(
4601 CANONICAL_BLOCK_HASHES,
4602 block_number.to_le_bytes().as_slice(),
4603 )?
4604 .map(|bytes| H256::decode(bytes.as_slice()))
4605 .transpose()
4606 .map_err(StoreError::from)
4607 }
4608
4609 fn load_block_header(
4610 &self,
4611 block_number: BlockNumber,
4612 ) -> Result<Option<BlockHeader>, StoreError> {
4613 let Some(block_hash) = self.load_canonical_block_hash(block_number)? else {
4614 return Ok(None);
4615 };
4616 self.load_block_header_by_hash(block_hash)
4617 }
4618
4619 fn load_block_header_by_hash(
4621 &self,
4622 block_hash: BlockHash,
4623 ) -> Result<Option<BlockHeader>, StoreError> {
4624 let txn = self.backend.begin_read()?;
4625 let hash_key = block_hash.encode_to_vec();
4626 let header_value = txn.get(HEADERS, hash_key.as_slice())?;
4627 let mut header = header_value
4628 .map(|bytes| BlockHeaderRLP::from_bytes(bytes).to())
4629 .transpose()
4630 .map_err(StoreError::from)?;
4631 header.as_mut().inspect(|h| {
4632 let _ = h.hash.set(block_hash);
4634 });
4635 Ok(header)
4636 }
4637
4638 pub fn last_written(&self) -> Result<Vec<u8>, StoreError> {
4639 let last_computed_flatkeyvalue = self
4640 .last_computed_flatkeyvalue
4641 .read()
4642 .map_err(|_| StoreError::LockError)?;
4643 Ok(last_computed_flatkeyvalue.clone())
4644 }
4645
4646 pub fn flatkeyvalue_fully_generated(&self) -> Result<bool, StoreError> {
4657 let tx = self.backend.begin_read()?;
4658 let marker = tx.get(MISC_VALUES, "last_written".as_bytes())?;
4659 Ok(Self::flatkeyvalue_generation_complete(marker.as_deref()))
4660 }
4661
4662 fn flatkeyvalue_generation_complete(marker: Option<&[u8]>) -> bool {
4666 marker == Some([0xff].as_slice())
4667 }
4668
4669 fn flatkeyvalue_computed_with_last_written(account: H256, last_written: &[u8]) -> bool {
4670 let account_nibbles = Nibbles::from_bytes(account.as_bytes());
4671 &last_written[0..64] > account_nibbles.as_ref()
4672 }
4673
4674 pub fn advance_flushed_upto(&self, block_number: BlockNumber) -> Result<(), StoreError> {
4687 if self
4688 .read_flushed_upto_opt()?
4689 .is_some_and(|current| current >= block_number)
4690 {
4691 return Ok(());
4692 }
4693 let mut tx = self.backend.begin_write()?;
4694 write_flushed_upto(tx.as_mut(), block_number)?;
4695 tx.commit()?;
4696 mutate_block_buffer(&self.block_data_buffer, |b| {
4697 b.set_flushed_upto(block_number)
4698 })
4699 }
4700
4701 pub fn read_flushed_upto(&self) -> Result<BlockNumber, StoreError> {
4706 Ok(self.read_flushed_upto_opt()?.unwrap_or(0))
4707 }
4708
4709 fn read_flushed_upto_opt(&self) -> Result<Option<BlockNumber>, StoreError> {
4712 let tx = self.backend.begin_read()?;
4713 match tx.get(MISC_VALUES, FLUSHED_UPTO_KEY)? {
4714 Some(bytes) => Ok(Some(decode_flushed_upto(&bytes)?)),
4715 None => Ok(None),
4716 }
4717 }
4718
4719 #[cfg(any(test, feature = "testing"))]
4722 pub fn buffer_block_for_test(&self, block: &Block) {
4723 mutate_block_buffer(&self.block_data_buffer, |b| {
4724 b.insert(block.clone(), vec![], vec![])
4725 })
4726 .expect("block_data_buffer lock poisoned");
4727 }
4728
4729 #[cfg(any(test, feature = "testing"))]
4732 pub fn flush_block_data_for_test(&self) -> Result<(), StoreError> {
4733 flush_block_data(self.backend.as_ref(), &self.block_data_buffer)
4734 }
4735
4736 #[cfg(any(test, feature = "testing"))]
4740 pub fn get_trie_node_for_test(
4741 &self,
4742 is_account: bool,
4743 key: &[u8],
4744 ) -> Result<Option<Vec<u8>>, StoreError> {
4745 let table = if is_account {
4746 ACCOUNT_TRIE_NODES
4747 } else {
4748 STORAGE_TRIE_NODES
4749 };
4750 self.backend.begin_read()?.get(table, key)
4751 }
4752
4753 #[cfg(any(test, feature = "testing"))]
4757 pub fn buffer_block_with_codes_for_test(&self, block: &Block, codes: Vec<(H256, Code)>) {
4758 mutate_block_buffer(&self.block_data_buffer, |b| {
4759 b.insert(block.clone(), vec![], codes)
4760 })
4761 .expect("block_data_buffer lock poisoned");
4762 }
4763
4764 #[cfg(any(test, feature = "testing"))]
4769 pub fn register_pending_root_for_test(&self, root: H256) -> Result<(), StoreError> {
4770 self.pending_trie_roots.register(root)
4771 }
4772
4773 #[cfg(any(test, feature = "testing"))]
4776 pub fn clear_pending_root_for_test(&self, root: H256) {
4777 self.pending_trie_roots.clear(root)
4778 }
4779
4780 async fn anchor_to_durable_head(&self, latest: BlockNumber) -> Result<(), StoreError> {
4799 let marker = self.read_flushed_upto_opt()?;
4800 let start = match marker {
4801 Some(flushed) => flushed.min(latest),
4802 None => latest,
4803 };
4804
4805 let mut head = start;
4806 let latest_block_header = loop {
4807 match self.load_block_header(head)? {
4808 Some(header) => break header,
4809 None if marker.is_none() => return Err(StoreError::MissingLatestBlockNumber),
4812 None if head == 0 => return Err(StoreError::MissingLatestBlockNumber),
4813 None => {
4814 warn!(
4815 "durable head {head}: canonical hash has no on-disk header \
4816 (reorg inside flush window); rewinding"
4817 );
4818 head -= 1;
4819 }
4820 }
4821 };
4822 self.latest_block_header.update(latest_block_header);
4823
4824 let reanchor = head != latest;
4829 let rewrite_marker = marker != Some(head);
4830 if reanchor || rewrite_marker {
4831 let mut tx = self.backend.begin_write()?;
4832 if reanchor {
4833 let latest_key = chain_data_key(ChainDataIndex::LatestBlockNumber);
4836 tx.put(CHAIN_DATA, &latest_key, &head.to_le_bytes())?;
4837 }
4838 if rewrite_marker {
4839 write_flushed_upto(tx.as_mut(), head)?;
4840 }
4841 tx.commit()?;
4842 }
4843 Ok(())
4844 }
4845}
4846
4847pub fn write_flushed_upto(
4851 tx: &mut dyn StorageWriteBatch,
4852 n: BlockNumber,
4853) -> Result<(), StoreError> {
4854 tx.put(MISC_VALUES, FLUSHED_UPTO_KEY, &n.to_le_bytes())
4855}
4856
4857fn decode_flushed_upto(bytes: &[u8]) -> Result<BlockNumber, StoreError> {
4863 let arr: [u8; 8] = bytes
4864 .try_into()
4865 .map_err(|_| StoreError::Custom("Invalid flushed_upto bytes".to_string()))?;
4866 Ok(BlockNumber::from_le_bytes(arr))
4867}
4868
4869fn mutate_block_buffer(
4875 buffer: &Arc<RwLock<Arc<BlockDataBuffer>>>,
4876 f: impl FnOnce(&mut BlockDataBuffer),
4877) -> Result<(), StoreError> {
4878 let mut new_buf = (*buffer.read().map_err(|_| StoreError::LockError)?.clone()).clone();
4879 f(&mut new_buf);
4880 *buffer.write().map_err(|_| StoreError::LockError)? = Arc::new(new_buf);
4881 Ok(())
4882}
4883
4884const DEFAULT_PERSIST_CHANNEL_CAPACITY: usize = 2;
4886
4887struct BlockPersist {
4893 blocks: Vec<(Block, Vec<Receipt>)>,
4894 codes: Vec<(H256, Code)>,
4895 parent_state_root: H256,
4896 child_state_root: H256,
4897 account_updates: TrieNodesUpdate,
4898 storage_updates: Vec<(H256, TrieNodesUpdate)>,
4899 commit_depth: Option<usize>,
4900 wait_for_flush: bool,
4901 block_number: BlockNumber,
4913 block_hash: H256,
4915 ack: std::sync::mpsc::SyncSender<Result<(), StoreError>>,
4916}
4917
4918enum PersistMessage {
4922 Block(Box<BlockPersist>),
4923 Commit(H256),
4929 Ping(std::sync::mpsc::SyncSender<Result<(), StoreError>>),
4930 Shutdown {
4935 ack: std::sync::mpsc::SyncSender<Result<(), StoreError>>,
4936 },
4937}
4938
4939fn write_block_data(
4944 tx: &mut dyn StorageWriteBatch,
4945 number: BlockNumber,
4946 hash: BlockHash,
4947 header: &BlockHeader,
4948 body: &BlockBody,
4949) -> Result<(), StoreError> {
4950 let hash_key = hash.encode_to_vec();
4951 tx.put(
4952 HEADERS,
4953 &hash_key,
4954 BlockHeaderRLP::from(header.clone()).bytes(),
4955 )?;
4956 tx.put(
4957 BODIES,
4958 &hash_key,
4959 BlockBodyRLP::from_bytes(body.encode_to_vec()).bytes(),
4960 )?;
4961 tx.put(BLOCK_NUMBERS, &hash_key, &number.to_le_bytes())?;
4962 for (index, transaction) in body.transactions.iter().enumerate() {
4963 tx.merge(
4964 TRANSACTION_LOCATIONS,
4965 transaction.hash(&NativeCrypto).as_bytes(),
4966 &encode_tx_location_operand(number, hash, index as u64),
4967 )?;
4968 }
4969 Ok(())
4970}
4971
4972fn flush_block_data(
4975 backend: &dyn StorageBackend,
4976 buffer: &Arc<RwLock<Arc<BlockDataBuffer>>>,
4977) -> Result<(), StoreError> {
4978 let snapshot = buffer.read().map_err(|_| StoreError::LockError)?.clone();
4979 let to_flush = snapshot.flushable();
4980 if to_flush.is_empty() {
4981 return Ok(());
4982 }
4983 let hashes: Vec<_> = to_flush.iter().map(|b| b.header.hash()).collect();
4984 let codes = snapshot.codes_for(&hashes);
4985 let mut max_number = snapshot.flushed_upto();
4986
4987 let mut tx = backend.begin_write()?;
4988 for b in &to_flush {
4989 let hash = b.header.hash();
4990 write_block_data(tx.as_mut(), b.number, hash, &b.header, &b.body)?;
4991 for (index, receipt) in b.receipts.iter().enumerate() {
4992 tx.put(
4993 RECEIPTS_V2,
4994 &receipt_key(&hash, index as u64),
4995 &receipt.encode_to_vec(),
4996 )?;
4997 }
4998 max_number = max_number.max(b.number);
4999 }
5000 for (code_hash, code) in codes {
5001 let buf = encode_code(&code);
5002 tx.put(ACCOUNT_CODES, code_hash.as_ref(), &buf)?;
5003 tx.put(
5004 ACCOUNT_CODE_METADATA,
5005 code_hash.as_ref(),
5006 &(code.len() as u64).to_be_bytes(),
5007 )?;
5008 }
5009 write_flushed_upto(tx.as_mut(), max_number)?;
5010 tx.commit()?;
5011
5012 mutate_block_buffer(buffer, |b| b.evict_flushed(max_number))
5014}
5015
5016type TrieNodesUpdate = Vec<(Nibbles, Vec<u8>)>;
5017
5018#[derive(Debug, Default)]
5024struct PendingTrieRoots {
5025 count: AtomicUsize,
5027 roots: Mutex<HashSet<H256>>,
5028 ready: Condvar,
5029}
5030
5031impl PendingTrieRoots {
5032 fn register(&self, root: H256) -> Result<(), StoreError> {
5036 let mut roots = self.roots.lock().map_err(|_| StoreError::LockError)?;
5037 if roots.insert(root) {
5038 self.count.fetch_add(1, Ordering::Release);
5039 }
5040 Ok(())
5041 }
5042
5043 fn clear(&self, root: H256) {
5048 let Ok(mut roots) = self.roots.lock() else {
5049 return;
5050 };
5051 if roots.remove(&root) {
5052 self.count.fetch_sub(1, Ordering::Release);
5053 self.ready.notify_all();
5054 }
5055 }
5056
5057 fn wait_until_ready(&self, root: H256) -> Result<(), StoreError> {
5060 if self.count.load(Ordering::Acquire) == 0 {
5061 return Ok(());
5062 }
5063 let mut roots = self.roots.lock().map_err(|_| StoreError::LockError)?;
5064 while roots.contains(&root) {
5065 roots = self.ready.wait(roots).map_err(|_| StoreError::LockError)?;
5066 }
5067 Ok(())
5068 }
5069}
5070
5071#[allow(clippy::too_many_arguments)]
5075fn apply_trie_phase1(
5076 trie_cache: &Arc<RwLock<Arc<TrieLayerCache>>>,
5077 pending_roots: &PendingTrieRoots,
5078 parent_state_root: H256,
5079 child_state_root: H256,
5080 block_number: BlockNumber,
5081 block_hash: H256,
5082 account_updates: TrieNodesUpdate,
5083 storage_updates: Vec<(H256, TrieNodesUpdate)>,
5084) -> Result<(), StoreError> {
5085 let build: Result<(), StoreError> = (|| {
5086 let new_layer = storage_updates
5087 .into_iter()
5088 .flat_map(|(account_hash, nodes)| {
5089 nodes
5090 .into_iter()
5091 .map(move |(path, node)| (apply_prefix(Some(account_hash), path), node))
5092 })
5093 .chain(account_updates)
5094 .collect();
5095 let trie = trie_cache
5096 .read()
5097 .map_err(|_| StoreError::LockError)?
5098 .clone();
5099 let mut trie_mut = (*trie).clone();
5100 trie_mut.put_batch(
5101 parent_state_root,
5102 child_state_root,
5103 block_number,
5104 block_hash,
5105 new_layer,
5106 );
5107 *trie_cache.write().map_err(|_| StoreError::LockError)? = Arc::new(trie_mut);
5108 Ok(())
5109 })();
5110 pending_roots.clear(child_state_root);
5115 build
5116}
5117
5118fn commit_trie_if_due(
5142 backend: &dyn StorageBackend,
5143 trie_cache: &Arc<RwLock<Arc<TrieLayerCache>>>,
5144 fkv_ctl: &SyncSender<FKVGeneratorControlMessage>,
5145 parent_state_root: H256,
5146 commit_depth: Option<usize>,
5147 is_batch: bool,
5148) -> Result<(), StoreError> {
5149 let trie = trie_cache
5150 .read()
5151 .map_err(|_| StoreError::LockError)?
5152 .clone();
5153 let commitable = match commit_depth {
5155 Some(depth) => trie.get_commitable_by_depth(parent_state_root, depth),
5156 None => trie.get_commitable(parent_state_root),
5157 };
5158 let Some(root) = commitable else {
5159 return Ok(());
5161 };
5162 commit_to_disk(backend, fkv_ctl, trie_cache, &trie, root, is_batch)
5163}
5164
5165fn commit_to_disk(
5175 backend: &dyn StorageBackend,
5176 fkv_ctl: &SyncSender<FKVGeneratorControlMessage>,
5177 trie_cache: &Arc<RwLock<Arc<TrieLayerCache>>>,
5178 trie: &Arc<TrieLayerCache>,
5179 root: H256,
5180 is_batch: bool,
5181) -> Result<(), StoreError> {
5182 if !trie.has_layer(root) {
5186 debug!(
5187 root = ?root,
5188 layers = trie.layer_count(),
5189 is_batch,
5190 "Skipping trie commit: state root has no in-memory layer. Expected when the block \
5191 did not change the state root (empty L2 blocks) or the root was already flushed."
5192 );
5193 return Ok(());
5194 }
5195
5196 let _ = fkv_ctl.send(FKVGeneratorControlMessage::Stop);
5199
5200 let mut trie_mut = (**trie).clone();
5202
5203 let read_view = backend.begin_read()?;
5217 let last_written = read_view
5218 .get(MISC_VALUES, "last_written".as_bytes())?
5219 .unwrap_or_default();
5220
5221 let mut write_tx = backend.begin_write()?;
5222
5223 let overlay_for_reconciliation = if !is_batch {
5233 trie.overlay().cloned()
5234 } else {
5235 None
5236 };
5237
5238 let root = if overlay_for_reconciliation.is_some() {
5248 trie.bottom_layer_root(root)
5249 } else {
5250 root
5251 };
5252
5253 let committed_layers = trie_mut.commit(root).ok_or_else(|| {
5263 StoreError::Custom(format!(
5264 "trie layer for state root {root:?} disappeared between the has_layer check and \
5265 commit (layers={}, is_batch={is_batch}): the TrieLayerCache clone lost the layer \
5266 map, so this block's state was not written",
5267 trie.layer_count(),
5268 ))
5269 })?;
5270
5271 debug_assert!(
5276 overlay_for_reconciliation.is_none() || committed_layers.len() == 1,
5277 "overlay-backed reconciliation must commit exactly one layer (T), got {}",
5278 committed_layers.len()
5279 );
5280
5281 let extra_writes: Vec<(Vec<u8>, Vec<u8>)> = match &overlay_for_reconciliation {
5287 Some(overlay) => {
5288 let layer_keys: rustc_hash::FxHashSet<&Vec<u8>> = committed_layers
5289 .iter()
5290 .flat_map(|l| l.nodes.iter().map(|(k, _)| k))
5291 .collect();
5292 overlay
5293 .iter_all_entries()
5294 .filter(|(_, key, _)| !layer_keys.contains(key))
5295 .map(|(_, key, value)| (key.clone(), value.clone().unwrap_or_default()))
5296 .collect()
5297 }
5298 None => Vec::new(),
5299 };
5300 let reorg_heights = overlay_for_reconciliation
5302 .as_ref()
5303 .map(|ov| (ov.to_block(), ov.from_block()));
5304
5305 let mut overlay: HashMap<Vec<u8>, Option<Vec<u8>>> = HashMap::new();
5315
5316 let mut result = Ok(());
5317 'layers: for layer in &committed_layers {
5318 let mut journal_account_trie: FlatDiff = Vec::new();
5326 let mut journal_storage_trie: FlatDiff = Vec::new();
5327 let mut journal_account_flat: FlatDiff = Vec::new();
5328 let mut journal_storage_flat: FlatDiff = Vec::new();
5329
5330 let is_reconciliation_layer = overlay_for_reconciliation.is_some();
5341 let extra: &[(Vec<u8>, Vec<u8>)] = if is_reconciliation_layer {
5342 &extra_writes
5343 } else {
5344 &[]
5345 };
5346
5347 let pivot_overlay = overlay_for_reconciliation.as_ref();
5355
5356 for (key, value) in layer.nodes.iter().chain(extra.iter()) {
5357 let (is_leaf, is_account) = classify_trie_key(key.len());
5358
5359 if is_leaf && key.as_slice() > last_written.as_slice() {
5364 continue;
5365 }
5366 let table = if is_leaf {
5367 if is_account {
5368 &ACCOUNT_FLATKEYVALUE
5369 } else {
5370 &STORAGE_FLATKEYVALUE
5371 }
5372 } else if is_account {
5373 &ACCOUNT_TRIE_NODES
5374 } else {
5375 &STORAGE_TRIE_NODES
5376 };
5377
5378 let prev_value = if !is_batch {
5382 match overlay.get(key) {
5383 Some(v) => Some(v.clone()),
5384 None => match pivot_overlay
5385 .and_then(|ov| ov.lookup(OverlayCf::classify_by_key_length(key.len()), key))
5386 {
5387 Some(v) => Some(v),
5388 None => match read_view.get(table, key) {
5389 Ok(v) => Some(v),
5390 Err(e) => {
5391 result = Err(e);
5392 break 'layers;
5393 }
5394 },
5395 },
5396 }
5397 } else {
5398 None
5399 };
5400
5401 let new_value = if value.is_empty() {
5402 result = write_tx.delete(table, key);
5403 None
5404 } else {
5405 result = write_tx.put(table, key, value);
5406 Some(value.clone())
5407 };
5408 if result.is_err() {
5409 break 'layers;
5410 }
5411
5412 if let Some(prev) = prev_value {
5415 let bucket = match (is_leaf, is_account) {
5416 (false, true) => &mut journal_account_trie,
5417 (false, false) => &mut journal_storage_trie,
5418 (true, true) => &mut journal_account_flat,
5419 (true, false) => &mut journal_storage_flat,
5420 };
5421 bucket.push((key.clone(), prev));
5422 overlay.insert(key.clone(), new_value);
5425 }
5426 }
5427
5428 if !is_batch && let Some((t, d)) = reorg_heights {
5433 debug_assert_eq!(
5434 layer.block_number, t,
5435 "first new-chain commit must be at the pivot's T height (overlay.to_block)"
5436 );
5437 if layer.block_number == t {
5444 let start = t.to_be_bytes();
5445 let end = d.saturating_add(1).to_be_bytes();
5446 result = write_tx.delete_range(STATE_HISTORY, &start, &end);
5447 if result.is_err() {
5448 break 'layers;
5449 }
5450 } else {
5451 error!(
5452 block_number = layer.block_number,
5453 t,
5454 d,
5455 "deep-reorg reconciliation skipped: committed layer not at pivot height; skipping STATE_HISTORY delete_range to avoid history gaps"
5456 );
5457 }
5458 }
5459
5460 if !is_batch {
5466 let entry = JournalEntry {
5467 block_hash: layer.block_hash,
5468 parent_state_root: layer.parent_state_root,
5469 account_trie_diff: journal_account_trie,
5470 storage_trie_diff: journal_storage_trie,
5471 account_flat_diff: journal_account_flat,
5472 storage_flat_diff: journal_storage_flat,
5473 };
5474 result = write_tx.put(
5475 STATE_HISTORY,
5476 &layer.block_number.to_be_bytes(),
5477 &entry.encode(),
5478 );
5479 if result.is_err() {
5480 break 'layers;
5481 }
5482 }
5483 }
5484
5485 if result.is_ok() {
5486 result = write_tx.commit();
5487 }
5488 let _ = fkv_ctl.send(FKVGeneratorControlMessage::Continue);
5490 result?;
5491
5492 if overlay_for_reconciliation.is_some() {
5495 trie_mut.clear_overlay();
5496 }
5497
5498 *trie_cache.write().map_err(|_| StoreError::LockError)? = Arc::new(trie_mut);
5504 Ok(())
5505}
5506
5507fn flatkeyvalue_generator(
5510 backend: &Arc<dyn StorageBackend>,
5511 last_computed_fkv: &RwLock<Vec<u8>>,
5512 control_rx: &std::sync::mpsc::Receiver<FKVGeneratorControlMessage>,
5513) -> Result<(), StoreError> {
5514 info!("Generation of FlatKeyValue started.");
5515 let initial_last_written = backend
5516 .begin_read()?
5517 .get(MISC_VALUES, "last_written".as_bytes())?
5518 .unwrap_or_default();
5519
5520 if initial_last_written.is_empty() {
5521 backend.clear_table(ACCOUNT_FLATKEYVALUE)?;
5523 backend.clear_table(STORAGE_FLATKEYVALUE)?;
5524 } else if initial_last_written == [0xff] {
5525 info!("FlatKeyValue already generated. Skipping.");
5527 return Ok(());
5528 }
5529
5530 loop {
5531 let read_tx = backend.begin_read()?;
5534 let root = read_tx
5535 .get(ACCOUNT_TRIE_NODES, &[])?
5536 .ok_or(StoreError::MissingLatestBlockNumber)?;
5537 let root: Node = ethrex_trie::Node::decode(&root)?;
5538 let state_root = root.compute_hash(&NativeCrypto).finalize(&NativeCrypto);
5539
5540 let last_written = read_tx
5541 .get(MISC_VALUES, "last_written".as_bytes())?
5542 .unwrap_or_default();
5543 let last_written_account = last_written
5544 .get(0..64)
5545 .map(|v| Nibbles::from_hex(v.to_vec()))
5546 .unwrap_or_default();
5547 let mut last_written_storage = last_written
5548 .get(66..130)
5549 .map(|v| Nibbles::from_hex(v.to_vec()))
5550 .unwrap_or_default();
5551
5552 debug!("Starting FlatKeyValue loop pivot={last_written:?} SR={state_root:x}");
5553
5554 let mut ctr = 0;
5555 let mut write_txn = backend.begin_write()?;
5556 let mut iter = Trie::open(
5557 Box::new(BackendTrieDB::new_for_accounts_with_view(
5558 backend.clone(),
5559 read_tx.clone(),
5560 last_written.clone(),
5561 )?),
5562 state_root,
5563 )
5564 .into_iter();
5565 if last_written_account > Nibbles::default() {
5566 iter.advance(last_written_account.to_bytes())?;
5567 }
5568 let res = iter.try_for_each(|(path, node)| -> Result<(), StoreError> {
5569 let Node::Leaf(node) = node else {
5570 return Ok(());
5571 };
5572 let account_state = AccountState::decode(&node.value)?;
5573 let account_hash = H256::from_slice(&path.to_bytes());
5574 write_txn.put(MISC_VALUES, "last_written".as_bytes(), path.as_ref())?;
5575 write_txn.put(ACCOUNT_FLATKEYVALUE, path.as_ref(), &node.value)?;
5576 ctr += 1;
5577 if ctr > 10_000 {
5578 write_txn.commit()?;
5579 write_txn = backend.begin_write()?;
5580 *last_computed_fkv
5581 .write()
5582 .map_err(|_| StoreError::LockError)? = path.as_ref().to_vec();
5583 ctr = 0;
5584 }
5585
5586 let mut iter_inner = Trie::open(
5587 Box::new(BackendTrieDB::new_for_account_storage_with_view(
5588 backend.clone(),
5589 read_tx.clone(),
5590 account_hash,
5591 path.as_ref().to_vec(),
5592 )?),
5593 account_state.storage_root,
5594 )
5595 .into_iter();
5596 if last_written_storage > Nibbles::default() {
5597 iter_inner.advance(last_written_storage.to_bytes())?;
5598 last_written_storage = Nibbles::default();
5599 }
5600 iter_inner.try_for_each(|(path, node)| -> Result<(), StoreError> {
5601 let Node::Leaf(node) = node else {
5602 return Ok(());
5603 };
5604 let key = apply_prefix(Some(account_hash), path);
5605 write_txn.put(MISC_VALUES, "last_written".as_bytes(), key.as_ref())?;
5606 write_txn.put(STORAGE_FLATKEYVALUE, key.as_ref(), &node.value)?;
5607 ctr += 1;
5608 if ctr > 10_000 {
5609 write_txn.commit()?;
5610 write_txn = backend.begin_write()?;
5611 *last_computed_fkv
5612 .write()
5613 .map_err(|_| StoreError::LockError)? = key.into_vec();
5614 ctr = 0;
5615 }
5616 fkv_check_for_stop_msg(control_rx)?;
5617 Ok(())
5618 })?;
5619 fkv_check_for_stop_msg(control_rx)?;
5620 Ok(())
5621 });
5622 match res {
5623 Err(StoreError::PivotChanged) => {
5624 match control_rx.recv() {
5625 Ok(FKVGeneratorControlMessage::Continue) => {}
5626 Ok(FKVGeneratorControlMessage::Stop) => {
5627 return Err(StoreError::Custom("Unexpected Stop message".to_string()));
5628 }
5629 Err(std::sync::mpsc::RecvError) => {
5631 info!("Store closed, stopping FlatKeyValue generation.");
5632 return Ok(());
5633 }
5634 }
5635 }
5636 Err(err) => return Err(err),
5637 Ok(()) => {
5638 write_txn.put(MISC_VALUES, "last_written".as_bytes(), &[0xff])?;
5639 write_txn.commit()?;
5640 *last_computed_fkv
5641 .write()
5642 .map_err(|_| StoreError::LockError)? = vec![0xff; 131];
5643 info!("FlatKeyValue generation finished.");
5644 return Ok(());
5645 }
5646 };
5647 }
5648}
5649
5650fn fkv_check_for_stop_msg(
5651 control_rx: &std::sync::mpsc::Receiver<FKVGeneratorControlMessage>,
5652) -> Result<(), StoreError> {
5653 match control_rx.try_recv() {
5654 Ok(FKVGeneratorControlMessage::Stop) | Err(TryRecvError::Disconnected) => {
5655 return Err(StoreError::PivotChanged);
5656 }
5657 Ok(FKVGeneratorControlMessage::Continue) => {
5658 return Err(StoreError::Custom(
5659 "Unexpected Continue message".to_string(),
5660 ));
5661 }
5662 Err(TryRecvError::Empty) => {}
5663 }
5664 Ok(())
5665}
5666
5667fn state_trie_locked_backend(
5668 backend: &dyn StorageBackend,
5669 last_written: Vec<u8>,
5670) -> Result<BackendTrieDBLocked, StoreError> {
5671 BackendTrieDBLocked::new(backend, last_written)
5673}
5674
5675pub struct AccountProof {
5676 pub proof: Vec<NodeRLP>,
5677 pub account: AccountState,
5678 pub storage_proof: Vec<StorageSlotProof>,
5679}
5680
5681pub struct StorageSlotProof {
5682 pub proof: Vec<NodeRLP>,
5683 pub key: H256,
5684 pub value: U256,
5685}
5686
5687pub struct AncestorIterator {
5688 store: Store,
5689 next_hash: BlockHash,
5690}
5691
5692impl Iterator for AncestorIterator {
5693 type Item = Result<(BlockHash, BlockHeader), StoreError>;
5694
5695 fn next(&mut self) -> Option<Self::Item> {
5696 let next_hash = self.next_hash;
5697 match self.store.get_block_header_by_hash(next_hash) {
5701 Ok(Some(header)) => {
5702 let ret_hash = self.next_hash;
5703 self.next_hash = header.parent_hash;
5704 Some(Ok((ret_hash, header)))
5705 }
5706 Ok(None) => None,
5707 Err(e) => Some(Err(e)),
5708 }
5709 }
5710}
5711
5712pub fn hash_address(address: &Address) -> Vec<u8> {
5713 keccak_hash(address.to_fixed_bytes()).to_vec()
5714}
5715
5716fn hash_address_fixed(address: &Address) -> H256 {
5717 keccak(address.to_fixed_bytes())
5718}
5719
5720pub fn hash_key(key: &H256) -> Vec<u8> {
5721 keccak_hash(key.to_fixed_bytes()).to_vec()
5722}
5723
5724pub fn hash_key_fixed(key: &H256) -> [u8; 32] {
5725 keccak_hash(key.to_fixed_bytes())
5726}
5727
5728fn chain_data_key(index: ChainDataIndex) -> Vec<u8> {
5729 (index as u8).encode_to_vec()
5730}
5731
5732fn decode_block_number(bytes: Vec<u8>) -> Result<BlockNumber, StoreError> {
5734 let array: [u8; 8] = bytes
5735 .try_into()
5736 .map_err(|_| StoreError::Custom("Invalid BlockNumber bytes".to_string()))?;
5737 Ok(BlockNumber::from_le_bytes(array))
5738}
5739
5740fn snap_state_key(index: SnapStateIndex) -> Vec<u8> {
5741 (index as u8).encode_to_vec()
5742}
5743
5744pub fn receipt_key(block_hash: &BlockHash, index: u64) -> Vec<u8> {
5746 let mut key = Vec::with_capacity(40);
5747 key.extend_from_slice(block_hash.as_bytes());
5748 key.extend_from_slice(&index.to_be_bytes());
5749 key
5750}
5751
5752pub fn encode_code(code: &Code) -> Vec<u8> {
5753 let jumpdests = code.jumpdests();
5754 let mut buf = Vec::with_capacity(6 + code.len() + jumpdests.len());
5755 code.code().encode(&mut buf);
5756 jumpdests.encode(&mut buf);
5757 buf
5758}
5759
5760fn decode_jumpdests(code: &[u8], encoded: &[u8]) -> Result<(Arc<[u8]>, bool), StoreError> {
5771 let (is_list, payload, _) = decode_rlp_item(encoded)?;
5772 if is_list {
5773 return Ok((Code::compute_jumpdests(code), true));
5774 }
5775 Ok((Arc::from(payload), false))
5776}
5777
5778#[derive(Debug, Default, Clone)]
5779struct LatestBlockHeaderCache {
5780 current: Arc<Mutex<Arc<BlockHeader>>>,
5781}
5782
5783impl LatestBlockHeaderCache {
5784 pub fn get(&self) -> Arc<BlockHeader> {
5785 self.current.lock().expect("poisoned mutex").clone()
5786 }
5787
5788 pub fn update(&self, header: BlockHeader) {
5789 let new = Arc::new(header);
5790 *self.current.lock().expect("poisoned mutex") = new;
5791 }
5792}
5793
5794#[derive(Debug, Serialize, Deserialize)]
5795pub struct StoreMetadata {
5796 pub schema_version: u64,
5797}
5798
5799impl StoreMetadata {
5800 pub fn new(schema_version: u64) -> Self {
5801 Self { schema_version }
5802 }
5803}
5804
5805fn read_store_schema_version(path: &Path) -> Result<Option<u64>, StoreError> {
5810 let metadata_path = path.join(STORE_METADATA_FILENAME);
5811 if !metadata_path.exists() {
5812 return Ok(None);
5813 }
5814 if !metadata_path.is_file() {
5815 return Err(StoreError::Custom(
5816 "store schema path exists but is not a file".to_string(),
5817 ));
5818 }
5819 let file_contents = std::fs::read_to_string(metadata_path)?;
5820 let metadata: StoreMetadata = serde_json::from_str(&file_contents)?;
5821 Ok(Some(metadata.schema_version))
5822}
5823
5824fn init_metadata_file(parent_path: &Path) -> Result<(), StoreError> {
5825 std::fs::create_dir_all(parent_path)?;
5826
5827 let metadata_path = parent_path.join(STORE_METADATA_FILENAME);
5828 let metadata = StoreMetadata::new(STORE_SCHEMA_VERSION);
5829 let serialized_metadata = serde_json::to_string_pretty(&metadata)?;
5830 let mut new_file = std::fs::File::create_new(metadata_path)?;
5831 new_file.write_all(serialized_metadata.as_bytes())?;
5832 Ok(())
5833}
5834
5835fn dir_contains_legacy_db(path: &Path) -> Result<bool, StoreError> {
5847 if path.join("CURRENT").is_file() {
5851 return Ok(true);
5852 }
5853 for entry in std::fs::read_dir(path)? {
5857 let entry = entry?;
5858 if !entry.file_type()?.is_file() {
5859 continue;
5860 }
5861 if entry.file_name().to_string_lossy().starts_with("MANIFEST-") {
5862 return Ok(true);
5863 }
5864 }
5865 Ok(false)
5866}
5867
5868pub fn has_valid_db(path: &Path) -> bool {
5872 let metadata_path = path.join(STORE_METADATA_FILENAME);
5873 if !metadata_path.is_file() {
5874 return false;
5875 }
5876 let Ok(contents) = std::fs::read_to_string(&metadata_path) else {
5877 return false;
5878 };
5879 let Ok(metadata) = serde_json::from_str::<StoreMetadata>(&contents) else {
5880 return false;
5881 };
5882 metadata.schema_version >= 1 && metadata.schema_version <= STORE_SCHEMA_VERSION
5883}
5884
5885pub fn read_chain_id_from_db(path: &Path) -> Option<u64> {
5894 if !has_valid_db(path) {
5895 return None;
5896 }
5897 #[cfg(feature = "rocksdb")]
5898 {
5899 let backend = match RocksDBBackend::open(path, MAX_ROCKSDB_BLOCK_CACHE_SIZE_BYTES) {
5902 Ok(backend) => backend,
5903 Err(e) => {
5904 warn!("Failed to open RocksDB at {path:?} to read chain ID: {e}");
5905 return None;
5906 }
5907 };
5908 let read = match backend.begin_read() {
5909 Ok(read) => read,
5910 Err(e) => {
5911 warn!("Failed to begin read transaction at {path:?}: {e}");
5912 return None;
5913 }
5914 };
5915 let key = chain_data_key(ChainDataIndex::ChainConfig);
5916 let bytes = match read.get(CHAIN_DATA, &key) {
5917 Ok(Some(bytes)) => bytes,
5918 Ok(None) => {
5919 warn!("Chain config entry not found in database at {path:?}");
5920 return None;
5921 }
5922 Err(e) => {
5923 warn!("Failed to read chain config from database at {path:?}: {e}");
5924 return None;
5925 }
5926 };
5927 #[derive(serde::Deserialize)]
5932 #[serde(rename_all = "camelCase")]
5933 struct ChainIdOnly {
5934 chain_id: u64,
5935 }
5936 match serde_json::from_slice::<ChainIdOnly>(&bytes) {
5937 Ok(partial) => Some(partial.chain_id),
5938 Err(e) => {
5939 warn!("Failed to deserialize chain ID from database at {path:?}: {e}");
5940 None
5941 }
5942 }
5943 }
5944 #[cfg(not(feature = "rocksdb"))]
5945 {
5946 let _ = path;
5947 None
5948 }
5949}
5950
5951#[cfg(test)]
5952mod account_code_tests {
5953 use super::*;
5954
5955 const JUMPDEST: u8 = 0x5b;
5956 const PUSH1: u8 = 0x60;
5957
5958 #[test]
5964 fn metadata_falls_back_to_the_bytecode_without_a_tokio_runtime() {
5965 use crate::backend::in_memory::InMemoryBackend;
5966
5967 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
5968 let dir = tempfile::tempdir().unwrap();
5969 let store = Store::from_backend(
5970 backend.clone(),
5971 dir.path().to_path_buf(),
5972 1,
5973 DEFAULT_PERSIST_CHANNEL_CAPACITY,
5974 )
5975 .unwrap();
5976
5977 let code = jumpdest_dense_code();
5980 let mut tx = backend.begin_write().unwrap();
5981 tx.put(ACCOUNT_CODES, code.hash.as_bytes(), &encode_code(&code))
5982 .unwrap();
5983 tx.commit().unwrap();
5984
5985 let metadata = store
5986 .get_code_metadata(code.hash)
5987 .expect("metadata read must not fail off-runtime");
5988 assert_eq!(metadata.map(|m| m.length), Some(code.len() as u64));
5989 }
5990
5991 fn jumpdest_dense_code() -> Code {
5995 let mut bytecode = vec![JUMPDEST; 24576];
5996 bytecode[0] = 0x00;
5997 Code::from_bytecode_unchecked(bytecode.into(), H256::zero())
5998 }
5999
6000 fn encode_code_legacy(code: &Code) -> Vec<u8> {
6003 let offsets: Vec<u32> = (0..code.len())
6004 .filter(|offset| code.is_valid_jumpdest(*offset))
6005 .map(|offset| offset as u32)
6006 .collect();
6007 let mut buf = Vec::new();
6008 code.code().encode(&mut buf);
6009 offsets.encode(&mut buf);
6010 buf
6011 }
6012
6013 #[test]
6014 fn encoded_value_carries_one_bitmap_bit_per_code_byte() {
6015 let code = jumpdest_dense_code();
6016 let encoded = encode_code(&code);
6017
6018 assert_eq!(encoded.len(), 6 + code.len() + code.len().div_ceil(8));
6019 }
6020
6021 #[test]
6022 fn round_trip_preserves_the_bitmap() {
6023 let code = jumpdest_dense_code();
6024 let encoded = encode_code(&code);
6025
6026 let (bytecode, jumpdests) = decode_bytes(&encoded).unwrap();
6027 assert_eq!(bytecode, code.code());
6028 let (rebuilt, was_legacy) = decode_jumpdests(bytecode, jumpdests).unwrap();
6029 assert_eq!(rebuilt.as_ref(), code.jumpdests());
6030 assert!(!was_legacy, "bitmap format must not be flagged legacy");
6031 }
6032
6033 #[test]
6040 fn legacy_offset_list_values_decode_to_the_same_bitmap() {
6041 for (bytecode, offsets) in [
6042 (vec![0x00, JUMPDEST, 0x00, JUMPDEST], vec![1u32, 3]),
6043 (vec![PUSH1, JUMPDEST, JUMPDEST], vec![2u32]),
6045 (vec![0x00; 32], vec![]),
6046 ] {
6047 let code = Code::from_bytecode_unchecked(bytecode.clone().into(), H256::zero());
6048 let legacy = encode_code_legacy(&code);
6049
6050 let mut expected = vec![0u8; bytecode.len().div_ceil(8)];
6051 for offset in &offsets {
6052 let index = usize::try_from(*offset).expect("offset fits usize");
6053 expected[index / 8] |= 1 << (index % 8);
6054 }
6055 if offsets.is_empty() {
6057 expected.clear();
6058 }
6059
6060 let (decoded_bytecode, jumpdests) = decode_bytes(&legacy).unwrap();
6061 assert_eq!(decoded_bytecode, code.code());
6062 let (rebuilt, was_legacy) = decode_jumpdests(decoded_bytecode, jumpdests).unwrap();
6063 assert_eq!(
6064 rebuilt.as_ref(),
6065 expected.as_slice(),
6066 "rebuilt bitmap disagrees with the legacy offsets for {bytecode:?}"
6067 );
6068 assert!(
6069 was_legacy,
6070 "legacy RLP-list format must be flagged for self-heal"
6071 );
6072 }
6073 }
6074
6075 #[test]
6078 fn repeated_inserts_do_not_inflate_the_accounted_size() {
6079 let code = jumpdest_dense_code();
6080 let mut cache = CodeCache::default();
6081
6082 cache.insert(&code).unwrap();
6083 let after_first = cache.cache_size;
6084 for _ in 0..8 {
6085 cache.insert(&code).unwrap();
6086 }
6087
6088 assert_eq!(cache.cache_size, after_first);
6089 assert_eq!(cache.inner_cache.len(), 1);
6090 }
6091
6092 #[test]
6095 fn accounted_size_covers_the_bytecode_and_bitmap() {
6096 let code = jumpdest_dense_code();
6097 let mut cache = CodeCache::default();
6098 cache.insert(&code).unwrap();
6099
6100 assert!(cache.cache_size >= (code.len() + code.jumpdests().len()) as u64);
6101 }
6102
6103 #[test]
6104 fn cache_evicts_down_to_its_budget() {
6105 let mut cache = CodeCache {
6106 max_size: 128 * 1024,
6107 ..Default::default()
6108 };
6109
6110 for i in 0..16u8 {
6111 let mut bytecode = vec![JUMPDEST; 24576];
6112 bytecode[0] = i;
6113 cache
6114 .insert(&Code::from_bytecode_unchecked(
6115 bytecode.into(),
6116 H256::from_low_u64_be(i.into()),
6117 ))
6118 .unwrap();
6119 }
6120
6121 assert!(cache.cache_size <= cache.max_size);
6122 assert!(cache.inner_cache.len() < 16);
6123 }
6124
6125 #[test]
6128 fn default_cache_uses_the_configured_budget() {
6129 assert_eq!(CodeCache::default().max_size, CODE_CACHE_MAX_SIZE);
6130 }
6131}
6132
6133#[cfg(test)]
6134mod state_history_tests {
6135 use super::*;
6136 use crate::api::tables::STATE_HISTORY;
6137 use crate::backend::in_memory::InMemoryBackend;
6138 use crate::journal::JournalEntry;
6139 use ethrex_common::types::{BlockBody, BlockHeader};
6140 use ethrex_trie::Nibbles;
6141 use std::time::{Duration, Instant};
6142
6143 fn make_block(number: BlockNumber, parent_hash: H256, state_root: H256) -> Block {
6144 let header = BlockHeader {
6145 number,
6146 parent_hash,
6147 state_root,
6148 ..Default::default()
6149 };
6150 Block::new(header, BlockBody::default())
6151 }
6152
6153 fn await_journal_entry(
6157 backend: &Arc<dyn StorageBackend>,
6158 block_number: BlockNumber,
6159 timeout: Duration,
6160 ) -> Option<Vec<u8>> {
6161 let key = block_number.to_be_bytes();
6162 let deadline = Instant::now() + timeout;
6163 loop {
6164 let read = backend.begin_read().ok()?;
6165 if let Ok(Some(v)) = read.get(STATE_HISTORY, &key) {
6166 return Some(v);
6167 }
6168 if Instant::now() >= deadline {
6169 return None;
6170 }
6171 std::thread::sleep(Duration::from_millis(10));
6172 }
6173 }
6174
6175 fn assert_no_journal_entry(backend: &Arc<dyn StorageBackend>, block_number: BlockNumber) {
6180 let window = Duration::from_millis(500);
6181 let key = block_number.to_be_bytes();
6182 let deadline = Instant::now() + window;
6183 loop {
6184 let read = backend.begin_read().expect("read view");
6185 let v = read.get(STATE_HISTORY, &key).expect("get");
6186 assert!(
6187 v.is_none(),
6188 "expected no STATE_HISTORY entry for block {block_number}, got {v:?}"
6189 );
6190 if Instant::now() >= deadline {
6191 return;
6192 }
6193 std::thread::sleep(Duration::from_millis(20));
6194 }
6195 }
6196
6197 #[test]
6203 fn journal_entry_written_per_block_in_regular_mode() {
6204 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6205 let dir = tempfile::tempdir().unwrap();
6206 let store = Store::from_backend(
6207 backend.clone(),
6208 dir.path().to_path_buf(),
6209 1,
6210 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6211 )
6212 .unwrap();
6213
6214 let state_root_1 = H256::repeat_byte(0x11);
6215 let block1 = make_block(1, H256::zero(), state_root_1);
6216 let block1_hash = block1.hash();
6217 store
6218 .store_block_updates(UpdateBatch {
6219 account_updates: vec![(Nibbles::from_raw(&[0x00, 0x01], false), vec![0xab, 0xcd])],
6220 storage_updates: vec![],
6221 blocks: vec![block1],
6222 receipts: vec![],
6223 code_updates: vec![],
6224 commit_depth: None,
6225 wait_for_flush: false,
6226 })
6227 .unwrap();
6228
6229 store.set_safe_commit_root(state_root_1).unwrap();
6232 let state_root_2 = H256::repeat_byte(0x22);
6233 let block2 = make_block(2, block1_hash, state_root_2);
6234 let block2_hash = block2.hash();
6235 store
6236 .store_block_updates(UpdateBatch {
6237 account_updates: vec![(Nibbles::from_raw(&[0x00, 0x02], false), vec![0xef, 0x11])],
6238 storage_updates: vec![],
6239 blocks: vec![block2],
6240 receipts: vec![],
6241 code_updates: vec![],
6242 commit_depth: None,
6243 wait_for_flush: false,
6244 })
6245 .unwrap();
6246
6247 let bytes = await_journal_entry(&backend, 1, Duration::from_secs(2))
6248 .expect("STATE_HISTORY entry for block 1 should appear after block 2 commits it");
6249 let entry = JournalEntry::decode(&bytes).unwrap();
6250 assert_eq!(entry.block_hash, block1_hash);
6251 assert_eq!(entry.parent_state_root, H256::zero());
6252 assert!(!entry.account_trie_diff.is_empty());
6253 let (path, prev) = &entry.account_trie_diff[0];
6254 assert_eq!(prev, &None, "first-time write means previous value is None");
6255 assert!(path.len() < 65);
6256
6257 store.set_safe_commit_root(state_root_2).unwrap();
6260 let state_root_3 = H256::repeat_byte(0x33);
6261 let block3 = make_block(3, block2_hash, state_root_3);
6262 store
6263 .store_block_updates(UpdateBatch {
6264 account_updates: vec![(Nibbles::from_raw(&[0x00, 0x03], false), vec![0x77])],
6265 storage_updates: vec![],
6266 blocks: vec![block3],
6267 receipts: vec![],
6268 code_updates: vec![],
6269 commit_depth: None,
6270 wait_for_flush: false,
6271 })
6272 .unwrap();
6273
6274 let bytes = await_journal_entry(&backend, 2, Duration::from_secs(2))
6275 .expect("STATE_HISTORY entry for block 2 should appear after block 3 commits it");
6276 let entry = JournalEntry::decode(&bytes).unwrap();
6277 assert_eq!(entry.block_hash, block2_hash);
6278 assert_eq!(entry.parent_state_root, state_root_1);
6279 assert!(!entry.account_trie_diff.is_empty());
6280 }
6281
6282 #[test]
6292 fn depth_gated_per_block_commit_journals_with_correct_pre_image() {
6293 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6294 let dir = tempfile::tempdir().unwrap();
6295 let store = Store::from_backend(
6296 backend.clone(),
6297 dir.path().to_path_buf(),
6298 1,
6299 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6300 )
6301 .unwrap();
6302
6303 let shared = Nibbles::from_raw(&[0x0a, 0x0b], false);
6305 let mut prev_hash = H256::zero();
6306 let mut hashes = vec![H256::zero()];
6307 let mut roots = vec![H256::zero()];
6308 for n in 1..=6u64 {
6309 let state_root = H256::repeat_byte(0xc0 | (n as u8));
6310 let block = make_block(n, prev_hash, state_root);
6311 prev_hash = block.hash();
6312 hashes.push(prev_hash);
6313 roots.push(state_root);
6314 store
6315 .store_block_updates(UpdateBatch {
6316 account_updates: vec![(shared.clone(), vec![0xb0 | (n as u8)])],
6317 storage_updates: vec![],
6318 blocks: vec![block],
6319 receipts: vec![],
6320 code_updates: vec![],
6321 commit_depth: Some(3),
6322 wait_for_flush: false,
6323 })
6324 .unwrap();
6325 }
6326
6327 let bytes = await_journal_entry(&backend, 1, Duration::from_secs(2))
6328 .expect("a depth-gated per-block commit must journal");
6329 let entry = JournalEntry::decode(&bytes).unwrap();
6330 assert_eq!(
6331 entry.block_hash, hashes[1],
6332 "entry 1 carries block 1's identity"
6333 );
6334 assert_eq!(entry.parent_state_root, H256::zero());
6335 assert_eq!(
6336 entry.account_trie_diff[0].1, None,
6337 "block 1 first-writes the key, so its pre-image is absence"
6338 );
6339
6340 let bytes =
6341 await_journal_entry(&backend, 2, Duration::from_secs(2)).expect("entry for block 2");
6342 let entry = JournalEntry::decode(&bytes).unwrap();
6343 assert_eq!(
6344 entry.block_hash, hashes[2],
6345 "entry 2 carries block 2's identity"
6346 );
6347 assert_eq!(
6348 entry.parent_state_root, roots[1],
6349 "entry 2's parent_state_root is block 1's state root"
6350 );
6351 assert_eq!(
6352 entry.account_trie_diff[0].1,
6353 Some(vec![0xb0 | 1u8]),
6354 "block 2's pre-image is the value block 1 wrote"
6355 );
6356 }
6357
6358 #[test]
6367 fn journal_skipped_in_batch_mode() {
6368 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6369 let dir = tempfile::tempdir().unwrap();
6370 let store = Store::from_backend(
6371 backend.clone(),
6372 dir.path().to_path_buf(),
6373 1,
6374 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6375 )
6376 .unwrap();
6377
6378 let mut prev_hash = H256::zero();
6379 for n in 1..=5u64 {
6380 let state_root = H256::repeat_byte(0xa0 | (n as u8));
6381 let block = make_block(n, prev_hash, state_root);
6382 prev_hash = block.hash();
6383 store
6384 .store_block_updates(UpdateBatch {
6385 account_updates: vec![(
6386 Nibbles::from_raw(&[n as u8], false),
6387 vec![0xde, 0xad, n as u8],
6388 )],
6389 storage_updates: vec![],
6390 blocks: vec![block],
6391 receipts: vec![],
6392 code_updates: vec![],
6393 commit_depth: Some(BATCH_COMMIT_THRESHOLD),
6394 wait_for_flush: true,
6395 })
6396 .unwrap();
6397 }
6398
6399 for n in 1..=5u64 {
6400 assert_no_journal_entry(&backend, n);
6401 }
6402 }
6403
6404 #[test]
6407 fn journal_storage_updates_appear_in_storage_diff() {
6408 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6409 let dir = tempfile::tempdir().unwrap();
6410 let store = Store::from_backend(
6411 backend.clone(),
6412 dir.path().to_path_buf(),
6413 1,
6414 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6415 )
6416 .unwrap();
6417
6418 let account_hash_a = H256::repeat_byte(0xa0);
6419 let account_hash_b = H256::repeat_byte(0xb0);
6420
6421 let state_root_1 = H256::repeat_byte(0x33);
6422 let block1 = make_block(1, H256::zero(), state_root_1);
6423 let block1_hash = block1.hash();
6424 store
6425 .store_block_updates(UpdateBatch {
6426 account_updates: vec![],
6427 storage_updates: vec![
6428 (
6429 account_hash_a,
6430 vec![(Nibbles::from_raw(&[0x05], false), vec![0x01])],
6431 ),
6432 (
6433 account_hash_b,
6434 vec![(Nibbles::from_raw(&[0x06], false), vec![0x02])],
6435 ),
6436 ],
6437 blocks: vec![block1],
6438 receipts: vec![],
6439 code_updates: vec![],
6440 commit_depth: None,
6441 wait_for_flush: false,
6442 })
6443 .unwrap();
6444
6445 store.set_safe_commit_root(state_root_1).unwrap();
6448 let state_root_2 = H256::repeat_byte(0x44);
6449 let block2 = make_block(2, block1_hash, state_root_2);
6450 store
6451 .store_block_updates(UpdateBatch {
6452 account_updates: vec![(Nibbles::from_raw(&[0xee], false), vec![0xff])],
6453 storage_updates: vec![],
6454 blocks: vec![block2],
6455 receipts: vec![],
6456 code_updates: vec![],
6457 commit_depth: None,
6458 wait_for_flush: false,
6459 })
6460 .unwrap();
6461
6462 let bytes = await_journal_entry(&backend, 1, Duration::from_secs(2))
6463 .expect("STATE_HISTORY entry for block 1");
6464 let entry = JournalEntry::decode(&bytes).unwrap();
6465 assert_eq!(entry.block_hash, block1_hash);
6466 assert_eq!(
6467 entry.storage_trie_diff.len(),
6468 2,
6469 "two distinct account hashes must produce two storage_trie entries"
6470 );
6471 for (_key, prev) in &entry.storage_trie_diff {
6472 assert_eq!(prev, &None, "first-time storage write has None pre-image");
6473 }
6474 assert!(entry.account_trie_diff.is_empty());
6475 }
6476
6477 fn seed_journal_entries(backend: &Arc<dyn StorageBackend>, block_numbers: &[BlockNumber]) {
6478 let mut tx = backend.begin_write().unwrap();
6479 for n in block_numbers {
6480 let entry = JournalEntry {
6481 block_hash: H256::repeat_byte(*n as u8),
6482 parent_state_root: H256::zero(),
6483 account_trie_diff: vec![(vec![*n as u8], None)],
6484 storage_trie_diff: vec![],
6485 account_flat_diff: vec![],
6486 storage_flat_diff: vec![],
6487 };
6488 tx.put(STATE_HISTORY, &n.to_be_bytes(), &entry.encode())
6489 .unwrap();
6490 }
6491 tx.commit().unwrap();
6492 }
6493
6494 fn journal_entry_exists(backend: &Arc<dyn StorageBackend>, block_number: BlockNumber) -> bool {
6495 backend
6496 .begin_read()
6497 .unwrap()
6498 .get(STATE_HISTORY, &block_number.to_be_bytes())
6499 .unwrap()
6500 .is_some()
6501 }
6502
6503 #[tokio::test]
6506 async fn finality_advance_prunes_journal_below_boundary() {
6507 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6508 let dir = tempfile::tempdir().unwrap();
6509 let store = Store::from_backend(
6510 backend.clone(),
6511 dir.path().to_path_buf(),
6512 1,
6513 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6514 )
6515 .unwrap();
6516
6517 seed_journal_entries(&backend, &(1..=10).collect::<Vec<_>>());
6518 for n in 1..=10 {
6519 assert!(journal_entry_exists(&backend, n), "seed entry {n} present");
6520 }
6521
6522 store
6523 .forkchoice_update_inner(vec![], 100, H256::zero(), None, Some(5))
6524 .await
6525 .unwrap();
6526
6527 for n in 1..=5 {
6528 assert!(
6529 !journal_entry_exists(&backend, n),
6530 "entry {n} should have been pruned (<= finalized)"
6531 );
6532 }
6533 for n in 6..=10 {
6534 assert!(
6535 journal_entry_exists(&backend, n),
6536 "entry {n} should remain (> finalized)"
6537 );
6538 }
6539 }
6540
6541 #[tokio::test]
6543 async fn finality_no_op_does_not_prune() {
6544 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6545 let dir = tempfile::tempdir().unwrap();
6546 let store = Store::from_backend(
6547 backend.clone(),
6548 dir.path().to_path_buf(),
6549 1,
6550 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6551 )
6552 .unwrap();
6553
6554 store
6555 .forkchoice_update_inner(vec![], 100, H256::zero(), None, Some(5))
6556 .await
6557 .unwrap();
6558
6559 seed_journal_entries(&backend, &(6..=10).collect::<Vec<_>>());
6560
6561 store
6563 .forkchoice_update_inner(vec![], 100, H256::zero(), None, Some(5))
6564 .await
6565 .unwrap();
6566
6567 for n in 6..=10 {
6568 assert!(
6569 journal_entry_exists(&backend, n),
6570 "entry {n} should still exist after no-op finality update"
6571 );
6572 }
6573
6574 store
6576 .forkchoice_update_inner(vec![], 100, H256::zero(), None, None)
6577 .await
6578 .unwrap();
6579
6580 for n in 6..=10 {
6581 assert!(
6582 journal_entry_exists(&backend, n),
6583 "entry {n} should still exist when finalized is None"
6584 );
6585 }
6586 }
6587
6588 #[tokio::test]
6592 async fn malformed_finalized_returns_error_not_silent_zero() {
6593 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6594 let dir = tempfile::tempdir().unwrap();
6595 let store = Store::from_backend(
6596 backend.clone(),
6597 dir.path().to_path_buf(),
6598 1,
6599 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6600 )
6601 .unwrap();
6602
6603 let mut tx = backend.begin_write().unwrap();
6605 let finalized_key = chain_data_key(ChainDataIndex::FinalizedBlockNumber);
6606 tx.put(CHAIN_DATA, &finalized_key, &[0u8, 0, 0, 0]).unwrap();
6607 tx.commit().unwrap();
6608
6609 seed_journal_entries(&backend, &(1..=5).collect::<Vec<_>>());
6610
6611 let err = store
6612 .forkchoice_update_inner(vec![], 100, H256::zero(), None, Some(3))
6613 .await
6614 .expect_err("malformed finalized must surface as an error");
6615 let msg = format!("{err}");
6616 assert!(
6617 msg.contains("FinalizedBlockNumber has unexpected length"),
6618 "expected length-mismatch error, got: {msg}"
6619 );
6620
6621 for n in 1..=5 {
6623 assert!(
6624 journal_entry_exists(&backend, n),
6625 "entry {n} must NOT be pruned when finalized read failed"
6626 );
6627 }
6628 }
6629
6630 #[tokio::test]
6637 async fn highest_state_history_block_number_finds_max() {
6638 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6639 let dir = tempfile::tempdir().unwrap();
6640 let store = Store::from_backend(
6641 backend.clone(),
6642 dir.path().to_path_buf(),
6643 1,
6644 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6645 )
6646 .unwrap();
6647
6648 assert_eq!(store.highest_state_history_block_number().unwrap(), None);
6649
6650 seed_journal_entries(&backend, &[3, 7, 5, 11, 8]);
6651 assert_eq!(
6652 store.highest_state_history_block_number().unwrap(),
6653 Some(11),
6654 "max over present entries"
6655 );
6656 }
6657
6658 #[tokio::test]
6662 async fn journaled_block_hash_reads_back() {
6663 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6664 let dir = tempfile::tempdir().unwrap();
6665 let store = Store::from_backend(
6666 backend.clone(),
6667 dir.path().to_path_buf(),
6668 1,
6669 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6670 )
6671 .unwrap();
6672
6673 seed_journal_entries(&backend, &[3, 11]);
6674 assert_eq!(
6675 store.get_state_history_block_hash(11).unwrap(),
6676 Some(H256::repeat_byte(11))
6677 );
6678 assert_eq!(store.get_state_history_block_hash(4).unwrap(), None);
6679 }
6680
6681 #[tokio::test]
6684 async fn advance_flushed_upto_only_moves_forward() {
6685 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6686 let dir = tempfile::tempdir().unwrap();
6687 let store = Store::from_backend(
6688 backend,
6689 dir.path().to_path_buf(),
6690 1,
6691 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6692 )
6693 .unwrap();
6694
6695 store.advance_flushed_upto(10).unwrap();
6696 assert_eq!(store.read_flushed_upto().unwrap(), 10);
6697 store.advance_flushed_upto(5).unwrap();
6698 assert_eq!(store.read_flushed_upto().unwrap(), 10);
6699 store.advance_flushed_upto(12).unwrap();
6700 assert_eq!(store.read_flushed_upto().unwrap(), 12);
6701 }
6702
6703 #[tokio::test]
6707 async fn lowest_state_history_block_number_finds_min() {
6708 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6709 let dir = tempfile::tempdir().unwrap();
6710 let store = Store::from_backend(
6711 backend.clone(),
6712 dir.path().to_path_buf(),
6713 1,
6714 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6715 )
6716 .unwrap();
6717
6718 assert_eq!(store.lowest_state_history_block_number().unwrap(), None);
6719
6720 seed_journal_entries(&backend, &[7, 3, 11, 5, 8]);
6721 assert_eq!(
6722 store.lowest_state_history_block_number().unwrap(),
6723 Some(3),
6724 "min over present entries"
6725 );
6726 }
6727
6728 #[tokio::test]
6732 async fn install_overlay_replaces_cache_with_fresh_one() {
6733 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6734 let dir = tempfile::tempdir().unwrap();
6735 let store = Store::from_backend(
6736 backend.clone(),
6737 dir.path().to_path_buf(),
6738 4,
6739 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6740 )
6741 .unwrap();
6742
6743 seed_journal_entries(&backend, &[3, 4]);
6744
6745 {
6747 let mut guard = store.trie_cache.write().unwrap();
6748 let mut updated = (**guard).clone();
6749 updated.put_batch(
6750 H256::zero(),
6751 H256::repeat_byte(0xff),
6752 42,
6753 H256::repeat_byte(0x42),
6754 vec![],
6755 );
6756 *guard = Arc::new(updated);
6757 }
6758 assert!(
6759 store
6760 .is_state_in_layer_cache(H256::repeat_byte(0xff))
6761 .unwrap()
6762 );
6763
6764 store
6765 .install_overlay_for_reorg(4, 3, |_| None)
6766 .expect("install must succeed");
6767
6768 assert!(
6769 !store
6770 .is_state_in_layer_cache(H256::repeat_byte(0xff))
6771 .unwrap(),
6772 "old cache layer must be gone after swap"
6773 );
6774 let cache = store.trie_cache.read().unwrap().clone();
6775 assert!(cache.overlay().is_some(), "overlay must be installed");
6776 assert_eq!(
6777 cache.commit_threshold(),
6778 4,
6779 "fresh cache must inherit threshold"
6780 );
6781 }
6782
6783 #[tokio::test]
6786 async fn install_overlay_failure_preserves_cache() {
6787 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6788 let dir = tempfile::tempdir().unwrap();
6789 let store = Store::from_backend(
6790 backend.clone(),
6791 dir.path().to_path_buf(),
6792 4,
6793 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6794 )
6795 .unwrap();
6796
6797 seed_journal_entries(&backend, &[5]);
6799 {
6800 let mut guard = store.trie_cache.write().unwrap();
6801 let mut updated = (**guard).clone();
6802 updated.put_batch(
6803 H256::zero(),
6804 H256::repeat_byte(0xaa),
6805 7,
6806 H256::repeat_byte(0x77),
6807 vec![],
6808 );
6809 *guard = Arc::new(updated);
6810 }
6811 assert!(
6812 store
6813 .is_state_in_layer_cache(H256::repeat_byte(0xaa))
6814 .unwrap()
6815 );
6816
6817 let err = store
6818 .install_overlay_for_reorg(5, 3, |_| None)
6819 .expect_err("missing entries 3 and 4 must abort");
6820 let msg = format!("{err}");
6821 assert!(
6822 msg.contains("overlay construction failed"),
6823 "error should explain construction failure: {msg}"
6824 );
6825
6826 assert!(
6828 store
6829 .is_state_in_layer_cache(H256::repeat_byte(0xaa))
6830 .unwrap(),
6831 "cache must survive a failed overlay install"
6832 );
6833 let cache = store.trie_cache.read().unwrap().clone();
6834 assert!(cache.overlay().is_none(), "no overlay should be installed");
6835 }
6836
6837 #[tokio::test]
6841 async fn abort_reorg_resets_cache_to_fresh() {
6842 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6843 let dir = tempfile::tempdir().unwrap();
6844 let store = Store::from_backend(
6845 backend.clone(),
6846 dir.path().to_path_buf(),
6847 4,
6848 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6849 )
6850 .unwrap();
6851
6852 seed_journal_entries(&backend, &[3, 4]);
6853 store.install_overlay_for_reorg(4, 3, |_| None).unwrap();
6854 {
6856 let mut guard = store.trie_cache.write().unwrap();
6857 let mut updated = (**guard).clone();
6858 updated.put_batch(
6859 H256::zero(),
6860 H256::repeat_byte(0xbb),
6861 4,
6862 H256::repeat_byte(0xb4),
6863 vec![],
6864 );
6865 *guard = Arc::new(updated);
6866 }
6867 assert!(
6868 store
6869 .is_state_in_layer_cache(H256::repeat_byte(0xbb))
6870 .unwrap()
6871 );
6872
6873 store.abort_reorg().unwrap();
6874
6875 assert!(
6876 !store
6877 .is_state_in_layer_cache(H256::repeat_byte(0xbb))
6878 .unwrap(),
6879 "partial new-chain layer must be discarded"
6880 );
6881 let cache = store.trie_cache.read().unwrap().clone();
6882 assert!(cache.overlay().is_none(), "overlay must be cleared");
6883 assert_eq!(
6884 cache.commit_threshold(),
6885 4,
6886 "fresh cache must inherit threshold"
6887 );
6888 }
6889
6890 #[tokio::test]
6893 async fn clear_reorg_overlay_removes_overlay_and_is_idempotent() {
6894 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6895 let dir = tempfile::tempdir().unwrap();
6896 let store = Store::from_backend(
6897 backend.clone(),
6898 dir.path().to_path_buf(),
6899 4,
6900 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6901 )
6902 .unwrap();
6903
6904 store.clear_reorg_overlay().unwrap();
6906 assert!(store.trie_cache.read().unwrap().overlay().is_none());
6907
6908 seed_journal_entries(&backend, &[7]);
6909 store.install_overlay_for_reorg(7, 7, |_| None).unwrap();
6910 assert!(store.trie_cache.read().unwrap().overlay().is_some());
6911
6912 store.clear_reorg_overlay().unwrap();
6913 assert!(store.trie_cache.read().unwrap().overlay().is_none());
6914
6915 store.clear_reorg_overlay().unwrap();
6917 assert!(store.trie_cache.read().unwrap().overlay().is_none());
6918 }
6919
6920 #[tokio::test]
6928 async fn deep_reorg_overlay_disables_flat_kv_fast_paths() {
6929 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6930
6931 {
6935 let mut tx = backend.begin_write().unwrap();
6936 tx.put(MISC_VALUES, "last_written".as_bytes(), &[0xff])
6937 .unwrap();
6938 tx.commit().unwrap();
6939 }
6940
6941 let dir = tempfile::tempdir().unwrap();
6942 let store = Store::from_backend(
6943 backend.clone(),
6944 dir.path().to_path_buf(),
6945 1,
6946 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6947 )
6948 .unwrap();
6949
6950 let address = Address::from_low_u64_be(0xbeef);
6955 let account_hash = hash_address_fixed(&address);
6956 let slot = H256::from_low_u64_be(1);
6957 let slot_hash = hash_key_fixed(&slot);
6958 let generator_account = AccountState {
6959 nonce: 7,
6960 balance: U256::from(999u64),
6961 storage_root: H256::zero(),
6962 code_hash: H256::zero(),
6963 };
6964 let generator_slot_value = U256::from(42u64);
6965 {
6966 let mut tx = backend.begin_write().unwrap();
6967 tx.put(
6968 ACCOUNT_FLATKEYVALUE,
6969 &Nibbles::from_bytes(account_hash.as_bytes()).into_vec(),
6970 &generator_account.encode_to_vec(),
6971 )
6972 .unwrap();
6973 tx.put(
6974 STORAGE_FLATKEYVALUE,
6975 &apply_prefix(Some(account_hash), Nibbles::from_bytes(&slot_hash)).into_vec(),
6976 &generator_slot_value.encode_to_vec(),
6977 )
6978 .unwrap();
6979 tx.commit().unwrap();
6980 }
6981
6982 let pivot_root = EMPTY_TRIE_HASH;
6987 {
6988 let mut tx = backend.begin_write().unwrap();
6989 for (n, parent_root) in [(10u64, pivot_root), (11, H256::repeat_byte(0x10))] {
6990 let entry = JournalEntry {
6991 block_hash: H256::repeat_byte(n as u8),
6992 parent_state_root: parent_root,
6993 account_trie_diff: vec![],
6994 storage_trie_diff: vec![],
6995 account_flat_diff: vec![],
6996 storage_flat_diff: vec![],
6997 };
6998 tx.put(STATE_HISTORY, &n.to_be_bytes(), &entry.encode())
6999 .unwrap();
7000 }
7001 tx.commit().unwrap();
7002 }
7003
7004 store
7005 .install_overlay_for_reorg(11, 10, |_| None)
7006 .expect("overlay install must succeed");
7007
7008 let accounts = store
7011 .get_account_states_batch_by_root(pivot_root, &[address])
7012 .unwrap();
7013 assert_eq!(
7014 accounts,
7015 vec![None],
7016 "batch account read must not serve the generator's stale flat-KV value"
7017 );
7018
7019 let slot_value = store
7020 .get_storage_at_root(pivot_root, address, slot)
7021 .unwrap();
7022 assert_eq!(
7023 slot_value, None,
7024 "storage read must not serve the generator's stale flat-KV value"
7025 );
7026
7027 let batched_slots = store
7028 .get_storage_values_batch_by_root(
7029 pivot_root,
7030 &[(hash_address_fixed(&address), EMPTY_TRIE_HASH, slot)],
7031 )
7032 .unwrap();
7033 assert_eq!(
7034 batched_slots,
7035 vec![None],
7036 "batch storage read must not serve the generator's stale flat-KV value"
7037 );
7038
7039 let state_trie = store.open_state_trie(pivot_root).unwrap();
7040 assert_eq!(
7041 state_trie.get(account_hash.as_bytes()).unwrap(),
7042 None,
7043 "trie read must not serve the generator's stale flat-KV value"
7044 );
7045
7046 let unserved_root = H256::repeat_byte(0x99);
7049 let accounts = store
7050 .get_account_states_batch_by_root(unserved_root, &[address])
7051 .unwrap();
7052 assert_eq!(
7053 accounts,
7054 vec![Some(generator_account)],
7055 "unserved roots must keep the flat-KV fast path"
7056 );
7057 let slot_value = store
7058 .get_storage_at_root(unserved_root, address, slot)
7059 .unwrap();
7060 assert_eq!(
7061 slot_value,
7062 Some(generator_slot_value),
7063 "unserved roots must keep the flat-KV fast path"
7064 );
7065
7066 let batched_slots = store
7067 .get_storage_values_batch_by_root(
7068 unserved_root,
7069 &[(hash_address_fixed(&address), EMPTY_TRIE_HASH, slot)],
7070 )
7071 .unwrap();
7072 assert_eq!(
7073 batched_slots,
7074 vec![Some(generator_slot_value)],
7075 "unserved roots must keep the flat-KV fast path"
7076 );
7077 }
7078
7079 #[tokio::test]
7087 async fn overlay_backed_commit_only_commits_bottom_layer_per_pass() {
7088 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
7089 let dir = tempfile::tempdir().unwrap();
7090 let store = Store::from_backend(
7091 backend.clone(),
7092 dir.path().to_path_buf(),
7093 4,
7094 DEFAULT_PERSIST_CHANNEL_CAPACITY,
7095 )
7096 .unwrap();
7097
7098 let pivot_root = H256::repeat_byte(0x09);
7101 {
7102 let mut tx = backend.begin_write().unwrap();
7103 for (n, parent_root) in [(10u64, pivot_root), (11, H256::repeat_byte(0x0a))] {
7104 let entry = JournalEntry {
7105 block_hash: H256::repeat_byte(n as u8),
7106 parent_state_root: parent_root,
7107 account_trie_diff: vec![(vec![0x00, n as u8], None)],
7108 storage_trie_diff: vec![],
7109 account_flat_diff: vec![],
7110 storage_flat_diff: vec![],
7111 };
7112 tx.put(STATE_HISTORY, &n.to_be_bytes(), &entry.encode())
7113 .unwrap();
7114 }
7115 tx.commit().unwrap();
7116 }
7117 store.install_overlay_for_reorg(11, 10, |_| None).unwrap();
7118
7119 let roots: Vec<H256> = (10..=12u8).map(H256::repeat_byte).collect();
7121 {
7122 let mut guard = store.trie_cache.write().unwrap();
7123 let mut updated = (**guard).clone();
7124 let mut parent = pivot_root;
7125 for (i, root) in roots.iter().enumerate() {
7126 let n = 10 + i as u64;
7127 updated.put_batch(
7128 parent,
7129 *root,
7130 n,
7131 H256::repeat_byte(n as u8),
7132 vec![(Nibbles::from_raw(&[0x01, n as u8], false), vec![n as u8])],
7133 );
7134 parent = *root;
7135 }
7136 *guard = Arc::new(updated);
7137 }
7138
7139 let trie = store.trie_cache.read().unwrap().clone();
7142 commit_to_disk(
7143 store.backend.as_ref(),
7144 &store.flatkeyvalue_control_tx,
7145 &store.trie_cache,
7146 &trie,
7147 roots[2],
7148 false,
7149 )
7150 .expect("bottom-layer commit must succeed (no debug_assert panic)");
7151
7152 assert!(
7154 !store.is_state_in_layer_cache(roots[0]).unwrap(),
7155 "bottom layer must be committed and pruned"
7156 );
7157 assert!(
7158 store.is_state_in_layer_cache(roots[1]).unwrap()
7159 && store.is_state_in_layer_cache(roots[2]).unwrap(),
7160 "upper layers must remain resident after the bottom-layer-only commit"
7161 );
7162 assert!(
7164 store.trie_cache.read().unwrap().overlay().is_none(),
7165 "reconciliation must clear the overlay"
7166 );
7167 assert!(
7169 journal_entry_exists(&backend, 10),
7170 "bottom layer T must be journaled"
7171 );
7172 assert!(!journal_entry_exists(&backend, 11));
7173 assert!(!journal_entry_exists(&backend, 12));
7174
7175 let entry_bytes = backend
7180 .begin_read()
7181 .unwrap()
7182 .get(STATE_HISTORY, &10u64.to_be_bytes())
7183 .unwrap()
7184 .expect("journal entry for block 10");
7185 let entry = JournalEntry::decode(&entry_bytes).unwrap();
7186 let diff_keys: Vec<&Vec<u8>> = entry.account_trie_diff.iter().map(|(k, _)| k).collect();
7187 assert!(
7188 diff_keys.contains(&&vec![0x00, 10]) && diff_keys.contains(&&vec![0x00, 11]),
7189 "bridge keys from the overlay must be folded into T's journal entry, got {diff_keys:?}"
7190 );
7191
7192 let trie = store.trie_cache.read().unwrap().clone();
7194 commit_to_disk(
7195 store.backend.as_ref(),
7196 &store.flatkeyvalue_control_tx,
7197 &store.trie_cache,
7198 &trie,
7199 roots[2],
7200 false,
7201 )
7202 .unwrap();
7203 assert!(!store.is_state_in_layer_cache(roots[1]).unwrap());
7204 assert!(!store.is_state_in_layer_cache(roots[2]).unwrap());
7205 assert!(journal_entry_exists(&backend, 11));
7206 assert!(journal_entry_exists(&backend, 12));
7207 }
7208
7209 #[tokio::test]
7214 async fn journal_pruning_pauses_during_reorg_and_catches_up_after() {
7215 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
7216 let dir = tempfile::tempdir().unwrap();
7217 let store = Store::from_backend(
7218 backend.clone(),
7219 dir.path().to_path_buf(),
7220 1,
7221 DEFAULT_PERSIST_CHANNEL_CAPACITY,
7222 )
7223 .unwrap();
7224
7225 seed_journal_entries(&backend, &(1..=5).collect::<Vec<_>>());
7226
7227 store.set_journal_pruning_paused(true);
7229 store
7230 .forkchoice_update_inner(vec![], 100, H256::zero(), None, Some(3))
7231 .await
7232 .unwrap();
7233 for n in 1..=5 {
7234 assert!(
7235 journal_entry_exists(&backend, n),
7236 "entry {n} must survive pruning while the reorg pause is held"
7237 );
7238 }
7239
7240 store.set_journal_pruning_paused(false);
7242 store
7243 .forkchoice_update_inner(vec![], 100, H256::zero(), None, Some(4))
7244 .await
7245 .unwrap();
7246 for n in 1..=4 {
7247 assert!(
7248 !journal_entry_exists(&backend, n),
7249 "entry {n} must be pruned once the pause is released"
7250 );
7251 }
7252 assert!(journal_entry_exists(&backend, 5));
7253 }
7254}
7255
7256#[cfg(test)]
7257mod merge_tests {
7258 use super::*;
7259
7260 fn h256(b: u8) -> H256 {
7261 H256::from_low_u64_be(b as u64)
7262 }
7263
7264 fn op(bn: BlockNumber, bh: H256, idx: Index) -> Vec<u8> {
7265 encode_tx_location_operand(bn, bh, idx)
7266 }
7267
7268 fn decode(v: &[u8]) -> Vec<(BlockNumber, BlockHash, Index)> {
7269 <Vec<(BlockNumber, BlockHash, Index)>>::decode(v).unwrap()
7270 }
7271
7272 #[test]
7273 fn single_operand_on_empty_base() {
7274 let out = tx_locations_merge(None, vec![op(100, h256(0x10), 0)]).unwrap();
7275 assert_eq!(decode(&out), vec![(100, h256(0x10), 0)]);
7276 }
7277
7278 #[test]
7279 fn operand_appended_to_existing_base() {
7280 let base = vec![(100u64, h256(0x10), 0u64)].encode_to_vec();
7281 let out = tx_locations_merge(Some(&base), vec![op(101, h256(0x11), 5)]).unwrap();
7282 let mut got = decode(&out);
7283 got.sort();
7284 let mut want = vec![(100, h256(0x10), 0), (101, h256(0x11), 5)];
7285 want.sort();
7286 assert_eq!(got, want);
7287 }
7288
7289 #[test]
7290 fn multiple_operands_combined() {
7291 let out = tx_locations_merge(
7292 None,
7293 vec![
7294 op(100, h256(0x10), 0),
7295 op(100, h256(0x11), 1),
7296 op(101, h256(0x12), 2),
7297 ],
7298 )
7299 .unwrap();
7300 assert_eq!(decode(&out).len(), 3);
7301 }
7302
7303 #[test]
7304 fn same_block_hash_is_deduped() {
7305 let out =
7307 tx_locations_merge(None, vec![op(100, h256(0x10), 0), op(100, h256(0x10), 7)]).unwrap();
7308 assert_eq!(decode(&out), vec![(100, h256(0x10), 7)]);
7309 }
7310
7311 #[test]
7312 fn malformed_operand_aborts_merge() {
7313 let out = tx_locations_merge(None, vec![vec![0xff, 0xff], op(100, h256(0x10), 0)]);
7316 assert!(out.is_none(), "merge must abort on a malformed operand");
7317 }
7318
7319 #[test]
7320 fn malformed_base_value_aborts_merge() {
7321 let out = tx_locations_merge(Some(&[0xff, 0xff]), vec![op(100, h256(0x10), 0)]);
7322 assert!(out.is_none(), "merge must abort on a corrupt base value");
7323 }
7324
7325 #[test]
7332 fn partial_merge_result_is_a_valid_operand() {
7333 let partial =
7335 tx_locations_merge(None, vec![op(100, h256(0x10), 0), op(101, h256(0x11), 1)]).unwrap();
7336
7337 let base = vec![(99u64, h256(0x09), 9u64)].encode_to_vec();
7341 let out = tx_locations_merge(Some(&base), vec![partial]).unwrap();
7342
7343 let mut got = decode(&out);
7344 got.sort();
7345 let mut want = vec![
7346 (99, h256(0x09), 9),
7347 (100, h256(0x10), 0),
7348 (101, h256(0x11), 1),
7349 ];
7350 want.sort();
7351 assert_eq!(
7352 got, want,
7353 "no entries may be lost when re-merging a partial result"
7354 );
7355 }
7356
7357 #[test]
7360 fn operand_encoding_matches_value_encoding() {
7361 let operand = op(100, h256(0x10), 3);
7362 assert_eq!(decode(&operand), vec![(100, h256(0x10), 3)]);
7364 }
7365
7366 #[test]
7368 fn chained_partial_merges() {
7369 let p1 = tx_locations_merge(None, vec![op(1, h256(0x01), 0)]).unwrap();
7370 let p2 = tx_locations_merge(None, vec![p1, op(2, h256(0x02), 0)]).unwrap();
7371 let p3 = tx_locations_merge(None, vec![p2, op(3, h256(0x03), 0)]).unwrap();
7372 let out = tx_locations_merge(None, vec![p3]).unwrap();
7373 assert_eq!(decode(&out).len(), 3);
7374 }
7375}
7376
7377#[cfg(test)]
7378mod backfill_write_tests {
7379 use super::*;
7380 use ethrex_common::types::{EIP1559Transaction, TxType};
7381
7382 fn test_header(number: BlockNumber) -> BlockHeader {
7383 BlockHeader {
7384 number,
7385 ..Default::default()
7386 }
7387 }
7388
7389 fn test_tx(nonce: u64) -> Transaction {
7390 Transaction::EIP1559Transaction(EIP1559Transaction {
7391 nonce,
7392 ..Default::default()
7393 })
7394 }
7395
7396 fn test_receipt() -> Receipt {
7397 Receipt {
7398 tx_type: TxType::EIP1559,
7399 succeeded: true,
7400 cumulative_gas_used: 21_000,
7401 logs: vec![],
7402 payer: None,
7403 frame_receipts: None,
7404 }
7405 }
7406
7407 #[tokio::test]
7411 async fn writes_data_indexes_txs_and_lowers_frontier() {
7412 let store = Store::new("", EngineType::InMemory).expect("in-memory store");
7413 store.update_earliest_block_number(100).await.unwrap();
7414
7415 let header = test_header(99);
7416 let hash = header.hash();
7417 let tx = test_tx(7);
7418 let body = BlockBody {
7419 transactions: vec![tx.clone()],
7420 ommers: vec![],
7421 withdrawals: Some(vec![]),
7422 };
7423 store.add_block_headers(vec![header.clone()]).await.unwrap();
7427 store
7428 .forkchoice_update(vec![(99, hash)], 99, hash, None, None)
7429 .await
7430 .unwrap();
7431
7432 store
7433 .add_backfilled_blocks(
7434 vec![BackfilledBlock {
7435 header,
7436 body,
7437 receipts: vec![test_receipt()],
7438 index_transactions: true,
7439 }],
7440 99,
7441 )
7442 .await
7443 .unwrap();
7444
7445 assert_eq!(
7446 store.get_earliest_block_number().await.unwrap(),
7447 99,
7448 "frontier must be lowered atomically with the data write"
7449 );
7450 assert!(
7451 store.get_block_body_by_hash(hash).await.unwrap().is_some(),
7452 "body must be stored"
7453 );
7454 assert_eq!(
7455 store.get_receipts_for_block(&hash).await.unwrap().len(),
7456 1,
7457 "receipts must be stored"
7458 );
7459 assert_eq!(
7460 store
7461 .get_transaction_location(tx.hash(&NativeCrypto))
7462 .await
7463 .unwrap(),
7464 Some((99, hash, 0)),
7465 "indexed transaction must be locatable"
7466 );
7467 }
7468
7469 #[tokio::test]
7473 async fn skips_tx_index_when_disabled() {
7474 let store = Store::new("", EngineType::InMemory).expect("in-memory store");
7475 store.update_earliest_block_number(100).await.unwrap();
7476
7477 let header = test_header(99);
7478 let hash = header.hash();
7479 let tx = test_tx(7);
7480 let body = BlockBody {
7481 transactions: vec![tx.clone()],
7482 ommers: vec![],
7483 withdrawals: Some(vec![]),
7484 };
7485 store.add_block_headers(vec![header.clone()]).await.unwrap();
7486 store
7487 .forkchoice_update(vec![(99, hash)], 99, hash, None, None)
7488 .await
7489 .unwrap();
7490
7491 store
7492 .add_backfilled_blocks(
7493 vec![BackfilledBlock {
7494 header,
7495 body,
7496 receipts: vec![test_receipt()],
7497 index_transactions: false,
7498 }],
7499 99,
7500 )
7501 .await
7502 .unwrap();
7503
7504 assert!(
7505 store.get_block_body_by_hash(hash).await.unwrap().is_some(),
7506 "body must still be stored"
7507 );
7508 assert_eq!(
7509 store.get_receipts_for_block(&hash).await.unwrap().len(),
7510 1,
7511 "receipts must still be stored"
7512 );
7513 assert_eq!(
7514 store
7515 .get_transaction_location(tx.hash(&NativeCrypto))
7516 .await
7517 .unwrap(),
7518 None,
7519 "transaction index must be skipped when index_transactions is false"
7520 );
7521 }
7522
7523 #[tokio::test]
7527 async fn historical_getters_transition_from_null_to_present() {
7528 let store = Store::new("", EngineType::InMemory).expect("in-memory store");
7529 store.update_earliest_block_number(100).await.unwrap();
7530
7531 let header = test_header(99);
7532 let hash = header.hash();
7533 let tx = test_tx(7);
7534 let tx_hash = tx.hash(&NativeCrypto);
7535 let body = BlockBody {
7536 transactions: vec![tx],
7537 ommers: vec![],
7538 withdrawals: Some(vec![]),
7539 };
7540 store.add_block_headers(vec![header.clone()]).await.unwrap();
7542 store
7543 .forkchoice_update(vec![(99, hash)], 99, hash, None, None)
7544 .await
7545 .unwrap();
7546
7547 assert!(
7549 store.get_block_body(99).await.unwrap().is_none(),
7550 "body absent before backfill"
7551 );
7552 assert!(
7553 store
7554 .get_transaction_by_hash(tx_hash)
7555 .await
7556 .unwrap()
7557 .is_none(),
7558 "tx unlocatable before backfill"
7559 );
7560 assert!(
7561 store.get_receipt(99, 0).await.unwrap().is_none(),
7562 "receipt absent before backfill"
7563 );
7564
7565 store
7566 .add_backfilled_blocks(
7567 vec![BackfilledBlock {
7568 header,
7569 body,
7570 receipts: vec![test_receipt()],
7571 index_transactions: true,
7572 }],
7573 99,
7574 )
7575 .await
7576 .unwrap();
7577
7578 assert!(
7580 store.get_block_body(99).await.unwrap().is_some(),
7581 "body present after backfill"
7582 );
7583 assert!(
7584 store
7585 .get_transaction_by_hash(tx_hash)
7586 .await
7587 .unwrap()
7588 .is_some(),
7589 "tx locatable after backfill"
7590 );
7591 assert!(
7592 store.get_receipt(99, 0).await.unwrap().is_some(),
7593 "receipt present after backfill"
7594 );
7595 }
7596
7597 #[tokio::test]
7600 async fn resume_fills_contiguously_without_gaps() {
7601 let store = Store::new("", EngineType::InMemory).expect("in-memory store");
7602 store.update_earliest_block_number(100).await.unwrap();
7603
7604 let make_batch = |range: std::ops::Range<u64>| -> Vec<BackfilledBlock> {
7605 range
7606 .rev()
7607 .map(|n| BackfilledBlock {
7608 header: test_header(n),
7609 body: BlockBody {
7610 transactions: vec![],
7611 ommers: vec![],
7612 withdrawals: Some(vec![]),
7613 },
7614 receipts: vec![],
7615 index_transactions: false,
7616 })
7617 .collect()
7618 };
7619
7620 store
7622 .add_backfilled_blocks(make_batch(90..100), 90)
7623 .await
7624 .unwrap();
7625 assert_eq!(store.get_earliest_block_number().await.unwrap(), 90);
7626
7627 let resume_from = store.get_earliest_block_number().await.unwrap();
7629 assert_eq!(resume_from, 90, "resume reads the persisted frontier");
7630
7631 store
7633 .add_backfilled_blocks(make_batch(80..90), 80)
7634 .await
7635 .unwrap();
7636 assert_eq!(store.get_earliest_block_number().await.unwrap(), 80);
7637
7638 for n in 80..100 {
7640 assert!(
7641 store
7642 .get_block_body_by_hash(test_header(n).hash())
7643 .await
7644 .unwrap()
7645 .is_some(),
7646 "block {n} must have a body (no gap left by resume)"
7647 );
7648 }
7649 }
7650}
7651
7652#[cfg(test)]
7655#[cfg(feature = "rocksdb")]
7656mod schema_version_guard_tests {
7657 use super::*;
7658
7659 fn write_marker(dir: &Path, schema_version: u64) {
7660 let metadata = StoreMetadata::new(schema_version);
7661 std::fs::write(
7662 dir.join(STORE_METADATA_FILENAME),
7663 serde_json::to_string_pretty(&metadata).unwrap(),
7664 )
7665 .unwrap();
7666 }
7667
7668 fn read_marker(dir: &Path) -> u64 {
7669 read_store_schema_version(dir).unwrap().unwrap()
7670 }
7671
7672 #[test]
7673 fn a_database_ahead_of_the_binary_is_refused_and_left_untouched() {
7674 let dir = tempfile::tempdir().unwrap();
7680 let newer = STORE_SCHEMA_VERSION + 1;
7681 write_marker(dir.path(), newer);
7682
7683 let result =
7684 Store::new_with_config(dir.path(), EngineType::RocksDB, StoreConfig::default());
7685
7686 assert!(
7687 matches!(
7688 result,
7689 Err(StoreError::IncompatibleDBVersion { found, expected })
7690 if found == newer && expected == STORE_SCHEMA_VERSION
7691 ),
7692 "expected IncompatibleDBVersion, got {:?}",
7693 result.err()
7694 );
7695 assert_eq!(read_marker(dir.path()), newer);
7696 }
7697
7698 #[test]
7699 fn a_zero_schema_version_is_incompatible_not_a_failed_migration() {
7700 let dir = tempfile::tempdir().unwrap();
7703 write_marker(dir.path(), 0);
7704
7705 let result =
7706 Store::new_with_config(dir.path(), EngineType::RocksDB, StoreConfig::default());
7707
7708 assert!(
7709 matches!(
7710 result,
7711 Err(StoreError::IncompatibleDBVersion { found: 0, expected })
7712 if expected == STORE_SCHEMA_VERSION
7713 ),
7714 "expected IncompatibleDBVersion, got {:?}",
7715 result.err()
7716 );
7717 }
7718}
7719
7720#[cfg(test)]
7721mod datadir_tests {
7722 use super::*;
7723 use std::fs;
7724
7725 #[test]
7726 fn empty_dir_has_no_existing_db() {
7727 let dir = tempfile::tempdir().unwrap();
7728 assert!(!dir_contains_legacy_db(dir.path()).unwrap());
7729 }
7730
7731 #[test]
7732 fn dir_with_only_unrelated_files_has_no_existing_db() {
7733 let dir = tempfile::tempdir().unwrap();
7736 fs::write(dir.path().join("jwt.hex"), "0xdeadbeef").unwrap();
7737 fs::write(dir.path().join("LOG"), "noise").unwrap();
7738 assert!(!dir_contains_legacy_db(dir.path()).unwrap());
7739 }
7740
7741 #[test]
7742 fn dir_with_rocksdb_markers_has_existing_db() {
7743 let dir = tempfile::tempdir().unwrap();
7745 fs::write(dir.path().join("CURRENT"), "MANIFEST-000001\n").unwrap();
7746 assert!(dir_contains_legacy_db(dir.path()).unwrap());
7747
7748 let dir2 = tempfile::tempdir().unwrap();
7749 fs::write(dir2.path().join("MANIFEST-000007"), "x").unwrap();
7750 assert!(dir_contains_legacy_db(dir2.path()).unwrap());
7751 }
7752
7753 #[test]
7754 fn dir_with_marker_named_subdirectories_has_no_existing_db() {
7755 let dir = tempfile::tempdir().unwrap();
7758 fs::create_dir(dir.path().join("CURRENT")).unwrap();
7759 fs::create_dir(dir.path().join("MANIFEST-000001")).unwrap();
7760 assert!(!dir_contains_legacy_db(dir.path()).unwrap());
7761 }
7762}
7763
7764#[cfg(test)]
7765mod flatkeyvalue_completeness_tests {
7766 use super::*;
7767
7768 #[test]
7773 fn only_the_one_byte_ff_sentinel_counts_as_complete() {
7774 assert!(Store::flatkeyvalue_generation_complete(Some(&[0xff])));
7776
7777 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])));
7785 assert!(!Store::flatkeyvalue_generation_complete(Some(&[
7786 0xff, 0xff
7787 ])));
7788 }
7789}
7790
7791#[cfg(test)]
7792mod cgroup_memory_limit_tests {
7793 use super::*;
7794 use std::fs;
7795
7796 fn cgroup_tree(root: &Path, relative: &str, file: &str, limits: &[(&str, &str)]) {
7799 fs::create_dir_all(root.join(relative)).expect("create cgroup tree");
7800 for (dir, contents) in limits {
7801 fs::write(root.join(dir).join(file), contents).expect("write limit");
7802 }
7803 }
7804
7805 #[test]
7809 fn ancestor_limit_applies() {
7810 let tmp = tempfile::tempdir().expect("tempdir");
7811 let root = tmp.path();
7812 cgroup_tree(
7813 root,
7814 "system.slice/ethrex.service",
7815 "memory.max",
7816 &[("", "max"), ("system.slice", "4294967296")],
7817 );
7818
7819 assert_eq!(
7820 cgroup_limit_up_to_root(root, "/system.slice/ethrex.service", "memory.max"),
7821 Some(4 * 1024 * 1024 * 1024)
7822 );
7823 }
7824
7825 #[test]
7827 fn smallest_limit_in_the_chain_wins() {
7828 let tmp = tempfile::tempdir().expect("tempdir");
7829 let root = tmp.path();
7830 cgroup_tree(
7831 root,
7832 "kubepods/pod123/container",
7833 "memory.max",
7834 &[
7835 ("kubepods", "8589934592"),
7836 ("kubepods/pod123", "2147483648"),
7837 ("kubepods/pod123/container", "4294967296"),
7838 ],
7839 );
7840
7841 assert_eq!(
7842 cgroup_limit_up_to_root(root, "kubepods/pod123/container", "memory.max"),
7843 Some(2 * 1024 * 1024 * 1024)
7844 );
7845 }
7846
7847 #[test]
7850 fn unlimited_chain_yields_no_limit() {
7851 let tmp = tempfile::tempdir().expect("tempdir");
7852 let root = tmp.path();
7853 cgroup_tree(root, "docker/abc", "memory.max", &[("", "max")]);
7854
7855 assert_eq!(
7856 cgroup_limit_up_to_root(root, "docker/abc", "memory.max"),
7857 None
7858 );
7859 assert_eq!(
7862 cgroup_limit_up_to_root(root, "not/here", "memory.max"),
7863 None
7864 );
7865 }
7866
7867 #[test]
7869 fn root_limit_applies_to_the_root_cgroup() {
7870 let tmp = tempfile::tempdir().expect("tempdir");
7871 let root = tmp.path();
7872 cgroup_tree(root, "", "memory.limit_in_bytes", &[("", "536870912")]);
7873
7874 assert_eq!(
7875 cgroup_limit_up_to_root(root, "/", "memory.limit_in_bytes"),
7876 Some(512 * 1024 * 1024)
7877 );
7878 }
7879}