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 key = block_hash.as_bytes().to_vec();
3068 let mut value = vec![];
3069 bal.encode(&mut value);
3070 self.write(BLOCK_ACCESS_LISTS, key, value)
3071 }
3072
3073 pub fn get_block_access_list(
3075 &self,
3076 block_hash: BlockHash,
3077 ) -> Result<Option<BlockAccessList>, StoreError> {
3078 let key = block_hash.as_bytes().to_vec();
3079 match self.read(BLOCK_ACCESS_LISTS, key)? {
3080 Some(value) => {
3081 let bal = BlockAccessList::decode(&value)
3082 .map_err(|e| StoreError::Custom(format!("Failed to decode BAL: {e}")))?;
3083 Ok(Some(bal))
3084 }
3085 None => Ok(None),
3086 }
3087 }
3088
3089 pub async fn add_initial_state(&mut self, genesis: Genesis) -> Result<(), StoreError> {
3090 self.add_initial_state_inner(genesis, false).await
3091 }
3092
3093 pub async fn add_initial_state_skip_validation(
3104 &mut self,
3105 genesis: Genesis,
3106 ) -> Result<(), StoreError> {
3107 self.add_initial_state_inner(genesis, true).await
3108 }
3109
3110 async fn add_initial_state_inner(
3111 &mut self,
3112 genesis: Genesis,
3113 skip_genesis_validation: bool,
3114 ) -> Result<(), StoreError> {
3115 debug!("Storing initial state from genesis");
3116
3117 let genesis_block = genesis.get_block();
3119 let genesis_block_number = genesis_block.header.number;
3120
3121 let genesis_hash = genesis_block.hash();
3122
3123 let stored_genesis_header = self.load_block_header(genesis_block_number)?;
3124
3125 self.set_chain_config(&genesis.config).await?;
3132
3133 if let Some(latest) = self.load_latest_block_number().await? {
3138 self.anchor_to_durable_head(latest).await?;
3139 }
3140
3141 match stored_genesis_header {
3142 Some(header) if skip_genesis_validation => {
3143 info!(
3144 stored_genesis = %header.hash(),
3145 "Skipping genesis state validation; trusting the genesis header and state already stored in the datadir"
3146 );
3147 return Ok(());
3148 }
3149 Some(header) if header.hash() == genesis_hash => {
3150 info!("Received genesis file matching a previously stored one, nothing to do");
3151 return Ok(());
3152 }
3153 Some(_) => {
3154 error!(
3155 "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."
3156 );
3157 return Err(StoreError::IncompatibleChainConfig);
3158 }
3159 None => {
3160 self.add_block_header(genesis_hash, genesis_block.header.clone())
3161 .await?
3162 }
3163 }
3164 let genesis_state_root = self.setup_genesis_state_trie(genesis.alloc).await?;
3167 debug_assert_eq!(genesis_state_root, genesis_block.header.state_root);
3168
3169 info!(hash = %genesis_hash, "Storing genesis block");
3171
3172 self.add_block(genesis_block).await?;
3173 self.update_earliest_block_number(genesis_block_number)
3174 .await?;
3175 self.forkchoice_update(vec![], genesis_block_number, genesis_hash, None, None)
3176 .await?;
3177 Ok(())
3178 }
3179
3180 pub async fn load_initial_state(&self) -> Result<(), StoreError> {
3181 info!("Loading initial state from DB");
3182 let Some(latest) = self.load_latest_block_number().await? else {
3183 return Err(StoreError::MissingLatestBlockNumber);
3184 };
3185 self.anchor_to_durable_head(latest).await?;
3189 Ok(())
3190 }
3191
3192 pub fn get_storage_at(
3193 &self,
3194 block_number: BlockNumber,
3195 address: Address,
3196 storage_key: H256,
3197 ) -> Result<Option<U256>, StoreError> {
3198 match self.get_block_header(block_number)? {
3199 Some(header) => self.get_storage_at_root(header.state_root, address, storage_key),
3200 None => Ok(None),
3201 }
3202 }
3203
3204 pub fn get_storage_at_root(
3205 &self,
3206 state_root: H256,
3207 address: Address,
3208 storage_key: H256,
3209 ) -> Result<Option<U256>, StoreError> {
3210 let account_hash = hash_address_fixed(&address);
3211
3212 let read_view = self.backend.begin_read()?;
3214 let cache = self.gated_snapshot(state_root)?;
3215 let last_written = self.last_written()?;
3216 let use_fkv = Self::flatkeyvalue_computed_with_last_written(account_hash, &last_written)
3223 && !cache.overlay_serves(state_root);
3224
3225 let storage_root = if use_fkv {
3226 EMPTY_TRIE_HASH
3228 } else {
3229 let state_trie = self.open_state_trie_shared(
3230 state_root,
3231 read_view.clone(),
3232 cache.clone(),
3233 last_written.clone(),
3234 )?;
3235 let Some(encoded_account) = state_trie.get(account_hash.as_bytes())? else {
3236 return Ok(None);
3237 };
3238 let account = AccountState::decode(&encoded_account)?;
3239 account.storage_root
3240 };
3241 let storage_trie = self.open_storage_trie_shared(
3242 account_hash,
3243 state_root,
3244 storage_root,
3245 read_view,
3246 cache,
3247 last_written,
3248 )?;
3249
3250 let hashed_key = hash_key_fixed(&storage_key);
3251 storage_trie
3252 .get(&hashed_key)?
3253 .map(|rlp| U256::decode(&rlp).map_err(StoreError::RLPDecode))
3254 .transpose()
3255 }
3256
3257 pub fn get_storage_at_root_with_known_storage_root(
3262 &self,
3263 state_root: H256,
3264 account_hash: H256,
3265 storage_root: H256,
3266 storage_key: H256,
3267 ) -> Result<Option<U256>, StoreError> {
3268 let read_view = self.backend.begin_read()?;
3269 let cache = self.gated_snapshot(state_root)?;
3270 let last_written = self.last_written()?;
3271 let storage_root =
3278 if Self::flatkeyvalue_computed_with_last_written(account_hash, &last_written)
3279 && !cache.overlay_serves(state_root)
3280 {
3281 EMPTY_TRIE_HASH
3282 } else {
3283 storage_root
3284 };
3285 let storage_trie = self.open_storage_trie_shared(
3286 account_hash,
3287 state_root,
3288 storage_root,
3289 read_view,
3290 cache,
3291 last_written,
3292 )?;
3293
3294 let hashed_key = hash_key_fixed(&storage_key);
3295 storage_trie
3296 .get(&hashed_key)?
3297 .map(|rlp| U256::decode(&rlp).map_err(StoreError::RLPDecode))
3298 .transpose()
3299 }
3300
3301 pub fn get_chain_config(&self) -> ChainConfig {
3302 self.chain_config
3303 }
3304
3305 pub async fn get_latest_canonical_block_hash(&self) -> Result<Option<BlockHash>, StoreError> {
3306 Ok(Some(self.latest_block_header.get().hash()))
3307 }
3308
3309 pub async fn forkchoice_update(
3314 &self,
3315 new_canonical_blocks: Vec<(BlockNumber, BlockHash)>,
3316 head_number: BlockNumber,
3317 head_hash: BlockHash,
3318 safe: Option<BlockNumber>,
3319 finalized: Option<BlockNumber>,
3320 ) -> Result<(), StoreError> {
3321 let _guard = self.fcu_lock.lock().await;
3327
3328 let previous_head = self.latest_block_header.get();
3333 let new_head = self
3334 .get_block_header_by_hash(head_hash)?
3335 .ok_or_else(|| StoreError::MissingLatestBlockNumber)?;
3336 self.latest_block_header.update(new_head);
3337 if let Err(err) = self
3338 .forkchoice_update_inner(
3339 new_canonical_blocks,
3340 head_number,
3341 head_hash,
3342 safe,
3343 finalized,
3344 )
3345 .await
3346 {
3347 self.latest_block_header.update((*previous_head).clone());
3348 return Err(err);
3349 }
3350
3351 if let Some(root) = self.compute_safe_commit_root(head_number)? {
3357 if self.set_safe_commit_root(root)? {
3362 let tx = self.persist_tx.clone();
3363 let _ = tokio::task::spawn_blocking(move || tx.send(PersistMessage::Commit(root)))
3364 .await;
3365 }
3366 }
3367
3368 Ok(())
3369 }
3370
3371 pub(crate) fn set_safe_commit_root(&self, root: H256) -> Result<bool, StoreError> {
3379 let mut guard = self
3380 .safe_commit_root
3381 .write()
3382 .map_err(|_| StoreError::LockError)?;
3383 let changed = *guard != root;
3384 *guard = root;
3385 Ok(changed)
3386 }
3387
3388 pub(crate) fn compute_safe_commit_root(
3397 &self,
3398 head: BlockNumber,
3399 ) -> Result<Option<H256>, StoreError> {
3400 let commit_threshold = self
3401 .trie_cache
3402 .read()
3403 .map_err(|_| StoreError::LockError)?
3404 .commit_threshold;
3405 let Some(target) = head.checked_sub(commit_threshold as u64) else {
3406 return Ok(None);
3407 };
3408 let Some(hash) = self.get_canonical_block_hash_sync(target)? else {
3409 return Ok(None);
3410 };
3411 let Some(header) = self.get_block_header_by_hash(hash)? else {
3412 return Ok(None);
3413 };
3414 Ok(Some(header.state_root))
3415 }
3416
3417 pub fn state_trie(&self, block_hash: BlockHash) -> Result<Option<Trie>, StoreError> {
3419 let Some(header) = self.get_block_header_by_hash(block_hash)? else {
3420 return Ok(None);
3421 };
3422 Ok(Some(self.open_state_trie(header.state_root)?))
3423 }
3424
3425 pub fn storage_trie(
3427 &self,
3428 block_hash: BlockHash,
3429 address: Address,
3430 ) -> Result<Option<Trie>, StoreError> {
3431 let Some(header) = self.get_block_header_by_hash(block_hash)? else {
3432 return Ok(None);
3433 };
3434 let Some(state_trie) = self.state_trie(block_hash)? else {
3436 return Ok(None);
3437 };
3438 let hashed_address = hash_address_fixed(&address);
3439 let Some(encoded_account) = state_trie.get(hashed_address.as_bytes())? else {
3440 return Ok(None);
3441 };
3442 let account = AccountState::decode(&encoded_account)?;
3443 let storage_root = account.storage_root;
3445 Ok(Some(self.open_storage_trie(
3446 hashed_address,
3447 header.state_root,
3448 storage_root,
3449 )?))
3450 }
3451
3452 pub async fn get_account_state(
3453 &self,
3454 block_number: BlockNumber,
3455 address: Address,
3456 ) -> Result<Option<AccountState>, StoreError> {
3457 let Some(block_hash) = self.get_canonical_block_hash(block_number).await? else {
3458 return Ok(None);
3459 };
3460 let Some(state_trie) = self.state_trie(block_hash)? else {
3461 return Ok(None);
3462 };
3463 self.get_account_state_from_trie(&state_trie, address)
3464 }
3465
3466 pub fn get_account_state_by_root(
3467 &self,
3468 state_root: H256,
3469 address: Address,
3470 ) -> Result<Option<AccountState>, StoreError> {
3471 let state_trie = self.open_state_trie(state_root)?;
3472 self.get_account_state_from_trie(&state_trie, address)
3473 }
3474
3475 pub fn get_account_state_from_trie(
3476 &self,
3477 state_trie: &Trie,
3478 address: Address,
3479 ) -> Result<Option<AccountState>, StoreError> {
3480 let hashed_address = hash_address_fixed(&address);
3481 let Some(encoded_state) = state_trie.get(hashed_address.as_bytes())? else {
3482 return Ok(None);
3483 };
3484 Ok(Some(AccountState::decode(&encoded_state)?))
3485 }
3486
3487 pub fn get_account_states_batch_by_root(
3496 &self,
3497 state_root: H256,
3498 addresses: &[Address],
3499 ) -> Result<Vec<Option<AccountState>>, StoreError> {
3500 if addresses.is_empty() {
3501 return Ok(Vec::new());
3502 }
3503
3504 let last_written = self.last_written()?;
3505 let trie_cache = self
3506 .trie_cache
3507 .read()
3508 .map_err(|_| StoreError::LockError)?
3509 .clone();
3510
3511 let mut results: Vec<Option<AccountState>> = vec![None; addresses.len()];
3512 let leaf_paths: Vec<Vec<u8>> = addresses
3514 .iter()
3515 .map(|addr| {
3516 let hashed = hash_address_fixed(addr);
3517 Nibbles::from_bytes(hashed.as_bytes()).into_vec()
3518 })
3519 .collect();
3520
3521 let mut fkv_indices: Vec<usize> = Vec::new();
3522 let mut trie_indices: Vec<usize> = Vec::new();
3523
3524 let overlay_active = trie_cache.overlay_serves(state_root);
3540 let fkv_cursor: &[u8] = last_written.as_slice();
3541 for (i, path) in leaf_paths.iter().enumerate() {
3542 if let Some(value) = trie_cache.get(state_root, path.as_slice()) {
3543 if !value.is_empty() {
3544 results[i] = Some(AccountState::decode(&value)?);
3545 }
3546 continue;
3547 }
3548 if !overlay_active && fkv_cursor >= path.as_slice() {
3549 fkv_indices.push(i);
3550 } else {
3551 trie_indices.push(i);
3552 }
3553 }
3554
3555 if !fkv_indices.is_empty() {
3556 fkv_indices.sort_unstable_by(|&a, &b| leaf_paths[a].cmp(&leaf_paths[b]));
3563 let read_view = self.backend.begin_read()?;
3564 let keys: Vec<&[u8]> = fkv_indices
3565 .iter()
3566 .map(|&i| leaf_paths[i].as_slice())
3567 .collect();
3568 let shards = shard_count(keys.len());
3569 let raw: Vec<Result<Option<Vec<u8>>, StoreError>> = if shards <= 1 {
3570 read_view.multi_get(ACCOUNT_FLATKEYVALUE, &keys)
3571 } else {
3572 let chunk = keys.len().div_ceil(shards);
3573 let rv = read_view.as_ref();
3574 std::thread::scope(|scope| {
3575 let handles: Vec<_> = keys
3576 .chunks(chunk)
3577 .map(|ck| scope.spawn(move || rv.multi_get(ACCOUNT_FLATKEYVALUE, ck)))
3578 .collect();
3579 handles
3580 .into_iter()
3581 .flat_map(|h| {
3587 h.join().unwrap_or_else(|_| {
3588 vec![Err(StoreError::Custom(
3589 "account prefetch shard panicked".into(),
3590 ))]
3591 })
3592 })
3593 .collect()
3594 })
3595 };
3596 for (slot, res) in fkv_indices.iter().zip(raw.into_iter()) {
3597 let Some(encoded) = res? else { continue };
3598 if encoded.is_empty() {
3599 continue;
3600 }
3601 results[*slot] = Some(AccountState::decode(&encoded)?);
3602 }
3603 }
3604
3605 if !trie_indices.is_empty() {
3606 let state_trie = self.open_state_trie(state_root)?;
3612 let fetched: Result<Vec<(usize, Option<AccountState>)>, StoreError> = trie_indices
3613 .par_iter()
3614 .map(|&i| {
3615 self.get_account_state_from_trie(&state_trie, addresses[i])
3616 .map(|s| (i, s))
3617 })
3618 .collect();
3619 for (i, s) in fetched? {
3620 results[i] = s;
3621 }
3622 }
3623
3624 Ok(results)
3625 }
3626
3627 pub fn get_storage_values_batch_by_root(
3647 &self,
3648 state_root: H256,
3649 slots: &[(H256, H256, H256)],
3650 ) -> Result<Vec<Option<U256>>, StoreError> {
3651 if slots.is_empty() {
3652 return Ok(Vec::new());
3653 }
3654
3655 let last_written = self.last_written()?;
3656 let trie_cache = self
3657 .trie_cache
3658 .read()
3659 .map_err(|_| StoreError::LockError)?
3660 .clone();
3661
3662 let mut results: Vec<Option<U256>> = vec![None; slots.len()];
3663 let leaf_paths: Vec<Vec<u8>> = slots
3669 .iter()
3670 .map(|(account_hash, _storage_root, slot)| {
3671 let hashed_slot = hash_key_fixed(slot);
3672 apply_prefix(Some(*account_hash), Nibbles::from_bytes(&hashed_slot)).into_vec()
3673 })
3674 .collect();
3675
3676 let mut fkv_indices: Vec<usize> = Vec::new();
3677 let mut trie_indices: Vec<usize> = Vec::new();
3678
3679 let overlay_active = trie_cache.overlay_serves(state_root);
3693 let fkv_cursor: &[u8] = last_written.as_slice();
3694 for (i, path) in leaf_paths.iter().enumerate() {
3695 if let Some(value) = trie_cache.get(state_root, path.as_slice()) {
3700 if !value.is_empty() {
3701 results[i] = Some(U256::decode(&value).map_err(StoreError::RLPDecode)?);
3702 }
3703 continue;
3704 }
3705 if !overlay_active && fkv_cursor >= path.as_slice() {
3706 fkv_indices.push(i);
3707 } else {
3708 trie_indices.push(i);
3709 }
3710 }
3711
3712 if !fkv_indices.is_empty() {
3713 fkv_indices.sort_unstable_by(|&a, &b| leaf_paths[a].cmp(&leaf_paths[b]));
3718 let read_view = self.backend.begin_read()?;
3719 let keys: Vec<&[u8]> = fkv_indices
3720 .iter()
3721 .map(|&i| leaf_paths[i].as_slice())
3722 .collect();
3723 let shards = shard_count(keys.len());
3736 let raw: Vec<Result<Option<Vec<u8>>, StoreError>> = if shards <= 1 {
3737 read_view.multi_get(STORAGE_FLATKEYVALUE, &keys)
3738 } else {
3739 let chunk = keys.len().div_ceil(shards);
3740 let rv = read_view.as_ref();
3741 std::thread::scope(|scope| {
3742 let handles: Vec<_> = keys
3743 .chunks(chunk)
3744 .map(|ck| scope.spawn(move || rv.multi_get(STORAGE_FLATKEYVALUE, ck)))
3745 .collect();
3746 handles
3747 .into_iter()
3748 .flat_map(|h| {
3754 h.join().unwrap_or_else(|_| {
3755 vec![Err(StoreError::Custom(
3756 "storage prefetch shard panicked".into(),
3757 ))]
3758 })
3759 })
3760 .collect()
3761 })
3762 };
3763 for (slot_idx, res) in fkv_indices.iter().zip(raw.into_iter()) {
3764 let Some(encoded) = res? else { continue };
3765 if encoded.is_empty() {
3766 continue;
3767 }
3768 results[*slot_idx] = Some(U256::decode(&encoded).map_err(StoreError::RLPDecode)?);
3769 }
3770 }
3771
3772 if !trie_indices.is_empty() {
3773 let fetched: Result<Vec<(usize, Option<U256>)>, StoreError> = trie_indices
3781 .par_iter()
3782 .map(|&i| {
3783 let (account_hash, storage_root, slot) = slots[i];
3784 self.get_storage_at_root_with_known_storage_root(
3785 state_root,
3786 account_hash,
3787 storage_root,
3788 slot,
3789 )
3790 .map(|v| (i, v))
3791 })
3792 .collect();
3793 for (i, v) in fetched? {
3794 results[i] = v;
3795 }
3796 }
3797
3798 Ok(results)
3799 }
3800
3801 pub fn prefetch_trie_nodes(
3828 &self,
3829 storage_slots: &[(Address, H256)],
3830 accounts: &[Address],
3831 ) -> Result<usize, StoreError> {
3832 const MAX_PREFETCH_DEPTH: usize = 8;
3837
3838 let mut storage_keys: Vec<Vec<u8>> =
3841 Vec::with_capacity(storage_slots.len() * (MAX_PREFETCH_DEPTH + 1));
3842 for (address, slot) in storage_slots {
3843 let hashed_address = hash_address_fixed(address);
3844 let hashed_slot = hash_key_fixed(slot);
3845 let slot_nibbles = Nibbles::from_bytes(&hashed_slot);
3846 let max_d = MAX_PREFETCH_DEPTH.min(slot_nibbles.len());
3847 for d in 0..=max_d {
3848 let prefix = apply_prefix(Some(hashed_address), slot_nibbles.slice(0, d));
3849 storage_keys.push(prefix.into_vec());
3850 }
3851 }
3852
3853 let mut account_keys: Vec<Vec<u8>> =
3855 Vec::with_capacity(accounts.len() * (MAX_PREFETCH_DEPTH + 1));
3856 for address in accounts {
3857 let hashed_address = hash_address_fixed(address);
3858 let addr_nibbles = Nibbles::from_bytes(hashed_address.as_bytes());
3859 let max_d = MAX_PREFETCH_DEPTH.min(addr_nibbles.len());
3860 for d in 0..=max_d {
3861 account_keys.push(addr_nibbles.slice(0, d).into_vec());
3862 }
3863 }
3864
3865 let hits = self.warm_trie_node_keys(STORAGE_TRIE_NODES, storage_keys)?
3866 + self.warm_trie_node_keys(ACCOUNT_TRIE_NODES, account_keys)?;
3867 Ok(hits)
3868 }
3869
3870 fn warm_trie_node_keys(
3877 &self,
3878 table: &'static str,
3879 mut keys: Vec<Vec<u8>>,
3880 ) -> Result<usize, StoreError> {
3881 if keys.is_empty() {
3882 return Ok(0);
3883 }
3884 keys.sort_unstable();
3885 keys.dedup();
3886
3887 let shards = shard_count(keys.len());
3888 let read_view = self.backend.begin_read()?;
3889 let count_hits = |res: Vec<Result<Option<Vec<u8>>, StoreError>>| {
3890 res.into_iter().filter(|r| matches!(r, Ok(Some(_)))).count()
3891 };
3892 if shards <= 1 {
3893 let refs: Vec<&[u8]> = keys.iter().map(|k| k.as_slice()).collect();
3894 return Ok(count_hits(read_view.multi_get(table, &refs)));
3895 }
3896 let chunk = keys.len().div_ceil(shards);
3897 let rv = read_view.as_ref();
3898 let hits = std::thread::scope(|scope| {
3899 let handles: Vec<_> = keys
3900 .chunks(chunk)
3901 .map(|ck| {
3902 scope.spawn(move || {
3903 let refs: Vec<&[u8]> = ck.iter().map(|k| k.as_slice()).collect();
3904 count_hits(rv.multi_get(table, &refs))
3906 })
3907 })
3908 .collect();
3909 handles
3910 .into_iter()
3911 .map(|h| h.join().unwrap_or(0))
3916 .sum::<usize>()
3917 });
3918 Ok(hits)
3919 }
3920
3921 pub async fn get_account_proof(
3926 &self,
3927 state_root: H256,
3928 address: Address,
3929 storage_keys: &[H256],
3930 ) -> Result<Option<AccountProof>, StoreError> {
3931 let state_trie = self.open_state_trie(state_root)?;
3936 let address_path = hash_address_fixed(&address);
3937 let proof = state_trie.get_proof(address_path.as_bytes())?;
3938 let account_opt = state_trie
3939 .get(address_path.as_bytes())?
3940 .map(|encoded_state| AccountState::decode(&encoded_state))
3941 .transpose()?;
3942
3943 let mut storage_proof = Vec::with_capacity(storage_keys.len());
3944
3945 if let Some(account) = &account_opt {
3946 let storage_trie =
3947 self.open_storage_trie(address_path, state_root, account.storage_root)?;
3948
3949 for key in storage_keys {
3950 let hashed_key = hash_key(key);
3951 let proof = storage_trie.get_proof(&hashed_key)?;
3952 let value = storage_trie
3953 .get(&hashed_key)?
3954 .map(|rlp| U256::decode(&rlp).map_err(StoreError::RLPDecode))
3955 .transpose()?
3956 .unwrap_or_default();
3957
3958 let slot_proof = StorageSlotProof {
3959 proof,
3960 key: *key,
3961 value,
3962 };
3963 storage_proof.push(slot_proof);
3964 }
3965 } else {
3966 storage_proof.extend(storage_keys.iter().map(|key| StorageSlotProof {
3967 proof: Vec::new(),
3968 key: *key,
3969 value: U256::zero(),
3970 }));
3971 }
3972 let account = account_opt.unwrap_or_default();
3973 let account_proof = AccountProof {
3974 proof,
3975 account,
3976 storage_proof,
3977 };
3978 Ok(Some(account_proof))
3979 }
3980
3981 pub fn iter_accounts_from(
3984 &self,
3985 state_root: H256,
3986 starting_address: H256,
3987 ) -> Result<impl Iterator<Item = (H256, AccountState)>, StoreError> {
3988 let mut iter = self.open_locked_state_trie(state_root)?.into_iter();
3989 iter.advance(starting_address.0.to_vec())?;
3990 Ok(iter.content().map_while(|(path, value)| {
3991 Some((H256::from_slice(&path), AccountState::decode(&value).ok()?))
3992 }))
3993 }
3994
3995 pub fn iter_accounts(
3998 &self,
3999 state_root: H256,
4000 ) -> Result<impl Iterator<Item = (H256, AccountState)>, StoreError> {
4001 self.iter_accounts_from(state_root, H256::zero())
4002 }
4003
4004 pub fn iter_storage_from(
4007 &self,
4008 state_root: H256,
4009 hashed_address: H256,
4010 starting_slot: H256,
4011 ) -> Result<Option<impl Iterator<Item = (H256, U256)>>, StoreError> {
4012 let state_trie = self.open_locked_state_trie(state_root)?;
4013 let Some(account_rlp) = state_trie.get(hashed_address.as_bytes())? else {
4014 return Ok(None);
4015 };
4016 let storage_root = AccountState::decode(&account_rlp)?.storage_root;
4017 let mut iter = self
4018 .open_locked_storage_trie(hashed_address, state_root, storage_root)?
4019 .into_iter();
4020 iter.advance(starting_slot.0.to_vec())?;
4021 Ok(Some(iter.content().map_while(|(path, value)| {
4022 Some((H256::from_slice(&path), U256::decode(&value).ok()?))
4023 })))
4024 }
4025
4026 pub fn iter_storage(
4029 &self,
4030 state_root: H256,
4031 hashed_address: H256,
4032 ) -> Result<Option<impl Iterator<Item = (H256, U256)>>, StoreError> {
4033 self.iter_storage_from(state_root, hashed_address, H256::zero())
4034 }
4035
4036 pub fn get_account_range_proof(
4037 &self,
4038 state_root: H256,
4039 starting_hash: H256,
4040 last_hash: Option<H256>,
4041 ) -> Result<Vec<Vec<u8>>, StoreError> {
4042 let state_trie = self.open_state_trie(state_root)?;
4043 let mut proof = state_trie.get_proof(starting_hash.as_bytes())?;
4044 if let Some(last_hash) = last_hash {
4045 proof.extend_from_slice(&state_trie.get_proof(last_hash.as_bytes())?);
4046 }
4047 Ok(proof)
4048 }
4049
4050 pub fn get_storage_range_proof(
4051 &self,
4052 state_root: H256,
4053 hashed_address: H256,
4054 starting_hash: H256,
4055 last_hash: Option<H256>,
4056 ) -> Result<Option<Vec<Vec<u8>>>, StoreError> {
4057 let state_trie = self.open_state_trie(state_root)?;
4058 let Some(account_rlp) = state_trie.get(hashed_address.as_bytes())? else {
4059 return Ok(None);
4060 };
4061 let storage_root = AccountState::decode(&account_rlp)?.storage_root;
4062 let storage_trie = self.open_storage_trie(hashed_address, state_root, storage_root)?;
4063 let mut proof = storage_trie.get_proof(starting_hash.as_bytes())?;
4064 if let Some(last_hash) = last_hash {
4065 proof.extend_from_slice(&storage_trie.get_proof(last_hash.as_bytes())?);
4066 }
4067 Ok(Some(proof))
4068 }
4069
4070 pub fn get_trie_nodes(
4077 &self,
4078 state_root: H256,
4079 paths: Vec<Vec<u8>>,
4080 byte_limit: u64,
4081 ) -> Result<Vec<Vec<u8>>, StoreError> {
4082 let Some(account_path) = paths.first() else {
4083 return Ok(vec![]);
4084 };
4085 let state_trie = self.open_state_trie(state_root)?;
4086 if paths.len() == 1 {
4088 let node = state_trie.get_node(account_path)?;
4090 return Ok(vec![node]);
4091 }
4092 let Some(account_state) = state_trie
4094 .get(account_path)?
4095 .map(|ref rlp| AccountState::decode(rlp))
4096 .transpose()?
4097 else {
4098 return Ok(vec![]);
4099 };
4100 let Ok(hashed_address) = account_path.clone().try_into().map(H256) else {
4102 return Ok(vec![]);
4103 };
4104 let storage_trie =
4105 self.open_storage_trie(hashed_address, state_root, account_state.storage_root)?;
4106 let mut nodes = vec![];
4108 let mut bytes_used = 0;
4109 for path in paths.iter().skip(1) {
4113 if bytes_used >= byte_limit {
4114 break;
4115 }
4116 let node = storage_trie.get_node(path)?;
4117 bytes_used += node.len() as u64;
4118 nodes.push(node);
4119 }
4120 Ok(nodes)
4121 }
4122
4123 fn gated_snapshot(&self, state_root: H256) -> Result<Arc<TrieLayerCache>, StoreError> {
4132 self.pending_trie_roots.wait_until_ready(state_root)?;
4133 Ok(self
4134 .trie_cache
4135 .read()
4136 .map_err(|_| StoreError::LockError)?
4137 .clone())
4138 }
4139
4140 pub fn open_state_trie(&self, state_root: H256) -> Result<Trie, StoreError> {
4144 let trie_db = TrieWrapper::new(
4145 state_root,
4146 self.gated_snapshot(state_root)?,
4147 Box::new(BackendTrieDB::new_for_accounts(
4148 self.backend.clone(),
4149 self.last_written()?,
4150 )?),
4151 None,
4152 );
4153 Ok(Trie::open(Box::new(trie_db), state_root))
4154 }
4155
4156 pub fn open_direct_state_trie(&self, state_root: H256) -> Result<Trie, StoreError> {
4160 Ok(Trie::open(
4161 Box::new(BackendTrieDB::new_for_accounts(
4162 self.backend.clone(),
4163 self.last_written()?,
4164 )?),
4165 state_root,
4166 ))
4167 }
4168
4169 pub fn open_locked_state_trie(&self, state_root: H256) -> Result<Trie, StoreError> {
4173 let trie_db = TrieWrapper::new(
4174 state_root,
4175 self.gated_snapshot(state_root)?,
4176 Box::new(state_trie_locked_backend(
4177 self.backend.as_ref(),
4178 self.last_written()?,
4179 )?),
4180 None,
4181 );
4182 Ok(Trie::open(Box::new(trie_db), state_root))
4183 }
4184
4185 pub fn open_storage_trie(
4188 &self,
4189 account_hash: H256,
4190 state_root: H256,
4191 storage_root: H256,
4192 ) -> Result<Trie, StoreError> {
4193 let trie_db = TrieWrapper::new(
4194 state_root,
4195 self.gated_snapshot(state_root)?,
4196 Box::new(BackendTrieDB::new_for_storages(
4197 self.backend.clone(),
4198 self.last_written()?,
4199 )?),
4200 Some(account_hash),
4201 );
4202 Ok(Trie::open(Box::new(trie_db), storage_root))
4203 }
4204
4205 fn open_state_trie_shared(
4209 &self,
4210 state_root: H256,
4211 read_view: Arc<dyn StorageReadView>,
4212 cache: Arc<TrieLayerCache>,
4213 last_written: Vec<u8>,
4214 ) -> Result<Trie, StoreError> {
4215 let trie_db = TrieWrapper::new(
4216 state_root,
4217 cache,
4218 Box::new(BackendTrieDB::new_for_accounts_with_view(
4219 self.backend.clone(),
4220 read_view,
4221 last_written,
4222 )?),
4223 None,
4224 );
4225 Ok(Trie::open(Box::new(trie_db), state_root))
4226 }
4227
4228 fn open_storage_trie_shared(
4230 &self,
4231 account_hash: H256,
4232 state_root: H256,
4233 storage_root: H256,
4234 read_view: Arc<dyn StorageReadView>,
4235 cache: Arc<TrieLayerCache>,
4236 last_written: Vec<u8>,
4237 ) -> Result<Trie, StoreError> {
4238 let trie_db = TrieWrapper::new(
4239 state_root,
4240 cache,
4241 Box::new(BackendTrieDB::new_for_storages_with_view(
4242 self.backend.clone(),
4243 read_view,
4244 last_written,
4245 )?),
4246 Some(account_hash),
4247 );
4248 Ok(Trie::open(Box::new(trie_db), storage_root))
4249 }
4250
4251 pub fn open_direct_storage_trie(
4254 &self,
4255 account_hash: H256,
4256 storage_root: H256,
4257 ) -> Result<Trie, StoreError> {
4258 Ok(Trie::open(
4259 Box::new(BackendTrieDB::new_for_account_storage(
4260 self.backend.clone(),
4261 account_hash,
4262 self.last_written()?,
4263 )?),
4264 storage_root,
4265 ))
4266 }
4267
4268 pub fn open_locked_storage_trie(
4271 &self,
4272 account_hash: H256,
4273 state_root: H256,
4274 storage_root: H256,
4275 ) -> Result<Trie, StoreError> {
4276 let trie_db = TrieWrapper::new(
4277 state_root,
4278 self.gated_snapshot(state_root)?,
4279 Box::new(state_trie_locked_backend(
4280 self.backend.as_ref(),
4281 self.last_written()?,
4282 )?),
4283 Some(account_hash),
4284 );
4285 Ok(Trie::open(Box::new(trie_db), storage_root))
4286 }
4287
4288 pub fn has_state_root(&self, state_root: H256) -> Result<bool, StoreError> {
4289 if state_root == EMPTY_TRIE_HASH {
4291 return Ok(true);
4292 }
4293 let trie = self.open_state_trie(state_root)?;
4294 let Some(root) = trie.db().get(Nibbles::default())? else {
4296 return Ok(false);
4297 };
4298 let root_hash = ethrex_trie::Node::decode(&root)?
4299 .compute_hash(&NativeCrypto)
4300 .finalize(&NativeCrypto);
4301 Ok(state_root == root_hash)
4302 }
4303
4304 pub fn is_state_in_layer_cache(&self, state_root: H256) -> Result<bool, StoreError> {
4313 let trie = self
4314 .trie_cache
4315 .read()
4316 .map_err(|_| StoreError::LockError)?
4317 .clone();
4318 Ok(trie.contains(state_root))
4319 }
4320
4321 pub fn highest_state_history_block_number(&self) -> Result<Option<BlockNumber>, StoreError> {
4327 let read = self.backend.begin_read()?;
4328 let Some(key) = read.last_key(STATE_HISTORY)? else {
4329 return Ok(None);
4330 };
4331 let arr = <[u8; 8]>::try_from(key.as_slice()).map_err(|_| {
4332 StoreError::Custom(format!(
4333 "STATE_HISTORY key has unexpected length: {}",
4334 key.len()
4335 ))
4336 })?;
4337 Ok(Some(BlockNumber::from_be_bytes(arr)))
4338 }
4339
4340 pub fn get_state_history_block_hash(
4347 &self,
4348 block_number: BlockNumber,
4349 ) -> Result<Option<BlockHash>, StoreError> {
4350 let read = self.backend.begin_read()?;
4351 let Some(bytes) = read.get(STATE_HISTORY, &block_number.to_be_bytes())? else {
4352 return Ok(None);
4353 };
4354 JournalEntry::decode_block_hash(&bytes)
4355 .map(Some)
4356 .map_err(|e| StoreError::Custom(format!("STATE_HISTORY entry {block_number}: {e}")))
4357 }
4358
4359 pub fn lowest_state_history_block_number(&self) -> Result<Option<BlockNumber>, StoreError> {
4364 let read = self.backend.begin_read()?;
4365 let Some(key) = read.first_key(STATE_HISTORY)? else {
4366 return Ok(None);
4367 };
4368 let arr = <[u8; 8]>::try_from(key.as_slice()).map_err(|_| {
4369 StoreError::Custom(format!(
4370 "STATE_HISTORY key has unexpected length: {}",
4371 key.len()
4372 ))
4373 })?;
4374 Ok(Some(BlockNumber::from_be_bytes(arr)))
4375 }
4376
4377 pub fn reorg_retention(&self) -> Result<u64, StoreError> {
4382 let cache = self.trie_cache.read().map_err(|_| StoreError::LockError)?;
4383 Ok(cache.commit_threshold() as u64)
4384 }
4385
4386 #[doc(hidden)]
4390 pub fn put_state_history_entry_for_test(
4391 &self,
4392 block_number: BlockNumber,
4393 encoded: &[u8],
4394 ) -> Result<(), StoreError> {
4395 let mut tx = self.backend.begin_write()?;
4396 tx.put(STATE_HISTORY, &block_number.to_be_bytes(), encoded)?;
4397 tx.commit()?;
4398 Ok(())
4399 }
4400
4401 pub fn install_overlay_for_reorg(
4422 &self,
4423 from_block: BlockNumber,
4424 to_block: BlockNumber,
4425 expected_hash: impl Fn(BlockNumber) -> Option<H256>,
4426 ) -> Result<(), StoreError> {
4427 let overlay =
4429 Overlay::from_journal(self.backend.as_ref(), from_block, to_block, expected_hash)
4430 .map_err(|e| StoreError::Custom(format!("overlay construction failed: {e}")))?;
4431
4432 let threshold = {
4433 let current = self.trie_cache.read().map_err(|_| StoreError::LockError)?;
4434 current.commit_threshold()
4435 };
4436 let mut fresh =
4439 TrieLayerCache::new_with_safe_commit(threshold, self.safe_commit_root.clone());
4440 fresh.set_overlay(Arc::new(overlay));
4441
4442 self.rendezvous_persist_worker("install_overlay_for_reorg")?;
4445
4446 let mut guard = self.trie_cache.write().map_err(|_| StoreError::LockError)?;
4447 *guard = Arc::new(fresh);
4448 Ok(())
4449 }
4450
4451 fn rendezvous_persist_worker(&self, caller: &str) -> Result<(), StoreError> {
4464 let (ack_tx, ack_rx) = sync_channel::<Result<(), StoreError>>(1);
4465 self.persist_tx
4466 .send(PersistMessage::Ping(ack_tx))
4467 .map_err(|e| {
4468 StoreError::Custom(format!("{caller}: failed to ping persist worker: {e}"))
4469 })?;
4470 ack_rx.recv().map_err(|e| {
4471 StoreError::Custom(format!("{caller}: persist worker ping ack failed: {e}"))
4472 })??;
4473 Ok(())
4474 }
4475
4476 pub fn reorg_overlay_size_hint(&self) -> Result<(usize, usize), StoreError> {
4481 let guard = self.trie_cache.read().map_err(|_| StoreError::LockError)?;
4482 match guard.overlay() {
4483 Some(ov) => Ok((ov.len(), ov.byte_size())),
4484 None => Ok((0, 0)),
4485 }
4486 }
4487
4488 pub fn set_journal_pruning_paused(&self, paused: bool) {
4492 self.journal_pruning_paused
4493 .store(paused, std::sync::atomic::Ordering::Release);
4494 }
4495
4496 pub fn clear_reorg_overlay(&self) -> Result<(), StoreError> {
4500 let mut guard = self.trie_cache.write().map_err(|_| StoreError::LockError)?;
4501 let mut updated = (**guard).clone();
4502 updated.clear_overlay();
4503 *guard = Arc::new(updated);
4504 Ok(())
4505 }
4506
4507 pub fn abort_reorg(&self) -> Result<(), StoreError> {
4513 self.rendezvous_persist_worker("abort_reorg")?;
4521 let mut guard = self.trie_cache.write().map_err(|_| StoreError::LockError)?;
4522 let threshold = guard.commit_threshold();
4523 *guard = Arc::new(TrieLayerCache::new_with_safe_commit(
4524 threshold,
4525 self.safe_commit_root.clone(),
4526 ));
4527 Ok(())
4528 }
4529
4530 pub fn ancestors(&self, block_hash: BlockHash) -> AncestorIterator {
4533 AncestorIterator {
4534 store: self.clone(),
4535 next_hash: block_hash,
4536 }
4537 }
4538
4539 pub fn is_canonical_sync(&self, block_hash: BlockHash) -> Result<bool, StoreError> {
4541 let Some(block_number) = self.get_block_number_sync(block_hash)? else {
4542 return Ok(false);
4543 };
4544 Ok(self
4545 .get_canonical_block_hash_sync(block_number)?
4546 .is_some_and(|h| h == block_hash))
4547 }
4548
4549 pub fn generate_flatkeyvalue(&self) -> Result<(), StoreError> {
4550 self.flatkeyvalue_control_tx
4551 .send(FKVGeneratorControlMessage::Continue)
4552 .map_err(|_| StoreError::Custom("FlatKeyValue thread disconnected.".to_string()))
4553 }
4554
4555 pub fn create_checkpoint(&self, path: impl AsRef<Path>) -> Result<(), StoreError> {
4556 self.backend.create_checkpoint(path.as_ref())?;
4557 init_metadata_file(path.as_ref())?;
4558 Ok(())
4559 }
4560
4561 pub fn get_store_directory(&self) -> Result<PathBuf, StoreError> {
4562 Ok(self.db_path.clone())
4563 }
4564
4565 async fn load_latest_block_number(&self) -> Result<Option<BlockNumber>, StoreError> {
4567 self.read_chain_data_block_number(ChainDataIndex::LatestBlockNumber)
4568 .await
4569 }
4570
4571 fn load_canonical_block_hash(
4572 &self,
4573 block_number: BlockNumber,
4574 ) -> Result<Option<BlockHash>, StoreError> {
4575 let txn = self.backend.begin_read()?;
4576 txn.get(
4577 CANONICAL_BLOCK_HASHES,
4578 block_number.to_le_bytes().as_slice(),
4579 )?
4580 .map(|bytes| H256::decode(bytes.as_slice()))
4581 .transpose()
4582 .map_err(StoreError::from)
4583 }
4584
4585 fn load_block_header(
4586 &self,
4587 block_number: BlockNumber,
4588 ) -> Result<Option<BlockHeader>, StoreError> {
4589 let Some(block_hash) = self.load_canonical_block_hash(block_number)? else {
4590 return Ok(None);
4591 };
4592 self.load_block_header_by_hash(block_hash)
4593 }
4594
4595 fn load_block_header_by_hash(
4597 &self,
4598 block_hash: BlockHash,
4599 ) -> Result<Option<BlockHeader>, StoreError> {
4600 let txn = self.backend.begin_read()?;
4601 let hash_key = block_hash.encode_to_vec();
4602 let header_value = txn.get(HEADERS, hash_key.as_slice())?;
4603 let mut header = header_value
4604 .map(|bytes| BlockHeaderRLP::from_bytes(bytes).to())
4605 .transpose()
4606 .map_err(StoreError::from)?;
4607 header.as_mut().inspect(|h| {
4608 let _ = h.hash.set(block_hash);
4610 });
4611 Ok(header)
4612 }
4613
4614 pub fn last_written(&self) -> Result<Vec<u8>, StoreError> {
4615 let last_computed_flatkeyvalue = self
4616 .last_computed_flatkeyvalue
4617 .read()
4618 .map_err(|_| StoreError::LockError)?;
4619 Ok(last_computed_flatkeyvalue.clone())
4620 }
4621
4622 pub fn flatkeyvalue_fully_generated(&self) -> Result<bool, StoreError> {
4633 let tx = self.backend.begin_read()?;
4634 let marker = tx.get(MISC_VALUES, "last_written".as_bytes())?;
4635 Ok(Self::flatkeyvalue_generation_complete(marker.as_deref()))
4636 }
4637
4638 fn flatkeyvalue_generation_complete(marker: Option<&[u8]>) -> bool {
4642 marker == Some([0xff].as_slice())
4643 }
4644
4645 fn flatkeyvalue_computed_with_last_written(account: H256, last_written: &[u8]) -> bool {
4646 let account_nibbles = Nibbles::from_bytes(account.as_bytes());
4647 &last_written[0..64] > account_nibbles.as_ref()
4648 }
4649
4650 pub fn advance_flushed_upto(&self, block_number: BlockNumber) -> Result<(), StoreError> {
4663 if self
4664 .read_flushed_upto_opt()?
4665 .is_some_and(|current| current >= block_number)
4666 {
4667 return Ok(());
4668 }
4669 let mut tx = self.backend.begin_write()?;
4670 write_flushed_upto(tx.as_mut(), block_number)?;
4671 tx.commit()?;
4672 mutate_block_buffer(&self.block_data_buffer, |b| {
4673 b.set_flushed_upto(block_number)
4674 })
4675 }
4676
4677 pub fn read_flushed_upto(&self) -> Result<BlockNumber, StoreError> {
4682 Ok(self.read_flushed_upto_opt()?.unwrap_or(0))
4683 }
4684
4685 fn read_flushed_upto_opt(&self) -> Result<Option<BlockNumber>, StoreError> {
4688 let tx = self.backend.begin_read()?;
4689 match tx.get(MISC_VALUES, FLUSHED_UPTO_KEY)? {
4690 Some(bytes) => Ok(Some(decode_flushed_upto(&bytes)?)),
4691 None => Ok(None),
4692 }
4693 }
4694
4695 #[cfg(any(test, feature = "testing"))]
4698 pub fn buffer_block_for_test(&self, block: &Block) {
4699 mutate_block_buffer(&self.block_data_buffer, |b| {
4700 b.insert(block.clone(), vec![], vec![])
4701 })
4702 .expect("block_data_buffer lock poisoned");
4703 }
4704
4705 #[cfg(any(test, feature = "testing"))]
4708 pub fn flush_block_data_for_test(&self) -> Result<(), StoreError> {
4709 flush_block_data(self.backend.as_ref(), &self.block_data_buffer)
4710 }
4711
4712 #[cfg(any(test, feature = "testing"))]
4716 pub fn get_trie_node_for_test(
4717 &self,
4718 is_account: bool,
4719 key: &[u8],
4720 ) -> Result<Option<Vec<u8>>, StoreError> {
4721 let table = if is_account {
4722 ACCOUNT_TRIE_NODES
4723 } else {
4724 STORAGE_TRIE_NODES
4725 };
4726 self.backend.begin_read()?.get(table, key)
4727 }
4728
4729 #[cfg(any(test, feature = "testing"))]
4733 pub fn buffer_block_with_codes_for_test(&self, block: &Block, codes: Vec<(H256, Code)>) {
4734 mutate_block_buffer(&self.block_data_buffer, |b| {
4735 b.insert(block.clone(), vec![], codes)
4736 })
4737 .expect("block_data_buffer lock poisoned");
4738 }
4739
4740 #[cfg(any(test, feature = "testing"))]
4745 pub fn register_pending_root_for_test(&self, root: H256) -> Result<(), StoreError> {
4746 self.pending_trie_roots.register(root)
4747 }
4748
4749 #[cfg(any(test, feature = "testing"))]
4752 pub fn clear_pending_root_for_test(&self, root: H256) {
4753 self.pending_trie_roots.clear(root)
4754 }
4755
4756 async fn anchor_to_durable_head(&self, latest: BlockNumber) -> Result<(), StoreError> {
4775 let marker = self.read_flushed_upto_opt()?;
4776 let start = match marker {
4777 Some(flushed) => flushed.min(latest),
4778 None => latest,
4779 };
4780
4781 let mut head = start;
4782 let latest_block_header = loop {
4783 match self.load_block_header(head)? {
4784 Some(header) => break header,
4785 None if marker.is_none() => return Err(StoreError::MissingLatestBlockNumber),
4788 None if head == 0 => return Err(StoreError::MissingLatestBlockNumber),
4789 None => {
4790 warn!(
4791 "durable head {head}: canonical hash has no on-disk header \
4792 (reorg inside flush window); rewinding"
4793 );
4794 head -= 1;
4795 }
4796 }
4797 };
4798 self.latest_block_header.update(latest_block_header);
4799
4800 let reanchor = head != latest;
4805 let rewrite_marker = marker != Some(head);
4806 if reanchor || rewrite_marker {
4807 let mut tx = self.backend.begin_write()?;
4808 if reanchor {
4809 let latest_key = chain_data_key(ChainDataIndex::LatestBlockNumber);
4812 tx.put(CHAIN_DATA, &latest_key, &head.to_le_bytes())?;
4813 }
4814 if rewrite_marker {
4815 write_flushed_upto(tx.as_mut(), head)?;
4816 }
4817 tx.commit()?;
4818 }
4819 Ok(())
4820 }
4821}
4822
4823pub fn write_flushed_upto(
4827 tx: &mut dyn StorageWriteBatch,
4828 n: BlockNumber,
4829) -> Result<(), StoreError> {
4830 tx.put(MISC_VALUES, FLUSHED_UPTO_KEY, &n.to_le_bytes())
4831}
4832
4833fn decode_flushed_upto(bytes: &[u8]) -> Result<BlockNumber, StoreError> {
4839 let arr: [u8; 8] = bytes
4840 .try_into()
4841 .map_err(|_| StoreError::Custom("Invalid flushed_upto bytes".to_string()))?;
4842 Ok(BlockNumber::from_le_bytes(arr))
4843}
4844
4845fn mutate_block_buffer(
4851 buffer: &Arc<RwLock<Arc<BlockDataBuffer>>>,
4852 f: impl FnOnce(&mut BlockDataBuffer),
4853) -> Result<(), StoreError> {
4854 let mut new_buf = (*buffer.read().map_err(|_| StoreError::LockError)?.clone()).clone();
4855 f(&mut new_buf);
4856 *buffer.write().map_err(|_| StoreError::LockError)? = Arc::new(new_buf);
4857 Ok(())
4858}
4859
4860const DEFAULT_PERSIST_CHANNEL_CAPACITY: usize = 2;
4862
4863struct BlockPersist {
4869 blocks: Vec<(Block, Vec<Receipt>)>,
4870 codes: Vec<(H256, Code)>,
4871 parent_state_root: H256,
4872 child_state_root: H256,
4873 account_updates: TrieNodesUpdate,
4874 storage_updates: Vec<(H256, TrieNodesUpdate)>,
4875 commit_depth: Option<usize>,
4876 wait_for_flush: bool,
4877 block_number: BlockNumber,
4889 block_hash: H256,
4891 ack: std::sync::mpsc::SyncSender<Result<(), StoreError>>,
4892}
4893
4894enum PersistMessage {
4898 Block(Box<BlockPersist>),
4899 Commit(H256),
4905 Ping(std::sync::mpsc::SyncSender<Result<(), StoreError>>),
4906 Shutdown {
4911 ack: std::sync::mpsc::SyncSender<Result<(), StoreError>>,
4912 },
4913}
4914
4915fn write_block_data(
4920 tx: &mut dyn StorageWriteBatch,
4921 number: BlockNumber,
4922 hash: BlockHash,
4923 header: &BlockHeader,
4924 body: &BlockBody,
4925) -> Result<(), StoreError> {
4926 let hash_key = hash.encode_to_vec();
4927 tx.put(
4928 HEADERS,
4929 &hash_key,
4930 BlockHeaderRLP::from(header.clone()).bytes(),
4931 )?;
4932 tx.put(
4933 BODIES,
4934 &hash_key,
4935 BlockBodyRLP::from_bytes(body.encode_to_vec()).bytes(),
4936 )?;
4937 tx.put(BLOCK_NUMBERS, &hash_key, &number.to_le_bytes())?;
4938 for (index, transaction) in body.transactions.iter().enumerate() {
4939 tx.merge(
4940 TRANSACTION_LOCATIONS,
4941 transaction.hash(&NativeCrypto).as_bytes(),
4942 &encode_tx_location_operand(number, hash, index as u64),
4943 )?;
4944 }
4945 Ok(())
4946}
4947
4948fn flush_block_data(
4951 backend: &dyn StorageBackend,
4952 buffer: &Arc<RwLock<Arc<BlockDataBuffer>>>,
4953) -> Result<(), StoreError> {
4954 let snapshot = buffer.read().map_err(|_| StoreError::LockError)?.clone();
4955 let to_flush = snapshot.flushable();
4956 if to_flush.is_empty() {
4957 return Ok(());
4958 }
4959 let hashes: Vec<_> = to_flush.iter().map(|b| b.header.hash()).collect();
4960 let codes = snapshot.codes_for(&hashes);
4961 let mut max_number = snapshot.flushed_upto();
4962
4963 let mut tx = backend.begin_write()?;
4964 for b in &to_flush {
4965 let hash = b.header.hash();
4966 write_block_data(tx.as_mut(), b.number, hash, &b.header, &b.body)?;
4967 for (index, receipt) in b.receipts.iter().enumerate() {
4968 tx.put(
4969 RECEIPTS_V2,
4970 &receipt_key(&hash, index as u64),
4971 &receipt.encode_to_vec(),
4972 )?;
4973 }
4974 max_number = max_number.max(b.number);
4975 }
4976 for (code_hash, code) in codes {
4977 let buf = encode_code(&code);
4978 tx.put(ACCOUNT_CODES, code_hash.as_ref(), &buf)?;
4979 tx.put(
4980 ACCOUNT_CODE_METADATA,
4981 code_hash.as_ref(),
4982 &(code.len() as u64).to_be_bytes(),
4983 )?;
4984 }
4985 write_flushed_upto(tx.as_mut(), max_number)?;
4986 tx.commit()?;
4987
4988 mutate_block_buffer(buffer, |b| b.evict_flushed(max_number))
4990}
4991
4992type TrieNodesUpdate = Vec<(Nibbles, Vec<u8>)>;
4993
4994#[derive(Debug, Default)]
5000struct PendingTrieRoots {
5001 count: AtomicUsize,
5003 roots: Mutex<HashSet<H256>>,
5004 ready: Condvar,
5005}
5006
5007impl PendingTrieRoots {
5008 fn register(&self, root: H256) -> Result<(), StoreError> {
5012 let mut roots = self.roots.lock().map_err(|_| StoreError::LockError)?;
5013 if roots.insert(root) {
5014 self.count.fetch_add(1, Ordering::Release);
5015 }
5016 Ok(())
5017 }
5018
5019 fn clear(&self, root: H256) {
5024 let Ok(mut roots) = self.roots.lock() else {
5025 return;
5026 };
5027 if roots.remove(&root) {
5028 self.count.fetch_sub(1, Ordering::Release);
5029 self.ready.notify_all();
5030 }
5031 }
5032
5033 fn wait_until_ready(&self, root: H256) -> Result<(), StoreError> {
5036 if self.count.load(Ordering::Acquire) == 0 {
5037 return Ok(());
5038 }
5039 let mut roots = self.roots.lock().map_err(|_| StoreError::LockError)?;
5040 while roots.contains(&root) {
5041 roots = self.ready.wait(roots).map_err(|_| StoreError::LockError)?;
5042 }
5043 Ok(())
5044 }
5045}
5046
5047#[allow(clippy::too_many_arguments)]
5051fn apply_trie_phase1(
5052 trie_cache: &Arc<RwLock<Arc<TrieLayerCache>>>,
5053 pending_roots: &PendingTrieRoots,
5054 parent_state_root: H256,
5055 child_state_root: H256,
5056 block_number: BlockNumber,
5057 block_hash: H256,
5058 account_updates: TrieNodesUpdate,
5059 storage_updates: Vec<(H256, TrieNodesUpdate)>,
5060) -> Result<(), StoreError> {
5061 let build: Result<(), StoreError> = (|| {
5062 let new_layer = storage_updates
5063 .into_iter()
5064 .flat_map(|(account_hash, nodes)| {
5065 nodes
5066 .into_iter()
5067 .map(move |(path, node)| (apply_prefix(Some(account_hash), path), node))
5068 })
5069 .chain(account_updates)
5070 .collect();
5071 let trie = trie_cache
5072 .read()
5073 .map_err(|_| StoreError::LockError)?
5074 .clone();
5075 let mut trie_mut = (*trie).clone();
5076 trie_mut.put_batch(
5077 parent_state_root,
5078 child_state_root,
5079 block_number,
5080 block_hash,
5081 new_layer,
5082 );
5083 *trie_cache.write().map_err(|_| StoreError::LockError)? = Arc::new(trie_mut);
5084 Ok(())
5085 })();
5086 pending_roots.clear(child_state_root);
5091 build
5092}
5093
5094fn commit_trie_if_due(
5118 backend: &dyn StorageBackend,
5119 trie_cache: &Arc<RwLock<Arc<TrieLayerCache>>>,
5120 fkv_ctl: &SyncSender<FKVGeneratorControlMessage>,
5121 parent_state_root: H256,
5122 commit_depth: Option<usize>,
5123 is_batch: bool,
5124) -> Result<(), StoreError> {
5125 let trie = trie_cache
5126 .read()
5127 .map_err(|_| StoreError::LockError)?
5128 .clone();
5129 let commitable = match commit_depth {
5131 Some(depth) => trie.get_commitable_by_depth(parent_state_root, depth),
5132 None => trie.get_commitable(parent_state_root),
5133 };
5134 let Some(root) = commitable else {
5135 return Ok(());
5137 };
5138 commit_to_disk(backend, fkv_ctl, trie_cache, &trie, root, is_batch)
5139}
5140
5141fn commit_to_disk(
5151 backend: &dyn StorageBackend,
5152 fkv_ctl: &SyncSender<FKVGeneratorControlMessage>,
5153 trie_cache: &Arc<RwLock<Arc<TrieLayerCache>>>,
5154 trie: &Arc<TrieLayerCache>,
5155 root: H256,
5156 is_batch: bool,
5157) -> Result<(), StoreError> {
5158 if !trie.has_layer(root) {
5162 debug!(
5163 root = ?root,
5164 layers = trie.layer_count(),
5165 is_batch,
5166 "Skipping trie commit: state root has no in-memory layer. Expected when the block \
5167 did not change the state root (empty L2 blocks) or the root was already flushed."
5168 );
5169 return Ok(());
5170 }
5171
5172 let _ = fkv_ctl.send(FKVGeneratorControlMessage::Stop);
5175
5176 let mut trie_mut = (**trie).clone();
5178
5179 let read_view = backend.begin_read()?;
5193 let last_written = read_view
5194 .get(MISC_VALUES, "last_written".as_bytes())?
5195 .unwrap_or_default();
5196
5197 let mut write_tx = backend.begin_write()?;
5198
5199 let overlay_for_reconciliation = if !is_batch {
5209 trie.overlay().cloned()
5210 } else {
5211 None
5212 };
5213
5214 let root = if overlay_for_reconciliation.is_some() {
5224 trie.bottom_layer_root(root)
5225 } else {
5226 root
5227 };
5228
5229 let committed_layers = trie_mut.commit(root).ok_or_else(|| {
5239 StoreError::Custom(format!(
5240 "trie layer for state root {root:?} disappeared between the has_layer check and \
5241 commit (layers={}, is_batch={is_batch}): the TrieLayerCache clone lost the layer \
5242 map, so this block's state was not written",
5243 trie.layer_count(),
5244 ))
5245 })?;
5246
5247 debug_assert!(
5252 overlay_for_reconciliation.is_none() || committed_layers.len() == 1,
5253 "overlay-backed reconciliation must commit exactly one layer (T), got {}",
5254 committed_layers.len()
5255 );
5256
5257 let extra_writes: Vec<(Vec<u8>, Vec<u8>)> = match &overlay_for_reconciliation {
5263 Some(overlay) => {
5264 let layer_keys: rustc_hash::FxHashSet<&Vec<u8>> = committed_layers
5265 .iter()
5266 .flat_map(|l| l.nodes.iter().map(|(k, _)| k))
5267 .collect();
5268 overlay
5269 .iter_all_entries()
5270 .filter(|(_, key, _)| !layer_keys.contains(key))
5271 .map(|(_, key, value)| (key.clone(), value.clone().unwrap_or_default()))
5272 .collect()
5273 }
5274 None => Vec::new(),
5275 };
5276 let reorg_heights = overlay_for_reconciliation
5278 .as_ref()
5279 .map(|ov| (ov.to_block(), ov.from_block()));
5280
5281 let mut overlay: HashMap<Vec<u8>, Option<Vec<u8>>> = HashMap::new();
5291
5292 let mut result = Ok(());
5293 'layers: for layer in &committed_layers {
5294 let mut journal_account_trie: FlatDiff = Vec::new();
5302 let mut journal_storage_trie: FlatDiff = Vec::new();
5303 let mut journal_account_flat: FlatDiff = Vec::new();
5304 let mut journal_storage_flat: FlatDiff = Vec::new();
5305
5306 let is_reconciliation_layer = overlay_for_reconciliation.is_some();
5317 let extra: &[(Vec<u8>, Vec<u8>)] = if is_reconciliation_layer {
5318 &extra_writes
5319 } else {
5320 &[]
5321 };
5322
5323 let pivot_overlay = overlay_for_reconciliation.as_ref();
5331
5332 for (key, value) in layer.nodes.iter().chain(extra.iter()) {
5333 let (is_leaf, is_account) = classify_trie_key(key.len());
5334
5335 if is_leaf && key.as_slice() > last_written.as_slice() {
5340 continue;
5341 }
5342 let table = if is_leaf {
5343 if is_account {
5344 &ACCOUNT_FLATKEYVALUE
5345 } else {
5346 &STORAGE_FLATKEYVALUE
5347 }
5348 } else if is_account {
5349 &ACCOUNT_TRIE_NODES
5350 } else {
5351 &STORAGE_TRIE_NODES
5352 };
5353
5354 let prev_value = if !is_batch {
5358 match overlay.get(key) {
5359 Some(v) => Some(v.clone()),
5360 None => match pivot_overlay
5361 .and_then(|ov| ov.lookup(OverlayCf::classify_by_key_length(key.len()), key))
5362 {
5363 Some(v) => Some(v),
5364 None => match read_view.get(table, key) {
5365 Ok(v) => Some(v),
5366 Err(e) => {
5367 result = Err(e);
5368 break 'layers;
5369 }
5370 },
5371 },
5372 }
5373 } else {
5374 None
5375 };
5376
5377 let new_value = if value.is_empty() {
5378 result = write_tx.delete(table, key);
5379 None
5380 } else {
5381 result = write_tx.put(table, key, value);
5382 Some(value.clone())
5383 };
5384 if result.is_err() {
5385 break 'layers;
5386 }
5387
5388 if let Some(prev) = prev_value {
5391 let bucket = match (is_leaf, is_account) {
5392 (false, true) => &mut journal_account_trie,
5393 (false, false) => &mut journal_storage_trie,
5394 (true, true) => &mut journal_account_flat,
5395 (true, false) => &mut journal_storage_flat,
5396 };
5397 bucket.push((key.clone(), prev));
5398 overlay.insert(key.clone(), new_value);
5401 }
5402 }
5403
5404 if !is_batch && let Some((t, d)) = reorg_heights {
5409 debug_assert_eq!(
5410 layer.block_number, t,
5411 "first new-chain commit must be at the pivot's T height (overlay.to_block)"
5412 );
5413 if layer.block_number == t {
5420 let start = t.to_be_bytes();
5421 let end = d.saturating_add(1).to_be_bytes();
5422 result = write_tx.delete_range(STATE_HISTORY, &start, &end);
5423 if result.is_err() {
5424 break 'layers;
5425 }
5426 } else {
5427 error!(
5428 block_number = layer.block_number,
5429 t,
5430 d,
5431 "deep-reorg reconciliation skipped: committed layer not at pivot height; skipping STATE_HISTORY delete_range to avoid history gaps"
5432 );
5433 }
5434 }
5435
5436 if !is_batch {
5442 let entry = JournalEntry {
5443 block_hash: layer.block_hash,
5444 parent_state_root: layer.parent_state_root,
5445 account_trie_diff: journal_account_trie,
5446 storage_trie_diff: journal_storage_trie,
5447 account_flat_diff: journal_account_flat,
5448 storage_flat_diff: journal_storage_flat,
5449 };
5450 result = write_tx.put(
5451 STATE_HISTORY,
5452 &layer.block_number.to_be_bytes(),
5453 &entry.encode(),
5454 );
5455 if result.is_err() {
5456 break 'layers;
5457 }
5458 }
5459 }
5460
5461 if result.is_ok() {
5462 result = write_tx.commit();
5463 }
5464 let _ = fkv_ctl.send(FKVGeneratorControlMessage::Continue);
5466 result?;
5467
5468 if overlay_for_reconciliation.is_some() {
5471 trie_mut.clear_overlay();
5472 }
5473
5474 *trie_cache.write().map_err(|_| StoreError::LockError)? = Arc::new(trie_mut);
5480 Ok(())
5481}
5482
5483fn flatkeyvalue_generator(
5486 backend: &Arc<dyn StorageBackend>,
5487 last_computed_fkv: &RwLock<Vec<u8>>,
5488 control_rx: &std::sync::mpsc::Receiver<FKVGeneratorControlMessage>,
5489) -> Result<(), StoreError> {
5490 info!("Generation of FlatKeyValue started.");
5491 let initial_last_written = backend
5492 .begin_read()?
5493 .get(MISC_VALUES, "last_written".as_bytes())?
5494 .unwrap_or_default();
5495
5496 if initial_last_written.is_empty() {
5497 backend.clear_table(ACCOUNT_FLATKEYVALUE)?;
5499 backend.clear_table(STORAGE_FLATKEYVALUE)?;
5500 } else if initial_last_written == [0xff] {
5501 info!("FlatKeyValue already generated. Skipping.");
5503 return Ok(());
5504 }
5505
5506 loop {
5507 let read_tx = backend.begin_read()?;
5510 let root = read_tx
5511 .get(ACCOUNT_TRIE_NODES, &[])?
5512 .ok_or(StoreError::MissingLatestBlockNumber)?;
5513 let root: Node = ethrex_trie::Node::decode(&root)?;
5514 let state_root = root.compute_hash(&NativeCrypto).finalize(&NativeCrypto);
5515
5516 let last_written = read_tx
5517 .get(MISC_VALUES, "last_written".as_bytes())?
5518 .unwrap_or_default();
5519 let last_written_account = last_written
5520 .get(0..64)
5521 .map(|v| Nibbles::from_hex(v.to_vec()))
5522 .unwrap_or_default();
5523 let mut last_written_storage = last_written
5524 .get(66..130)
5525 .map(|v| Nibbles::from_hex(v.to_vec()))
5526 .unwrap_or_default();
5527
5528 debug!("Starting FlatKeyValue loop pivot={last_written:?} SR={state_root:x}");
5529
5530 let mut ctr = 0;
5531 let mut write_txn = backend.begin_write()?;
5532 let mut iter = Trie::open(
5533 Box::new(BackendTrieDB::new_for_accounts_with_view(
5534 backend.clone(),
5535 read_tx.clone(),
5536 last_written.clone(),
5537 )?),
5538 state_root,
5539 )
5540 .into_iter();
5541 if last_written_account > Nibbles::default() {
5542 iter.advance(last_written_account.to_bytes())?;
5543 }
5544 let res = iter.try_for_each(|(path, node)| -> Result<(), StoreError> {
5545 let Node::Leaf(node) = node else {
5546 return Ok(());
5547 };
5548 let account_state = AccountState::decode(&node.value)?;
5549 let account_hash = H256::from_slice(&path.to_bytes());
5550 write_txn.put(MISC_VALUES, "last_written".as_bytes(), path.as_ref())?;
5551 write_txn.put(ACCOUNT_FLATKEYVALUE, path.as_ref(), &node.value)?;
5552 ctr += 1;
5553 if ctr > 10_000 {
5554 write_txn.commit()?;
5555 write_txn = backend.begin_write()?;
5556 *last_computed_fkv
5557 .write()
5558 .map_err(|_| StoreError::LockError)? = path.as_ref().to_vec();
5559 ctr = 0;
5560 }
5561
5562 let mut iter_inner = Trie::open(
5563 Box::new(BackendTrieDB::new_for_account_storage_with_view(
5564 backend.clone(),
5565 read_tx.clone(),
5566 account_hash,
5567 path.as_ref().to_vec(),
5568 )?),
5569 account_state.storage_root,
5570 )
5571 .into_iter();
5572 if last_written_storage > Nibbles::default() {
5573 iter_inner.advance(last_written_storage.to_bytes())?;
5574 last_written_storage = Nibbles::default();
5575 }
5576 iter_inner.try_for_each(|(path, node)| -> Result<(), StoreError> {
5577 let Node::Leaf(node) = node else {
5578 return Ok(());
5579 };
5580 let key = apply_prefix(Some(account_hash), path);
5581 write_txn.put(MISC_VALUES, "last_written".as_bytes(), key.as_ref())?;
5582 write_txn.put(STORAGE_FLATKEYVALUE, key.as_ref(), &node.value)?;
5583 ctr += 1;
5584 if ctr > 10_000 {
5585 write_txn.commit()?;
5586 write_txn = backend.begin_write()?;
5587 *last_computed_fkv
5588 .write()
5589 .map_err(|_| StoreError::LockError)? = key.into_vec();
5590 ctr = 0;
5591 }
5592 fkv_check_for_stop_msg(control_rx)?;
5593 Ok(())
5594 })?;
5595 fkv_check_for_stop_msg(control_rx)?;
5596 Ok(())
5597 });
5598 match res {
5599 Err(StoreError::PivotChanged) => {
5600 match control_rx.recv() {
5601 Ok(FKVGeneratorControlMessage::Continue) => {}
5602 Ok(FKVGeneratorControlMessage::Stop) => {
5603 return Err(StoreError::Custom("Unexpected Stop message".to_string()));
5604 }
5605 Err(std::sync::mpsc::RecvError) => {
5607 info!("Store closed, stopping FlatKeyValue generation.");
5608 return Ok(());
5609 }
5610 }
5611 }
5612 Err(err) => return Err(err),
5613 Ok(()) => {
5614 write_txn.put(MISC_VALUES, "last_written".as_bytes(), &[0xff])?;
5615 write_txn.commit()?;
5616 *last_computed_fkv
5617 .write()
5618 .map_err(|_| StoreError::LockError)? = vec![0xff; 131];
5619 info!("FlatKeyValue generation finished.");
5620 return Ok(());
5621 }
5622 };
5623 }
5624}
5625
5626fn fkv_check_for_stop_msg(
5627 control_rx: &std::sync::mpsc::Receiver<FKVGeneratorControlMessage>,
5628) -> Result<(), StoreError> {
5629 match control_rx.try_recv() {
5630 Ok(FKVGeneratorControlMessage::Stop) | Err(TryRecvError::Disconnected) => {
5631 return Err(StoreError::PivotChanged);
5632 }
5633 Ok(FKVGeneratorControlMessage::Continue) => {
5634 return Err(StoreError::Custom(
5635 "Unexpected Continue message".to_string(),
5636 ));
5637 }
5638 Err(TryRecvError::Empty) => {}
5639 }
5640 Ok(())
5641}
5642
5643fn state_trie_locked_backend(
5644 backend: &dyn StorageBackend,
5645 last_written: Vec<u8>,
5646) -> Result<BackendTrieDBLocked, StoreError> {
5647 BackendTrieDBLocked::new(backend, last_written)
5649}
5650
5651pub struct AccountProof {
5652 pub proof: Vec<NodeRLP>,
5653 pub account: AccountState,
5654 pub storage_proof: Vec<StorageSlotProof>,
5655}
5656
5657pub struct StorageSlotProof {
5658 pub proof: Vec<NodeRLP>,
5659 pub key: H256,
5660 pub value: U256,
5661}
5662
5663pub struct AncestorIterator {
5664 store: Store,
5665 next_hash: BlockHash,
5666}
5667
5668impl Iterator for AncestorIterator {
5669 type Item = Result<(BlockHash, BlockHeader), StoreError>;
5670
5671 fn next(&mut self) -> Option<Self::Item> {
5672 let next_hash = self.next_hash;
5673 match self.store.get_block_header_by_hash(next_hash) {
5677 Ok(Some(header)) => {
5678 let ret_hash = self.next_hash;
5679 self.next_hash = header.parent_hash;
5680 Some(Ok((ret_hash, header)))
5681 }
5682 Ok(None) => None,
5683 Err(e) => Some(Err(e)),
5684 }
5685 }
5686}
5687
5688pub fn hash_address(address: &Address) -> Vec<u8> {
5689 keccak_hash(address.to_fixed_bytes()).to_vec()
5690}
5691
5692fn hash_address_fixed(address: &Address) -> H256 {
5693 keccak(address.to_fixed_bytes())
5694}
5695
5696pub fn hash_key(key: &H256) -> Vec<u8> {
5697 keccak_hash(key.to_fixed_bytes()).to_vec()
5698}
5699
5700pub fn hash_key_fixed(key: &H256) -> [u8; 32] {
5701 keccak_hash(key.to_fixed_bytes())
5702}
5703
5704fn chain_data_key(index: ChainDataIndex) -> Vec<u8> {
5705 (index as u8).encode_to_vec()
5706}
5707
5708fn decode_block_number(bytes: Vec<u8>) -> Result<BlockNumber, StoreError> {
5710 let array: [u8; 8] = bytes
5711 .try_into()
5712 .map_err(|_| StoreError::Custom("Invalid BlockNumber bytes".to_string()))?;
5713 Ok(BlockNumber::from_le_bytes(array))
5714}
5715
5716fn snap_state_key(index: SnapStateIndex) -> Vec<u8> {
5717 (index as u8).encode_to_vec()
5718}
5719
5720pub fn receipt_key(block_hash: &BlockHash, index: u64) -> Vec<u8> {
5722 let mut key = Vec::with_capacity(40);
5723 key.extend_from_slice(block_hash.as_bytes());
5724 key.extend_from_slice(&index.to_be_bytes());
5725 key
5726}
5727
5728fn encode_code(code: &Code) -> Vec<u8> {
5729 let jumpdests = code.jumpdests();
5730 let mut buf = Vec::with_capacity(6 + code.len() + jumpdests.len());
5731 code.code().encode(&mut buf);
5732 jumpdests.encode(&mut buf);
5733 buf
5734}
5735
5736fn decode_jumpdests(code: &[u8], encoded: &[u8]) -> Result<(Arc<[u8]>, bool), StoreError> {
5747 let (is_list, payload, _) = decode_rlp_item(encoded)?;
5748 if is_list {
5749 return Ok((Code::compute_jumpdests(code), true));
5750 }
5751 Ok((Arc::from(payload), false))
5752}
5753
5754#[derive(Debug, Default, Clone)]
5755struct LatestBlockHeaderCache {
5756 current: Arc<Mutex<Arc<BlockHeader>>>,
5757}
5758
5759impl LatestBlockHeaderCache {
5760 pub fn get(&self) -> Arc<BlockHeader> {
5761 self.current.lock().expect("poisoned mutex").clone()
5762 }
5763
5764 pub fn update(&self, header: BlockHeader) {
5765 let new = Arc::new(header);
5766 *self.current.lock().expect("poisoned mutex") = new;
5767 }
5768}
5769
5770#[derive(Debug, Serialize, Deserialize)]
5771pub struct StoreMetadata {
5772 pub schema_version: u64,
5773}
5774
5775impl StoreMetadata {
5776 pub fn new(schema_version: u64) -> Self {
5777 Self { schema_version }
5778 }
5779}
5780
5781fn read_store_schema_version(path: &Path) -> Result<Option<u64>, StoreError> {
5786 let metadata_path = path.join(STORE_METADATA_FILENAME);
5787 if !metadata_path.exists() {
5788 return Ok(None);
5789 }
5790 if !metadata_path.is_file() {
5791 return Err(StoreError::Custom(
5792 "store schema path exists but is not a file".to_string(),
5793 ));
5794 }
5795 let file_contents = std::fs::read_to_string(metadata_path)?;
5796 let metadata: StoreMetadata = serde_json::from_str(&file_contents)?;
5797 Ok(Some(metadata.schema_version))
5798}
5799
5800fn init_metadata_file(parent_path: &Path) -> Result<(), StoreError> {
5801 std::fs::create_dir_all(parent_path)?;
5802
5803 let metadata_path = parent_path.join(STORE_METADATA_FILENAME);
5804 let metadata = StoreMetadata::new(STORE_SCHEMA_VERSION);
5805 let serialized_metadata = serde_json::to_string_pretty(&metadata)?;
5806 let mut new_file = std::fs::File::create_new(metadata_path)?;
5807 new_file.write_all(serialized_metadata.as_bytes())?;
5808 Ok(())
5809}
5810
5811fn dir_contains_legacy_db(path: &Path) -> Result<bool, StoreError> {
5823 if path.join("CURRENT").is_file() {
5827 return Ok(true);
5828 }
5829 for entry in std::fs::read_dir(path)? {
5833 let entry = entry?;
5834 if !entry.file_type()?.is_file() {
5835 continue;
5836 }
5837 if entry.file_name().to_string_lossy().starts_with("MANIFEST-") {
5838 return Ok(true);
5839 }
5840 }
5841 Ok(false)
5842}
5843
5844pub fn has_valid_db(path: &Path) -> bool {
5848 let metadata_path = path.join(STORE_METADATA_FILENAME);
5849 if !metadata_path.is_file() {
5850 return false;
5851 }
5852 let Ok(contents) = std::fs::read_to_string(&metadata_path) else {
5853 return false;
5854 };
5855 let Ok(metadata) = serde_json::from_str::<StoreMetadata>(&contents) else {
5856 return false;
5857 };
5858 metadata.schema_version >= 1 && metadata.schema_version <= STORE_SCHEMA_VERSION
5859}
5860
5861pub fn read_chain_id_from_db(path: &Path) -> Option<u64> {
5870 if !has_valid_db(path) {
5871 return None;
5872 }
5873 #[cfg(feature = "rocksdb")]
5874 {
5875 let backend = match RocksDBBackend::open(path, MAX_ROCKSDB_BLOCK_CACHE_SIZE_BYTES) {
5878 Ok(backend) => backend,
5879 Err(e) => {
5880 warn!("Failed to open RocksDB at {path:?} to read chain ID: {e}");
5881 return None;
5882 }
5883 };
5884 let read = match backend.begin_read() {
5885 Ok(read) => read,
5886 Err(e) => {
5887 warn!("Failed to begin read transaction at {path:?}: {e}");
5888 return None;
5889 }
5890 };
5891 let key = chain_data_key(ChainDataIndex::ChainConfig);
5892 let bytes = match read.get(CHAIN_DATA, &key) {
5893 Ok(Some(bytes)) => bytes,
5894 Ok(None) => {
5895 warn!("Chain config entry not found in database at {path:?}");
5896 return None;
5897 }
5898 Err(e) => {
5899 warn!("Failed to read chain config from database at {path:?}: {e}");
5900 return None;
5901 }
5902 };
5903 #[derive(serde::Deserialize)]
5908 #[serde(rename_all = "camelCase")]
5909 struct ChainIdOnly {
5910 chain_id: u64,
5911 }
5912 match serde_json::from_slice::<ChainIdOnly>(&bytes) {
5913 Ok(partial) => Some(partial.chain_id),
5914 Err(e) => {
5915 warn!("Failed to deserialize chain ID from database at {path:?}: {e}");
5916 None
5917 }
5918 }
5919 }
5920 #[cfg(not(feature = "rocksdb"))]
5921 {
5922 let _ = path;
5923 None
5924 }
5925}
5926
5927#[cfg(test)]
5928mod account_code_tests {
5929 use super::*;
5930
5931 const JUMPDEST: u8 = 0x5b;
5932 const PUSH1: u8 = 0x60;
5933
5934 #[test]
5940 fn metadata_falls_back_to_the_bytecode_without_a_tokio_runtime() {
5941 use crate::backend::in_memory::InMemoryBackend;
5942
5943 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
5944 let dir = tempfile::tempdir().unwrap();
5945 let store = Store::from_backend(
5946 backend.clone(),
5947 dir.path().to_path_buf(),
5948 1,
5949 DEFAULT_PERSIST_CHANNEL_CAPACITY,
5950 )
5951 .unwrap();
5952
5953 let code = jumpdest_dense_code();
5956 let mut tx = backend.begin_write().unwrap();
5957 tx.put(ACCOUNT_CODES, code.hash.as_bytes(), &encode_code(&code))
5958 .unwrap();
5959 tx.commit().unwrap();
5960
5961 let metadata = store
5962 .get_code_metadata(code.hash)
5963 .expect("metadata read must not fail off-runtime");
5964 assert_eq!(metadata.map(|m| m.length), Some(code.len() as u64));
5965 }
5966
5967 fn jumpdest_dense_code() -> Code {
5971 let mut bytecode = vec![JUMPDEST; 24576];
5972 bytecode[0] = 0x00;
5973 Code::from_bytecode_unchecked(bytecode.into(), H256::zero())
5974 }
5975
5976 fn encode_code_legacy(code: &Code) -> Vec<u8> {
5979 let offsets: Vec<u32> = (0..code.len())
5980 .filter(|offset| code.is_valid_jumpdest(*offset))
5981 .map(|offset| offset as u32)
5982 .collect();
5983 let mut buf = Vec::new();
5984 code.code().encode(&mut buf);
5985 offsets.encode(&mut buf);
5986 buf
5987 }
5988
5989 #[test]
5990 fn encoded_value_carries_one_bitmap_bit_per_code_byte() {
5991 let code = jumpdest_dense_code();
5992 let encoded = encode_code(&code);
5993
5994 assert_eq!(encoded.len(), 6 + code.len() + code.len().div_ceil(8));
5995 }
5996
5997 #[test]
5998 fn round_trip_preserves_the_bitmap() {
5999 let code = jumpdest_dense_code();
6000 let encoded = encode_code(&code);
6001
6002 let (bytecode, jumpdests) = decode_bytes(&encoded).unwrap();
6003 assert_eq!(bytecode, code.code());
6004 let (rebuilt, was_legacy) = decode_jumpdests(bytecode, jumpdests).unwrap();
6005 assert_eq!(rebuilt.as_ref(), code.jumpdests());
6006 assert!(!was_legacy, "bitmap format must not be flagged legacy");
6007 }
6008
6009 #[test]
6016 fn legacy_offset_list_values_decode_to_the_same_bitmap() {
6017 for (bytecode, offsets) in [
6018 (vec![0x00, JUMPDEST, 0x00, JUMPDEST], vec![1u32, 3]),
6019 (vec![PUSH1, JUMPDEST, JUMPDEST], vec![2u32]),
6021 (vec![0x00; 32], vec![]),
6022 ] {
6023 let code = Code::from_bytecode_unchecked(bytecode.clone().into(), H256::zero());
6024 let legacy = encode_code_legacy(&code);
6025
6026 let mut expected = vec![0u8; bytecode.len().div_ceil(8)];
6027 for offset in &offsets {
6028 let index = usize::try_from(*offset).expect("offset fits usize");
6029 expected[index / 8] |= 1 << (index % 8);
6030 }
6031 if offsets.is_empty() {
6033 expected.clear();
6034 }
6035
6036 let (decoded_bytecode, jumpdests) = decode_bytes(&legacy).unwrap();
6037 assert_eq!(decoded_bytecode, code.code());
6038 let (rebuilt, was_legacy) = decode_jumpdests(decoded_bytecode, jumpdests).unwrap();
6039 assert_eq!(
6040 rebuilt.as_ref(),
6041 expected.as_slice(),
6042 "rebuilt bitmap disagrees with the legacy offsets for {bytecode:?}"
6043 );
6044 assert!(
6045 was_legacy,
6046 "legacy RLP-list format must be flagged for self-heal"
6047 );
6048 }
6049 }
6050
6051 #[test]
6054 fn repeated_inserts_do_not_inflate_the_accounted_size() {
6055 let code = jumpdest_dense_code();
6056 let mut cache = CodeCache::default();
6057
6058 cache.insert(&code).unwrap();
6059 let after_first = cache.cache_size;
6060 for _ in 0..8 {
6061 cache.insert(&code).unwrap();
6062 }
6063
6064 assert_eq!(cache.cache_size, after_first);
6065 assert_eq!(cache.inner_cache.len(), 1);
6066 }
6067
6068 #[test]
6071 fn accounted_size_covers_the_bytecode_and_bitmap() {
6072 let code = jumpdest_dense_code();
6073 let mut cache = CodeCache::default();
6074 cache.insert(&code).unwrap();
6075
6076 assert!(cache.cache_size >= (code.len() + code.jumpdests().len()) as u64);
6077 }
6078
6079 #[test]
6080 fn cache_evicts_down_to_its_budget() {
6081 let mut cache = CodeCache {
6082 max_size: 128 * 1024,
6083 ..Default::default()
6084 };
6085
6086 for i in 0..16u8 {
6087 let mut bytecode = vec![JUMPDEST; 24576];
6088 bytecode[0] = i;
6089 cache
6090 .insert(&Code::from_bytecode_unchecked(
6091 bytecode.into(),
6092 H256::from_low_u64_be(i.into()),
6093 ))
6094 .unwrap();
6095 }
6096
6097 assert!(cache.cache_size <= cache.max_size);
6098 assert!(cache.inner_cache.len() < 16);
6099 }
6100
6101 #[test]
6104 fn default_cache_uses_the_configured_budget() {
6105 assert_eq!(CodeCache::default().max_size, CODE_CACHE_MAX_SIZE);
6106 }
6107}
6108
6109#[cfg(test)]
6110mod state_history_tests {
6111 use super::*;
6112 use crate::api::tables::STATE_HISTORY;
6113 use crate::backend::in_memory::InMemoryBackend;
6114 use crate::journal::JournalEntry;
6115 use ethrex_common::types::{BlockBody, BlockHeader};
6116 use ethrex_trie::Nibbles;
6117 use std::time::{Duration, Instant};
6118
6119 fn make_block(number: BlockNumber, parent_hash: H256, state_root: H256) -> Block {
6120 let header = BlockHeader {
6121 number,
6122 parent_hash,
6123 state_root,
6124 ..Default::default()
6125 };
6126 Block::new(header, BlockBody::default())
6127 }
6128
6129 fn await_journal_entry(
6133 backend: &Arc<dyn StorageBackend>,
6134 block_number: BlockNumber,
6135 timeout: Duration,
6136 ) -> Option<Vec<u8>> {
6137 let key = block_number.to_be_bytes();
6138 let deadline = Instant::now() + timeout;
6139 loop {
6140 let read = backend.begin_read().ok()?;
6141 if let Ok(Some(v)) = read.get(STATE_HISTORY, &key) {
6142 return Some(v);
6143 }
6144 if Instant::now() >= deadline {
6145 return None;
6146 }
6147 std::thread::sleep(Duration::from_millis(10));
6148 }
6149 }
6150
6151 fn assert_no_journal_entry(backend: &Arc<dyn StorageBackend>, block_number: BlockNumber) {
6156 let window = Duration::from_millis(500);
6157 let key = block_number.to_be_bytes();
6158 let deadline = Instant::now() + window;
6159 loop {
6160 let read = backend.begin_read().expect("read view");
6161 let v = read.get(STATE_HISTORY, &key).expect("get");
6162 assert!(
6163 v.is_none(),
6164 "expected no STATE_HISTORY entry for block {block_number}, got {v:?}"
6165 );
6166 if Instant::now() >= deadline {
6167 return;
6168 }
6169 std::thread::sleep(Duration::from_millis(20));
6170 }
6171 }
6172
6173 #[test]
6179 fn journal_entry_written_per_block_in_regular_mode() {
6180 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6181 let dir = tempfile::tempdir().unwrap();
6182 let store = Store::from_backend(
6183 backend.clone(),
6184 dir.path().to_path_buf(),
6185 1,
6186 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6187 )
6188 .unwrap();
6189
6190 let state_root_1 = H256::repeat_byte(0x11);
6191 let block1 = make_block(1, H256::zero(), state_root_1);
6192 let block1_hash = block1.hash();
6193 store
6194 .store_block_updates(UpdateBatch {
6195 account_updates: vec![(Nibbles::from_raw(&[0x00, 0x01], false), vec![0xab, 0xcd])],
6196 storage_updates: vec![],
6197 blocks: vec![block1],
6198 receipts: vec![],
6199 code_updates: vec![],
6200 commit_depth: None,
6201 wait_for_flush: false,
6202 })
6203 .unwrap();
6204
6205 store.set_safe_commit_root(state_root_1).unwrap();
6208 let state_root_2 = H256::repeat_byte(0x22);
6209 let block2 = make_block(2, block1_hash, state_root_2);
6210 let block2_hash = block2.hash();
6211 store
6212 .store_block_updates(UpdateBatch {
6213 account_updates: vec![(Nibbles::from_raw(&[0x00, 0x02], false), vec![0xef, 0x11])],
6214 storage_updates: vec![],
6215 blocks: vec![block2],
6216 receipts: vec![],
6217 code_updates: vec![],
6218 commit_depth: None,
6219 wait_for_flush: false,
6220 })
6221 .unwrap();
6222
6223 let bytes = await_journal_entry(&backend, 1, Duration::from_secs(2))
6224 .expect("STATE_HISTORY entry for block 1 should appear after block 2 commits it");
6225 let entry = JournalEntry::decode(&bytes).unwrap();
6226 assert_eq!(entry.block_hash, block1_hash);
6227 assert_eq!(entry.parent_state_root, H256::zero());
6228 assert!(!entry.account_trie_diff.is_empty());
6229 let (path, prev) = &entry.account_trie_diff[0];
6230 assert_eq!(prev, &None, "first-time write means previous value is None");
6231 assert!(path.len() < 65);
6232
6233 store.set_safe_commit_root(state_root_2).unwrap();
6236 let state_root_3 = H256::repeat_byte(0x33);
6237 let block3 = make_block(3, block2_hash, state_root_3);
6238 store
6239 .store_block_updates(UpdateBatch {
6240 account_updates: vec![(Nibbles::from_raw(&[0x00, 0x03], false), vec![0x77])],
6241 storage_updates: vec![],
6242 blocks: vec![block3],
6243 receipts: vec![],
6244 code_updates: vec![],
6245 commit_depth: None,
6246 wait_for_flush: false,
6247 })
6248 .unwrap();
6249
6250 let bytes = await_journal_entry(&backend, 2, Duration::from_secs(2))
6251 .expect("STATE_HISTORY entry for block 2 should appear after block 3 commits it");
6252 let entry = JournalEntry::decode(&bytes).unwrap();
6253 assert_eq!(entry.block_hash, block2_hash);
6254 assert_eq!(entry.parent_state_root, state_root_1);
6255 assert!(!entry.account_trie_diff.is_empty());
6256 }
6257
6258 #[test]
6268 fn depth_gated_per_block_commit_journals_with_correct_pre_image() {
6269 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6270 let dir = tempfile::tempdir().unwrap();
6271 let store = Store::from_backend(
6272 backend.clone(),
6273 dir.path().to_path_buf(),
6274 1,
6275 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6276 )
6277 .unwrap();
6278
6279 let shared = Nibbles::from_raw(&[0x0a, 0x0b], false);
6281 let mut prev_hash = H256::zero();
6282 let mut hashes = vec![H256::zero()];
6283 let mut roots = vec![H256::zero()];
6284 for n in 1..=6u64 {
6285 let state_root = H256::repeat_byte(0xc0 | (n as u8));
6286 let block = make_block(n, prev_hash, state_root);
6287 prev_hash = block.hash();
6288 hashes.push(prev_hash);
6289 roots.push(state_root);
6290 store
6291 .store_block_updates(UpdateBatch {
6292 account_updates: vec![(shared.clone(), vec![0xb0 | (n as u8)])],
6293 storage_updates: vec![],
6294 blocks: vec![block],
6295 receipts: vec![],
6296 code_updates: vec![],
6297 commit_depth: Some(3),
6298 wait_for_flush: false,
6299 })
6300 .unwrap();
6301 }
6302
6303 let bytes = await_journal_entry(&backend, 1, Duration::from_secs(2))
6304 .expect("a depth-gated per-block commit must journal");
6305 let entry = JournalEntry::decode(&bytes).unwrap();
6306 assert_eq!(
6307 entry.block_hash, hashes[1],
6308 "entry 1 carries block 1's identity"
6309 );
6310 assert_eq!(entry.parent_state_root, H256::zero());
6311 assert_eq!(
6312 entry.account_trie_diff[0].1, None,
6313 "block 1 first-writes the key, so its pre-image is absence"
6314 );
6315
6316 let bytes =
6317 await_journal_entry(&backend, 2, Duration::from_secs(2)).expect("entry for block 2");
6318 let entry = JournalEntry::decode(&bytes).unwrap();
6319 assert_eq!(
6320 entry.block_hash, hashes[2],
6321 "entry 2 carries block 2's identity"
6322 );
6323 assert_eq!(
6324 entry.parent_state_root, roots[1],
6325 "entry 2's parent_state_root is block 1's state root"
6326 );
6327 assert_eq!(
6328 entry.account_trie_diff[0].1,
6329 Some(vec![0xb0 | 1u8]),
6330 "block 2's pre-image is the value block 1 wrote"
6331 );
6332 }
6333
6334 #[test]
6343 fn journal_skipped_in_batch_mode() {
6344 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6345 let dir = tempfile::tempdir().unwrap();
6346 let store = Store::from_backend(
6347 backend.clone(),
6348 dir.path().to_path_buf(),
6349 1,
6350 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6351 )
6352 .unwrap();
6353
6354 let mut prev_hash = H256::zero();
6355 for n in 1..=5u64 {
6356 let state_root = H256::repeat_byte(0xa0 | (n as u8));
6357 let block = make_block(n, prev_hash, state_root);
6358 prev_hash = block.hash();
6359 store
6360 .store_block_updates(UpdateBatch {
6361 account_updates: vec![(
6362 Nibbles::from_raw(&[n as u8], false),
6363 vec![0xde, 0xad, n as u8],
6364 )],
6365 storage_updates: vec![],
6366 blocks: vec![block],
6367 receipts: vec![],
6368 code_updates: vec![],
6369 commit_depth: Some(BATCH_COMMIT_THRESHOLD),
6370 wait_for_flush: true,
6371 })
6372 .unwrap();
6373 }
6374
6375 for n in 1..=5u64 {
6376 assert_no_journal_entry(&backend, n);
6377 }
6378 }
6379
6380 #[test]
6383 fn journal_storage_updates_appear_in_storage_diff() {
6384 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6385 let dir = tempfile::tempdir().unwrap();
6386 let store = Store::from_backend(
6387 backend.clone(),
6388 dir.path().to_path_buf(),
6389 1,
6390 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6391 )
6392 .unwrap();
6393
6394 let account_hash_a = H256::repeat_byte(0xa0);
6395 let account_hash_b = H256::repeat_byte(0xb0);
6396
6397 let state_root_1 = H256::repeat_byte(0x33);
6398 let block1 = make_block(1, H256::zero(), state_root_1);
6399 let block1_hash = block1.hash();
6400 store
6401 .store_block_updates(UpdateBatch {
6402 account_updates: vec![],
6403 storage_updates: vec![
6404 (
6405 account_hash_a,
6406 vec![(Nibbles::from_raw(&[0x05], false), vec![0x01])],
6407 ),
6408 (
6409 account_hash_b,
6410 vec![(Nibbles::from_raw(&[0x06], false), vec![0x02])],
6411 ),
6412 ],
6413 blocks: vec![block1],
6414 receipts: vec![],
6415 code_updates: vec![],
6416 commit_depth: None,
6417 wait_for_flush: false,
6418 })
6419 .unwrap();
6420
6421 store.set_safe_commit_root(state_root_1).unwrap();
6424 let state_root_2 = H256::repeat_byte(0x44);
6425 let block2 = make_block(2, block1_hash, state_root_2);
6426 store
6427 .store_block_updates(UpdateBatch {
6428 account_updates: vec![(Nibbles::from_raw(&[0xee], false), vec![0xff])],
6429 storage_updates: vec![],
6430 blocks: vec![block2],
6431 receipts: vec![],
6432 code_updates: vec![],
6433 commit_depth: None,
6434 wait_for_flush: false,
6435 })
6436 .unwrap();
6437
6438 let bytes = await_journal_entry(&backend, 1, Duration::from_secs(2))
6439 .expect("STATE_HISTORY entry for block 1");
6440 let entry = JournalEntry::decode(&bytes).unwrap();
6441 assert_eq!(entry.block_hash, block1_hash);
6442 assert_eq!(
6443 entry.storage_trie_diff.len(),
6444 2,
6445 "two distinct account hashes must produce two storage_trie entries"
6446 );
6447 for (_key, prev) in &entry.storage_trie_diff {
6448 assert_eq!(prev, &None, "first-time storage write has None pre-image");
6449 }
6450 assert!(entry.account_trie_diff.is_empty());
6451 }
6452
6453 fn seed_journal_entries(backend: &Arc<dyn StorageBackend>, block_numbers: &[BlockNumber]) {
6454 let mut tx = backend.begin_write().unwrap();
6455 for n in block_numbers {
6456 let entry = JournalEntry {
6457 block_hash: H256::repeat_byte(*n as u8),
6458 parent_state_root: H256::zero(),
6459 account_trie_diff: vec![(vec![*n as u8], None)],
6460 storage_trie_diff: vec![],
6461 account_flat_diff: vec![],
6462 storage_flat_diff: vec![],
6463 };
6464 tx.put(STATE_HISTORY, &n.to_be_bytes(), &entry.encode())
6465 .unwrap();
6466 }
6467 tx.commit().unwrap();
6468 }
6469
6470 fn journal_entry_exists(backend: &Arc<dyn StorageBackend>, block_number: BlockNumber) -> bool {
6471 backend
6472 .begin_read()
6473 .unwrap()
6474 .get(STATE_HISTORY, &block_number.to_be_bytes())
6475 .unwrap()
6476 .is_some()
6477 }
6478
6479 #[tokio::test]
6482 async fn finality_advance_prunes_journal_below_boundary() {
6483 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6484 let dir = tempfile::tempdir().unwrap();
6485 let store = Store::from_backend(
6486 backend.clone(),
6487 dir.path().to_path_buf(),
6488 1,
6489 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6490 )
6491 .unwrap();
6492
6493 seed_journal_entries(&backend, &(1..=10).collect::<Vec<_>>());
6494 for n in 1..=10 {
6495 assert!(journal_entry_exists(&backend, n), "seed entry {n} present");
6496 }
6497
6498 store
6499 .forkchoice_update_inner(vec![], 100, H256::zero(), None, Some(5))
6500 .await
6501 .unwrap();
6502
6503 for n in 1..=5 {
6504 assert!(
6505 !journal_entry_exists(&backend, n),
6506 "entry {n} should have been pruned (<= finalized)"
6507 );
6508 }
6509 for n in 6..=10 {
6510 assert!(
6511 journal_entry_exists(&backend, n),
6512 "entry {n} should remain (> finalized)"
6513 );
6514 }
6515 }
6516
6517 #[tokio::test]
6519 async fn finality_no_op_does_not_prune() {
6520 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6521 let dir = tempfile::tempdir().unwrap();
6522 let store = Store::from_backend(
6523 backend.clone(),
6524 dir.path().to_path_buf(),
6525 1,
6526 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6527 )
6528 .unwrap();
6529
6530 store
6531 .forkchoice_update_inner(vec![], 100, H256::zero(), None, Some(5))
6532 .await
6533 .unwrap();
6534
6535 seed_journal_entries(&backend, &(6..=10).collect::<Vec<_>>());
6536
6537 store
6539 .forkchoice_update_inner(vec![], 100, H256::zero(), None, Some(5))
6540 .await
6541 .unwrap();
6542
6543 for n in 6..=10 {
6544 assert!(
6545 journal_entry_exists(&backend, n),
6546 "entry {n} should still exist after no-op finality update"
6547 );
6548 }
6549
6550 store
6552 .forkchoice_update_inner(vec![], 100, H256::zero(), None, None)
6553 .await
6554 .unwrap();
6555
6556 for n in 6..=10 {
6557 assert!(
6558 journal_entry_exists(&backend, n),
6559 "entry {n} should still exist when finalized is None"
6560 );
6561 }
6562 }
6563
6564 #[tokio::test]
6568 async fn malformed_finalized_returns_error_not_silent_zero() {
6569 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6570 let dir = tempfile::tempdir().unwrap();
6571 let store = Store::from_backend(
6572 backend.clone(),
6573 dir.path().to_path_buf(),
6574 1,
6575 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6576 )
6577 .unwrap();
6578
6579 let mut tx = backend.begin_write().unwrap();
6581 let finalized_key = chain_data_key(ChainDataIndex::FinalizedBlockNumber);
6582 tx.put(CHAIN_DATA, &finalized_key, &[0u8, 0, 0, 0]).unwrap();
6583 tx.commit().unwrap();
6584
6585 seed_journal_entries(&backend, &(1..=5).collect::<Vec<_>>());
6586
6587 let err = store
6588 .forkchoice_update_inner(vec![], 100, H256::zero(), None, Some(3))
6589 .await
6590 .expect_err("malformed finalized must surface as an error");
6591 let msg = format!("{err}");
6592 assert!(
6593 msg.contains("FinalizedBlockNumber has unexpected length"),
6594 "expected length-mismatch error, got: {msg}"
6595 );
6596
6597 for n in 1..=5 {
6599 assert!(
6600 journal_entry_exists(&backend, n),
6601 "entry {n} must NOT be pruned when finalized read failed"
6602 );
6603 }
6604 }
6605
6606 #[tokio::test]
6613 async fn highest_state_history_block_number_finds_max() {
6614 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6615 let dir = tempfile::tempdir().unwrap();
6616 let store = Store::from_backend(
6617 backend.clone(),
6618 dir.path().to_path_buf(),
6619 1,
6620 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6621 )
6622 .unwrap();
6623
6624 assert_eq!(store.highest_state_history_block_number().unwrap(), None);
6625
6626 seed_journal_entries(&backend, &[3, 7, 5, 11, 8]);
6627 assert_eq!(
6628 store.highest_state_history_block_number().unwrap(),
6629 Some(11),
6630 "max over present entries"
6631 );
6632 }
6633
6634 #[tokio::test]
6638 async fn journaled_block_hash_reads_back() {
6639 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6640 let dir = tempfile::tempdir().unwrap();
6641 let store = Store::from_backend(
6642 backend.clone(),
6643 dir.path().to_path_buf(),
6644 1,
6645 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6646 )
6647 .unwrap();
6648
6649 seed_journal_entries(&backend, &[3, 11]);
6650 assert_eq!(
6651 store.get_state_history_block_hash(11).unwrap(),
6652 Some(H256::repeat_byte(11))
6653 );
6654 assert_eq!(store.get_state_history_block_hash(4).unwrap(), None);
6655 }
6656
6657 #[tokio::test]
6660 async fn advance_flushed_upto_only_moves_forward() {
6661 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6662 let dir = tempfile::tempdir().unwrap();
6663 let store = Store::from_backend(
6664 backend,
6665 dir.path().to_path_buf(),
6666 1,
6667 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6668 )
6669 .unwrap();
6670
6671 store.advance_flushed_upto(10).unwrap();
6672 assert_eq!(store.read_flushed_upto().unwrap(), 10);
6673 store.advance_flushed_upto(5).unwrap();
6674 assert_eq!(store.read_flushed_upto().unwrap(), 10);
6675 store.advance_flushed_upto(12).unwrap();
6676 assert_eq!(store.read_flushed_upto().unwrap(), 12);
6677 }
6678
6679 #[tokio::test]
6683 async fn lowest_state_history_block_number_finds_min() {
6684 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6685 let dir = tempfile::tempdir().unwrap();
6686 let store = Store::from_backend(
6687 backend.clone(),
6688 dir.path().to_path_buf(),
6689 1,
6690 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6691 )
6692 .unwrap();
6693
6694 assert_eq!(store.lowest_state_history_block_number().unwrap(), None);
6695
6696 seed_journal_entries(&backend, &[7, 3, 11, 5, 8]);
6697 assert_eq!(
6698 store.lowest_state_history_block_number().unwrap(),
6699 Some(3),
6700 "min over present entries"
6701 );
6702 }
6703
6704 #[tokio::test]
6708 async fn install_overlay_replaces_cache_with_fresh_one() {
6709 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6710 let dir = tempfile::tempdir().unwrap();
6711 let store = Store::from_backend(
6712 backend.clone(),
6713 dir.path().to_path_buf(),
6714 4,
6715 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6716 )
6717 .unwrap();
6718
6719 seed_journal_entries(&backend, &[3, 4]);
6720
6721 {
6723 let mut guard = store.trie_cache.write().unwrap();
6724 let mut updated = (**guard).clone();
6725 updated.put_batch(
6726 H256::zero(),
6727 H256::repeat_byte(0xff),
6728 42,
6729 H256::repeat_byte(0x42),
6730 vec![],
6731 );
6732 *guard = Arc::new(updated);
6733 }
6734 assert!(
6735 store
6736 .is_state_in_layer_cache(H256::repeat_byte(0xff))
6737 .unwrap()
6738 );
6739
6740 store
6741 .install_overlay_for_reorg(4, 3, |_| None)
6742 .expect("install must succeed");
6743
6744 assert!(
6745 !store
6746 .is_state_in_layer_cache(H256::repeat_byte(0xff))
6747 .unwrap(),
6748 "old cache layer must be gone after swap"
6749 );
6750 let cache = store.trie_cache.read().unwrap().clone();
6751 assert!(cache.overlay().is_some(), "overlay must be installed");
6752 assert_eq!(
6753 cache.commit_threshold(),
6754 4,
6755 "fresh cache must inherit threshold"
6756 );
6757 }
6758
6759 #[tokio::test]
6762 async fn install_overlay_failure_preserves_cache() {
6763 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6764 let dir = tempfile::tempdir().unwrap();
6765 let store = Store::from_backend(
6766 backend.clone(),
6767 dir.path().to_path_buf(),
6768 4,
6769 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6770 )
6771 .unwrap();
6772
6773 seed_journal_entries(&backend, &[5]);
6775 {
6776 let mut guard = store.trie_cache.write().unwrap();
6777 let mut updated = (**guard).clone();
6778 updated.put_batch(
6779 H256::zero(),
6780 H256::repeat_byte(0xaa),
6781 7,
6782 H256::repeat_byte(0x77),
6783 vec![],
6784 );
6785 *guard = Arc::new(updated);
6786 }
6787 assert!(
6788 store
6789 .is_state_in_layer_cache(H256::repeat_byte(0xaa))
6790 .unwrap()
6791 );
6792
6793 let err = store
6794 .install_overlay_for_reorg(5, 3, |_| None)
6795 .expect_err("missing entries 3 and 4 must abort");
6796 let msg = format!("{err}");
6797 assert!(
6798 msg.contains("overlay construction failed"),
6799 "error should explain construction failure: {msg}"
6800 );
6801
6802 assert!(
6804 store
6805 .is_state_in_layer_cache(H256::repeat_byte(0xaa))
6806 .unwrap(),
6807 "cache must survive a failed overlay install"
6808 );
6809 let cache = store.trie_cache.read().unwrap().clone();
6810 assert!(cache.overlay().is_none(), "no overlay should be installed");
6811 }
6812
6813 #[tokio::test]
6817 async fn abort_reorg_resets_cache_to_fresh() {
6818 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6819 let dir = tempfile::tempdir().unwrap();
6820 let store = Store::from_backend(
6821 backend.clone(),
6822 dir.path().to_path_buf(),
6823 4,
6824 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6825 )
6826 .unwrap();
6827
6828 seed_journal_entries(&backend, &[3, 4]);
6829 store.install_overlay_for_reorg(4, 3, |_| None).unwrap();
6830 {
6832 let mut guard = store.trie_cache.write().unwrap();
6833 let mut updated = (**guard).clone();
6834 updated.put_batch(
6835 H256::zero(),
6836 H256::repeat_byte(0xbb),
6837 4,
6838 H256::repeat_byte(0xb4),
6839 vec![],
6840 );
6841 *guard = Arc::new(updated);
6842 }
6843 assert!(
6844 store
6845 .is_state_in_layer_cache(H256::repeat_byte(0xbb))
6846 .unwrap()
6847 );
6848
6849 store.abort_reorg().unwrap();
6850
6851 assert!(
6852 !store
6853 .is_state_in_layer_cache(H256::repeat_byte(0xbb))
6854 .unwrap(),
6855 "partial new-chain layer must be discarded"
6856 );
6857 let cache = store.trie_cache.read().unwrap().clone();
6858 assert!(cache.overlay().is_none(), "overlay must be cleared");
6859 assert_eq!(
6860 cache.commit_threshold(),
6861 4,
6862 "fresh cache must inherit threshold"
6863 );
6864 }
6865
6866 #[tokio::test]
6869 async fn clear_reorg_overlay_removes_overlay_and_is_idempotent() {
6870 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6871 let dir = tempfile::tempdir().unwrap();
6872 let store = Store::from_backend(
6873 backend.clone(),
6874 dir.path().to_path_buf(),
6875 4,
6876 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6877 )
6878 .unwrap();
6879
6880 store.clear_reorg_overlay().unwrap();
6882 assert!(store.trie_cache.read().unwrap().overlay().is_none());
6883
6884 seed_journal_entries(&backend, &[7]);
6885 store.install_overlay_for_reorg(7, 7, |_| None).unwrap();
6886 assert!(store.trie_cache.read().unwrap().overlay().is_some());
6887
6888 store.clear_reorg_overlay().unwrap();
6889 assert!(store.trie_cache.read().unwrap().overlay().is_none());
6890
6891 store.clear_reorg_overlay().unwrap();
6893 assert!(store.trie_cache.read().unwrap().overlay().is_none());
6894 }
6895
6896 #[tokio::test]
6904 async fn deep_reorg_overlay_disables_flat_kv_fast_paths() {
6905 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
6906
6907 {
6911 let mut tx = backend.begin_write().unwrap();
6912 tx.put(MISC_VALUES, "last_written".as_bytes(), &[0xff])
6913 .unwrap();
6914 tx.commit().unwrap();
6915 }
6916
6917 let dir = tempfile::tempdir().unwrap();
6918 let store = Store::from_backend(
6919 backend.clone(),
6920 dir.path().to_path_buf(),
6921 1,
6922 DEFAULT_PERSIST_CHANNEL_CAPACITY,
6923 )
6924 .unwrap();
6925
6926 let address = Address::from_low_u64_be(0xbeef);
6931 let account_hash = hash_address_fixed(&address);
6932 let slot = H256::from_low_u64_be(1);
6933 let slot_hash = hash_key_fixed(&slot);
6934 let generator_account = AccountState {
6935 nonce: 7,
6936 balance: U256::from(999u64),
6937 storage_root: H256::zero(),
6938 code_hash: H256::zero(),
6939 };
6940 let generator_slot_value = U256::from(42u64);
6941 {
6942 let mut tx = backend.begin_write().unwrap();
6943 tx.put(
6944 ACCOUNT_FLATKEYVALUE,
6945 &Nibbles::from_bytes(account_hash.as_bytes()).into_vec(),
6946 &generator_account.encode_to_vec(),
6947 )
6948 .unwrap();
6949 tx.put(
6950 STORAGE_FLATKEYVALUE,
6951 &apply_prefix(Some(account_hash), Nibbles::from_bytes(&slot_hash)).into_vec(),
6952 &generator_slot_value.encode_to_vec(),
6953 )
6954 .unwrap();
6955 tx.commit().unwrap();
6956 }
6957
6958 let pivot_root = EMPTY_TRIE_HASH;
6963 {
6964 let mut tx = backend.begin_write().unwrap();
6965 for (n, parent_root) in [(10u64, pivot_root), (11, H256::repeat_byte(0x10))] {
6966 let entry = JournalEntry {
6967 block_hash: H256::repeat_byte(n as u8),
6968 parent_state_root: parent_root,
6969 account_trie_diff: vec![],
6970 storage_trie_diff: vec![],
6971 account_flat_diff: vec![],
6972 storage_flat_diff: vec![],
6973 };
6974 tx.put(STATE_HISTORY, &n.to_be_bytes(), &entry.encode())
6975 .unwrap();
6976 }
6977 tx.commit().unwrap();
6978 }
6979
6980 store
6981 .install_overlay_for_reorg(11, 10, |_| None)
6982 .expect("overlay install must succeed");
6983
6984 let accounts = store
6987 .get_account_states_batch_by_root(pivot_root, &[address])
6988 .unwrap();
6989 assert_eq!(
6990 accounts,
6991 vec![None],
6992 "batch account read must not serve the generator's stale flat-KV value"
6993 );
6994
6995 let slot_value = store
6996 .get_storage_at_root(pivot_root, address, slot)
6997 .unwrap();
6998 assert_eq!(
6999 slot_value, None,
7000 "storage read must not serve the generator's stale flat-KV value"
7001 );
7002
7003 let batched_slots = store
7004 .get_storage_values_batch_by_root(
7005 pivot_root,
7006 &[(hash_address_fixed(&address), EMPTY_TRIE_HASH, slot)],
7007 )
7008 .unwrap();
7009 assert_eq!(
7010 batched_slots,
7011 vec![None],
7012 "batch storage read must not serve the generator's stale flat-KV value"
7013 );
7014
7015 let state_trie = store.open_state_trie(pivot_root).unwrap();
7016 assert_eq!(
7017 state_trie.get(account_hash.as_bytes()).unwrap(),
7018 None,
7019 "trie read must not serve the generator's stale flat-KV value"
7020 );
7021
7022 let unserved_root = H256::repeat_byte(0x99);
7025 let accounts = store
7026 .get_account_states_batch_by_root(unserved_root, &[address])
7027 .unwrap();
7028 assert_eq!(
7029 accounts,
7030 vec![Some(generator_account)],
7031 "unserved roots must keep the flat-KV fast path"
7032 );
7033 let slot_value = store
7034 .get_storage_at_root(unserved_root, address, slot)
7035 .unwrap();
7036 assert_eq!(
7037 slot_value,
7038 Some(generator_slot_value),
7039 "unserved roots must keep the flat-KV fast path"
7040 );
7041
7042 let batched_slots = store
7043 .get_storage_values_batch_by_root(
7044 unserved_root,
7045 &[(hash_address_fixed(&address), EMPTY_TRIE_HASH, slot)],
7046 )
7047 .unwrap();
7048 assert_eq!(
7049 batched_slots,
7050 vec![Some(generator_slot_value)],
7051 "unserved roots must keep the flat-KV fast path"
7052 );
7053 }
7054
7055 #[tokio::test]
7063 async fn overlay_backed_commit_only_commits_bottom_layer_per_pass() {
7064 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
7065 let dir = tempfile::tempdir().unwrap();
7066 let store = Store::from_backend(
7067 backend.clone(),
7068 dir.path().to_path_buf(),
7069 4,
7070 DEFAULT_PERSIST_CHANNEL_CAPACITY,
7071 )
7072 .unwrap();
7073
7074 let pivot_root = H256::repeat_byte(0x09);
7077 {
7078 let mut tx = backend.begin_write().unwrap();
7079 for (n, parent_root) in [(10u64, pivot_root), (11, H256::repeat_byte(0x0a))] {
7080 let entry = JournalEntry {
7081 block_hash: H256::repeat_byte(n as u8),
7082 parent_state_root: parent_root,
7083 account_trie_diff: vec![(vec![0x00, n as u8], None)],
7084 storage_trie_diff: vec![],
7085 account_flat_diff: vec![],
7086 storage_flat_diff: vec![],
7087 };
7088 tx.put(STATE_HISTORY, &n.to_be_bytes(), &entry.encode())
7089 .unwrap();
7090 }
7091 tx.commit().unwrap();
7092 }
7093 store.install_overlay_for_reorg(11, 10, |_| None).unwrap();
7094
7095 let roots: Vec<H256> = (10..=12u8).map(H256::repeat_byte).collect();
7097 {
7098 let mut guard = store.trie_cache.write().unwrap();
7099 let mut updated = (**guard).clone();
7100 let mut parent = pivot_root;
7101 for (i, root) in roots.iter().enumerate() {
7102 let n = 10 + i as u64;
7103 updated.put_batch(
7104 parent,
7105 *root,
7106 n,
7107 H256::repeat_byte(n as u8),
7108 vec![(Nibbles::from_raw(&[0x01, n as u8], false), vec![n as u8])],
7109 );
7110 parent = *root;
7111 }
7112 *guard = Arc::new(updated);
7113 }
7114
7115 let trie = store.trie_cache.read().unwrap().clone();
7118 commit_to_disk(
7119 store.backend.as_ref(),
7120 &store.flatkeyvalue_control_tx,
7121 &store.trie_cache,
7122 &trie,
7123 roots[2],
7124 false,
7125 )
7126 .expect("bottom-layer commit must succeed (no debug_assert panic)");
7127
7128 assert!(
7130 !store.is_state_in_layer_cache(roots[0]).unwrap(),
7131 "bottom layer must be committed and pruned"
7132 );
7133 assert!(
7134 store.is_state_in_layer_cache(roots[1]).unwrap()
7135 && store.is_state_in_layer_cache(roots[2]).unwrap(),
7136 "upper layers must remain resident after the bottom-layer-only commit"
7137 );
7138 assert!(
7140 store.trie_cache.read().unwrap().overlay().is_none(),
7141 "reconciliation must clear the overlay"
7142 );
7143 assert!(
7145 journal_entry_exists(&backend, 10),
7146 "bottom layer T must be journaled"
7147 );
7148 assert!(!journal_entry_exists(&backend, 11));
7149 assert!(!journal_entry_exists(&backend, 12));
7150
7151 let entry_bytes = backend
7156 .begin_read()
7157 .unwrap()
7158 .get(STATE_HISTORY, &10u64.to_be_bytes())
7159 .unwrap()
7160 .expect("journal entry for block 10");
7161 let entry = JournalEntry::decode(&entry_bytes).unwrap();
7162 let diff_keys: Vec<&Vec<u8>> = entry.account_trie_diff.iter().map(|(k, _)| k).collect();
7163 assert!(
7164 diff_keys.contains(&&vec![0x00, 10]) && diff_keys.contains(&&vec![0x00, 11]),
7165 "bridge keys from the overlay must be folded into T's journal entry, got {diff_keys:?}"
7166 );
7167
7168 let trie = store.trie_cache.read().unwrap().clone();
7170 commit_to_disk(
7171 store.backend.as_ref(),
7172 &store.flatkeyvalue_control_tx,
7173 &store.trie_cache,
7174 &trie,
7175 roots[2],
7176 false,
7177 )
7178 .unwrap();
7179 assert!(!store.is_state_in_layer_cache(roots[1]).unwrap());
7180 assert!(!store.is_state_in_layer_cache(roots[2]).unwrap());
7181 assert!(journal_entry_exists(&backend, 11));
7182 assert!(journal_entry_exists(&backend, 12));
7183 }
7184
7185 #[tokio::test]
7190 async fn journal_pruning_pauses_during_reorg_and_catches_up_after() {
7191 let backend: Arc<dyn StorageBackend> = Arc::new(InMemoryBackend::open().unwrap());
7192 let dir = tempfile::tempdir().unwrap();
7193 let store = Store::from_backend(
7194 backend.clone(),
7195 dir.path().to_path_buf(),
7196 1,
7197 DEFAULT_PERSIST_CHANNEL_CAPACITY,
7198 )
7199 .unwrap();
7200
7201 seed_journal_entries(&backend, &(1..=5).collect::<Vec<_>>());
7202
7203 store.set_journal_pruning_paused(true);
7205 store
7206 .forkchoice_update_inner(vec![], 100, H256::zero(), None, Some(3))
7207 .await
7208 .unwrap();
7209 for n in 1..=5 {
7210 assert!(
7211 journal_entry_exists(&backend, n),
7212 "entry {n} must survive pruning while the reorg pause is held"
7213 );
7214 }
7215
7216 store.set_journal_pruning_paused(false);
7218 store
7219 .forkchoice_update_inner(vec![], 100, H256::zero(), None, Some(4))
7220 .await
7221 .unwrap();
7222 for n in 1..=4 {
7223 assert!(
7224 !journal_entry_exists(&backend, n),
7225 "entry {n} must be pruned once the pause is released"
7226 );
7227 }
7228 assert!(journal_entry_exists(&backend, 5));
7229 }
7230}
7231
7232#[cfg(test)]
7233mod merge_tests {
7234 use super::*;
7235
7236 fn h256(b: u8) -> H256 {
7237 H256::from_low_u64_be(b as u64)
7238 }
7239
7240 fn op(bn: BlockNumber, bh: H256, idx: Index) -> Vec<u8> {
7241 encode_tx_location_operand(bn, bh, idx)
7242 }
7243
7244 fn decode(v: &[u8]) -> Vec<(BlockNumber, BlockHash, Index)> {
7245 <Vec<(BlockNumber, BlockHash, Index)>>::decode(v).unwrap()
7246 }
7247
7248 #[test]
7249 fn single_operand_on_empty_base() {
7250 let out = tx_locations_merge(None, vec![op(100, h256(0x10), 0)]).unwrap();
7251 assert_eq!(decode(&out), vec![(100, h256(0x10), 0)]);
7252 }
7253
7254 #[test]
7255 fn operand_appended_to_existing_base() {
7256 let base = vec![(100u64, h256(0x10), 0u64)].encode_to_vec();
7257 let out = tx_locations_merge(Some(&base), vec![op(101, h256(0x11), 5)]).unwrap();
7258 let mut got = decode(&out);
7259 got.sort();
7260 let mut want = vec![(100, h256(0x10), 0), (101, h256(0x11), 5)];
7261 want.sort();
7262 assert_eq!(got, want);
7263 }
7264
7265 #[test]
7266 fn multiple_operands_combined() {
7267 let out = tx_locations_merge(
7268 None,
7269 vec![
7270 op(100, h256(0x10), 0),
7271 op(100, h256(0x11), 1),
7272 op(101, h256(0x12), 2),
7273 ],
7274 )
7275 .unwrap();
7276 assert_eq!(decode(&out).len(), 3);
7277 }
7278
7279 #[test]
7280 fn same_block_hash_is_deduped() {
7281 let out =
7283 tx_locations_merge(None, vec![op(100, h256(0x10), 0), op(100, h256(0x10), 7)]).unwrap();
7284 assert_eq!(decode(&out), vec![(100, h256(0x10), 7)]);
7285 }
7286
7287 #[test]
7288 fn malformed_operand_aborts_merge() {
7289 let out = tx_locations_merge(None, vec![vec![0xff, 0xff], op(100, h256(0x10), 0)]);
7292 assert!(out.is_none(), "merge must abort on a malformed operand");
7293 }
7294
7295 #[test]
7296 fn malformed_base_value_aborts_merge() {
7297 let out = tx_locations_merge(Some(&[0xff, 0xff]), vec![op(100, h256(0x10), 0)]);
7298 assert!(out.is_none(), "merge must abort on a corrupt base value");
7299 }
7300
7301 #[test]
7308 fn partial_merge_result_is_a_valid_operand() {
7309 let partial =
7311 tx_locations_merge(None, vec![op(100, h256(0x10), 0), op(101, h256(0x11), 1)]).unwrap();
7312
7313 let base = vec![(99u64, h256(0x09), 9u64)].encode_to_vec();
7317 let out = tx_locations_merge(Some(&base), vec![partial]).unwrap();
7318
7319 let mut got = decode(&out);
7320 got.sort();
7321 let mut want = vec![
7322 (99, h256(0x09), 9),
7323 (100, h256(0x10), 0),
7324 (101, h256(0x11), 1),
7325 ];
7326 want.sort();
7327 assert_eq!(
7328 got, want,
7329 "no entries may be lost when re-merging a partial result"
7330 );
7331 }
7332
7333 #[test]
7336 fn operand_encoding_matches_value_encoding() {
7337 let operand = op(100, h256(0x10), 3);
7338 assert_eq!(decode(&operand), vec![(100, h256(0x10), 3)]);
7340 }
7341
7342 #[test]
7344 fn chained_partial_merges() {
7345 let p1 = tx_locations_merge(None, vec![op(1, h256(0x01), 0)]).unwrap();
7346 let p2 = tx_locations_merge(None, vec![p1, op(2, h256(0x02), 0)]).unwrap();
7347 let p3 = tx_locations_merge(None, vec![p2, op(3, h256(0x03), 0)]).unwrap();
7348 let out = tx_locations_merge(None, vec![p3]).unwrap();
7349 assert_eq!(decode(&out).len(), 3);
7350 }
7351}
7352
7353#[cfg(test)]
7354mod backfill_write_tests {
7355 use super::*;
7356 use ethrex_common::types::{EIP1559Transaction, TxType};
7357
7358 fn test_header(number: BlockNumber) -> BlockHeader {
7359 BlockHeader {
7360 number,
7361 ..Default::default()
7362 }
7363 }
7364
7365 fn test_tx(nonce: u64) -> Transaction {
7366 Transaction::EIP1559Transaction(EIP1559Transaction {
7367 nonce,
7368 ..Default::default()
7369 })
7370 }
7371
7372 fn test_receipt() -> Receipt {
7373 Receipt {
7374 tx_type: TxType::EIP1559,
7375 succeeded: true,
7376 cumulative_gas_used: 21_000,
7377 logs: vec![],
7378 payer: None,
7379 frame_receipts: None,
7380 }
7381 }
7382
7383 #[tokio::test]
7387 async fn writes_data_indexes_txs_and_lowers_frontier() {
7388 let store = Store::new("", EngineType::InMemory).expect("in-memory store");
7389 store.update_earliest_block_number(100).await.unwrap();
7390
7391 let header = test_header(99);
7392 let hash = header.hash();
7393 let tx = test_tx(7);
7394 let body = BlockBody {
7395 transactions: vec![tx.clone()],
7396 ommers: vec![],
7397 withdrawals: Some(vec![]),
7398 };
7399 store.add_block_headers(vec![header.clone()]).await.unwrap();
7403 store
7404 .forkchoice_update(vec![(99, hash)], 99, hash, None, None)
7405 .await
7406 .unwrap();
7407
7408 store
7409 .add_backfilled_blocks(
7410 vec![BackfilledBlock {
7411 header,
7412 body,
7413 receipts: vec![test_receipt()],
7414 index_transactions: true,
7415 }],
7416 99,
7417 )
7418 .await
7419 .unwrap();
7420
7421 assert_eq!(
7422 store.get_earliest_block_number().await.unwrap(),
7423 99,
7424 "frontier must be lowered atomically with the data write"
7425 );
7426 assert!(
7427 store.get_block_body_by_hash(hash).await.unwrap().is_some(),
7428 "body must be stored"
7429 );
7430 assert_eq!(
7431 store.get_receipts_for_block(&hash).await.unwrap().len(),
7432 1,
7433 "receipts must be stored"
7434 );
7435 assert_eq!(
7436 store
7437 .get_transaction_location(tx.hash(&NativeCrypto))
7438 .await
7439 .unwrap(),
7440 Some((99, hash, 0)),
7441 "indexed transaction must be locatable"
7442 );
7443 }
7444
7445 #[tokio::test]
7449 async fn skips_tx_index_when_disabled() {
7450 let store = Store::new("", EngineType::InMemory).expect("in-memory store");
7451 store.update_earliest_block_number(100).await.unwrap();
7452
7453 let header = test_header(99);
7454 let hash = header.hash();
7455 let tx = test_tx(7);
7456 let body = BlockBody {
7457 transactions: vec![tx.clone()],
7458 ommers: vec![],
7459 withdrawals: Some(vec![]),
7460 };
7461 store.add_block_headers(vec![header.clone()]).await.unwrap();
7462 store
7463 .forkchoice_update(vec![(99, hash)], 99, hash, None, None)
7464 .await
7465 .unwrap();
7466
7467 store
7468 .add_backfilled_blocks(
7469 vec![BackfilledBlock {
7470 header,
7471 body,
7472 receipts: vec![test_receipt()],
7473 index_transactions: false,
7474 }],
7475 99,
7476 )
7477 .await
7478 .unwrap();
7479
7480 assert!(
7481 store.get_block_body_by_hash(hash).await.unwrap().is_some(),
7482 "body must still be stored"
7483 );
7484 assert_eq!(
7485 store.get_receipts_for_block(&hash).await.unwrap().len(),
7486 1,
7487 "receipts must still be stored"
7488 );
7489 assert_eq!(
7490 store
7491 .get_transaction_location(tx.hash(&NativeCrypto))
7492 .await
7493 .unwrap(),
7494 None,
7495 "transaction index must be skipped when index_transactions is false"
7496 );
7497 }
7498
7499 #[tokio::test]
7503 async fn historical_getters_transition_from_null_to_present() {
7504 let store = Store::new("", EngineType::InMemory).expect("in-memory store");
7505 store.update_earliest_block_number(100).await.unwrap();
7506
7507 let header = test_header(99);
7508 let hash = header.hash();
7509 let tx = test_tx(7);
7510 let tx_hash = tx.hash(&NativeCrypto);
7511 let body = BlockBody {
7512 transactions: vec![tx],
7513 ommers: vec![],
7514 withdrawals: Some(vec![]),
7515 };
7516 store.add_block_headers(vec![header.clone()]).await.unwrap();
7518 store
7519 .forkchoice_update(vec![(99, hash)], 99, hash, None, None)
7520 .await
7521 .unwrap();
7522
7523 assert!(
7525 store.get_block_body(99).await.unwrap().is_none(),
7526 "body absent before backfill"
7527 );
7528 assert!(
7529 store
7530 .get_transaction_by_hash(tx_hash)
7531 .await
7532 .unwrap()
7533 .is_none(),
7534 "tx unlocatable before backfill"
7535 );
7536 assert!(
7537 store.get_receipt(99, 0).await.unwrap().is_none(),
7538 "receipt absent before backfill"
7539 );
7540
7541 store
7542 .add_backfilled_blocks(
7543 vec![BackfilledBlock {
7544 header,
7545 body,
7546 receipts: vec![test_receipt()],
7547 index_transactions: true,
7548 }],
7549 99,
7550 )
7551 .await
7552 .unwrap();
7553
7554 assert!(
7556 store.get_block_body(99).await.unwrap().is_some(),
7557 "body present after backfill"
7558 );
7559 assert!(
7560 store
7561 .get_transaction_by_hash(tx_hash)
7562 .await
7563 .unwrap()
7564 .is_some(),
7565 "tx locatable after backfill"
7566 );
7567 assert!(
7568 store.get_receipt(99, 0).await.unwrap().is_some(),
7569 "receipt present after backfill"
7570 );
7571 }
7572
7573 #[tokio::test]
7576 async fn resume_fills_contiguously_without_gaps() {
7577 let store = Store::new("", EngineType::InMemory).expect("in-memory store");
7578 store.update_earliest_block_number(100).await.unwrap();
7579
7580 let make_batch = |range: std::ops::Range<u64>| -> Vec<BackfilledBlock> {
7581 range
7582 .rev()
7583 .map(|n| BackfilledBlock {
7584 header: test_header(n),
7585 body: BlockBody {
7586 transactions: vec![],
7587 ommers: vec![],
7588 withdrawals: Some(vec![]),
7589 },
7590 receipts: vec![],
7591 index_transactions: false,
7592 })
7593 .collect()
7594 };
7595
7596 store
7598 .add_backfilled_blocks(make_batch(90..100), 90)
7599 .await
7600 .unwrap();
7601 assert_eq!(store.get_earliest_block_number().await.unwrap(), 90);
7602
7603 let resume_from = store.get_earliest_block_number().await.unwrap();
7605 assert_eq!(resume_from, 90, "resume reads the persisted frontier");
7606
7607 store
7609 .add_backfilled_blocks(make_batch(80..90), 80)
7610 .await
7611 .unwrap();
7612 assert_eq!(store.get_earliest_block_number().await.unwrap(), 80);
7613
7614 for n in 80..100 {
7616 assert!(
7617 store
7618 .get_block_body_by_hash(test_header(n).hash())
7619 .await
7620 .unwrap()
7621 .is_some(),
7622 "block {n} must have a body (no gap left by resume)"
7623 );
7624 }
7625 }
7626}
7627
7628#[cfg(test)]
7631#[cfg(feature = "rocksdb")]
7632mod schema_version_guard_tests {
7633 use super::*;
7634
7635 fn write_marker(dir: &Path, schema_version: u64) {
7636 let metadata = StoreMetadata::new(schema_version);
7637 std::fs::write(
7638 dir.join(STORE_METADATA_FILENAME),
7639 serde_json::to_string_pretty(&metadata).unwrap(),
7640 )
7641 .unwrap();
7642 }
7643
7644 fn read_marker(dir: &Path) -> u64 {
7645 read_store_schema_version(dir).unwrap().unwrap()
7646 }
7647
7648 #[test]
7649 fn a_database_ahead_of_the_binary_is_refused_and_left_untouched() {
7650 let dir = tempfile::tempdir().unwrap();
7656 let newer = STORE_SCHEMA_VERSION + 1;
7657 write_marker(dir.path(), newer);
7658
7659 let result =
7660 Store::new_with_config(dir.path(), EngineType::RocksDB, StoreConfig::default());
7661
7662 assert!(
7663 matches!(
7664 result,
7665 Err(StoreError::IncompatibleDBVersion { found, expected })
7666 if found == newer && expected == STORE_SCHEMA_VERSION
7667 ),
7668 "expected IncompatibleDBVersion, got {:?}",
7669 result.err()
7670 );
7671 assert_eq!(read_marker(dir.path()), newer);
7672 }
7673
7674 #[test]
7675 fn a_zero_schema_version_is_incompatible_not_a_failed_migration() {
7676 let dir = tempfile::tempdir().unwrap();
7679 write_marker(dir.path(), 0);
7680
7681 let result =
7682 Store::new_with_config(dir.path(), EngineType::RocksDB, StoreConfig::default());
7683
7684 assert!(
7685 matches!(
7686 result,
7687 Err(StoreError::IncompatibleDBVersion { found: 0, expected })
7688 if expected == STORE_SCHEMA_VERSION
7689 ),
7690 "expected IncompatibleDBVersion, got {:?}",
7691 result.err()
7692 );
7693 }
7694}
7695
7696#[cfg(test)]
7697mod datadir_tests {
7698 use super::*;
7699 use std::fs;
7700
7701 #[test]
7702 fn empty_dir_has_no_existing_db() {
7703 let dir = tempfile::tempdir().unwrap();
7704 assert!(!dir_contains_legacy_db(dir.path()).unwrap());
7705 }
7706
7707 #[test]
7708 fn dir_with_only_unrelated_files_has_no_existing_db() {
7709 let dir = tempfile::tempdir().unwrap();
7712 fs::write(dir.path().join("jwt.hex"), "0xdeadbeef").unwrap();
7713 fs::write(dir.path().join("LOG"), "noise").unwrap();
7714 assert!(!dir_contains_legacy_db(dir.path()).unwrap());
7715 }
7716
7717 #[test]
7718 fn dir_with_rocksdb_markers_has_existing_db() {
7719 let dir = tempfile::tempdir().unwrap();
7721 fs::write(dir.path().join("CURRENT"), "MANIFEST-000001\n").unwrap();
7722 assert!(dir_contains_legacy_db(dir.path()).unwrap());
7723
7724 let dir2 = tempfile::tempdir().unwrap();
7725 fs::write(dir2.path().join("MANIFEST-000007"), "x").unwrap();
7726 assert!(dir_contains_legacy_db(dir2.path()).unwrap());
7727 }
7728
7729 #[test]
7730 fn dir_with_marker_named_subdirectories_has_no_existing_db() {
7731 let dir = tempfile::tempdir().unwrap();
7734 fs::create_dir(dir.path().join("CURRENT")).unwrap();
7735 fs::create_dir(dir.path().join("MANIFEST-000001")).unwrap();
7736 assert!(!dir_contains_legacy_db(dir.path()).unwrap());
7737 }
7738}
7739
7740#[cfg(test)]
7741mod flatkeyvalue_completeness_tests {
7742 use super::*;
7743
7744 #[test]
7749 fn only_the_one_byte_ff_sentinel_counts_as_complete() {
7750 assert!(Store::flatkeyvalue_generation_complete(Some(&[0xff])));
7752
7753 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])));
7761 assert!(!Store::flatkeyvalue_generation_complete(Some(&[
7762 0xff, 0xff
7763 ])));
7764 }
7765}
7766
7767#[cfg(test)]
7768mod cgroup_memory_limit_tests {
7769 use super::*;
7770 use std::fs;
7771
7772 fn cgroup_tree(root: &Path, relative: &str, file: &str, limits: &[(&str, &str)]) {
7775 fs::create_dir_all(root.join(relative)).expect("create cgroup tree");
7776 for (dir, contents) in limits {
7777 fs::write(root.join(dir).join(file), contents).expect("write limit");
7778 }
7779 }
7780
7781 #[test]
7785 fn ancestor_limit_applies() {
7786 let tmp = tempfile::tempdir().expect("tempdir");
7787 let root = tmp.path();
7788 cgroup_tree(
7789 root,
7790 "system.slice/ethrex.service",
7791 "memory.max",
7792 &[("", "max"), ("system.slice", "4294967296")],
7793 );
7794
7795 assert_eq!(
7796 cgroup_limit_up_to_root(root, "/system.slice/ethrex.service", "memory.max"),
7797 Some(4 * 1024 * 1024 * 1024)
7798 );
7799 }
7800
7801 #[test]
7803 fn smallest_limit_in_the_chain_wins() {
7804 let tmp = tempfile::tempdir().expect("tempdir");
7805 let root = tmp.path();
7806 cgroup_tree(
7807 root,
7808 "kubepods/pod123/container",
7809 "memory.max",
7810 &[
7811 ("kubepods", "8589934592"),
7812 ("kubepods/pod123", "2147483648"),
7813 ("kubepods/pod123/container", "4294967296"),
7814 ],
7815 );
7816
7817 assert_eq!(
7818 cgroup_limit_up_to_root(root, "kubepods/pod123/container", "memory.max"),
7819 Some(2 * 1024 * 1024 * 1024)
7820 );
7821 }
7822
7823 #[test]
7826 fn unlimited_chain_yields_no_limit() {
7827 let tmp = tempfile::tempdir().expect("tempdir");
7828 let root = tmp.path();
7829 cgroup_tree(root, "docker/abc", "memory.max", &[("", "max")]);
7830
7831 assert_eq!(
7832 cgroup_limit_up_to_root(root, "docker/abc", "memory.max"),
7833 None
7834 );
7835 assert_eq!(
7838 cgroup_limit_up_to_root(root, "not/here", "memory.max"),
7839 None
7840 );
7841 }
7842
7843 #[test]
7845 fn root_limit_applies_to_the_root_cgroup() {
7846 let tmp = tempfile::tempdir().expect("tempdir");
7847 let root = tmp.path();
7848 cgroup_tree(root, "", "memory.limit_in_bytes", &[("", "536870912")]);
7849
7850 assert_eq!(
7851 cgroup_limit_up_to_root(root, "/", "memory.limit_in_bytes"),
7852 Some(512 * 1024 * 1024)
7853 );
7854 }
7855}