1pub mod constants;
46pub mod error;
47pub mod fork_choice;
48pub mod mempool;
49pub mod payload;
50pub mod tracing;
51pub mod vm;
52
53use ::tracing::{debug, error, info, instrument, warn};
54use constants::{AMSTERDAM_MAX_INITCODE_SIZE, MAX_INITCODE_SIZE, POST_OSAKA_GAS_LIMIT_CAP};
55use error::MempoolError;
56use error::{ChainError, InvalidBlockError};
57use ethrex_common::constants::{EMPTY_KECCAK_HASH, EMPTY_TRIE_HASH, MIN_BASE_FEE_PER_BLOB_GAS};
58
59use crossbeam::channel::{self as cb, TryRecvError, select};
60#[cfg(feature = "c-kzg")]
62use ethrex_common::types::EIP4844Transaction;
63#[cfg(feature = "c-kzg")]
64use ethrex_common::types::MAX_BLOB_TX_SIZE;
65use ethrex_common::types::MAX_TX_SIZE;
66use ethrex_common::types::block_access_list::BlockAccessList;
67use ethrex_common::types::block_execution_witness::ExecutionWitness;
68use ethrex_common::types::fee_config::FeeConfig;
69use ethrex_common::types::{
70 AccountInfo, AccountState, AccountUpdate, BalSynthesisItem, Block, BlockHash, BlockHeader,
71 BlockNumber, ChainConfig, Code, Receipt, Transaction, WrappedEIP4844Transaction,
72 synthesize_bal_updates, validate_block_body,
73};
74use ethrex_common::types::{EIP7702_DELEGATED_CODE_LEN, is_eip7702_delegation};
75use ethrex_common::types::{ELASTICITY_MULTIPLIER, P2PTransaction};
76use ethrex_common::types::{Fork, MempoolTransaction};
77use ethrex_common::utils::keccak;
78use ethrex_common::{Address, H256, TrieLogger, U256};
79pub use ethrex_common::{
80 get_total_blob_gas, validate_block_access_list_hash, validate_block_pre_execution,
81 validate_gas_used, validate_receipts_root_and_logs_bloom, validate_requests_hash,
82};
83use ethrex_crypto::NativeCrypto;
84use ethrex_metrics::metrics;
85use ethrex_rlp::constants::RLP_NULL;
86use ethrex_rlp::decode::RLPDecode;
87use ethrex_rlp::encode::RLPEncode;
88use ethrex_storage::{
89 AccountUpdatesList, Store, UpdateBatch, error::StoreError, hash_address, hash_key,
90};
91use ethrex_trie::node::{BranchNode, ExtensionNode, LeafNode};
92use ethrex_trie::{Nibbles, Node, NodeRef, Trie, TrieError, TrieNode};
93use ethrex_vm::backends::CachingDatabase;
94#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
95use ethrex_vm::backends::levm::LEVM;
96use ethrex_vm::backends::levm::db::DatabaseLogger;
97use ethrex_vm::{BlockExecutionResult, DynVmDatabase, Evm, EvmError};
98use mempool::Mempool;
99use payload::PayloadOrTask;
100use rustc_hash::{FxHashMap, FxHashSet};
101use std::collections::hash_map::Entry;
102use std::collections::{BTreeMap, HashMap, HashSet};
103use std::sync::LazyLock;
104use std::sync::mpsc::Sender;
105use std::sync::{
106 Arc, RwLock,
107 atomic::{AtomicBool, AtomicUsize, Ordering},
108 mpsc::{Receiver, channel},
109};
110use std::time::{Duration, Instant};
111use tokio::sync::Mutex as TokioMutex;
112use tokio_util::sync::CancellationToken;
113
114use vm::StoreVmDatabase;
115
116#[cfg(feature = "metrics")]
117use ethrex_metrics::bal::METRICS_BAL;
118#[cfg(feature = "metrics")]
119use ethrex_metrics::blocks::METRICS_BLOCKS;
120
121#[cfg(feature = "c-kzg")]
122use ethrex_common::types::BlobsBundle;
123
124const MAX_PAYLOADS: usize = 10;
125const MAX_MEMPOOL_SIZE_DEFAULT: usize = 10_000;
126
127static DROP_SENDER: LazyLock<Sender<Box<dyn Send>>> = LazyLock::new(|| {
131 let (tx, rx) = channel::<Box<dyn Send>>();
132 std::thread::Builder::new()
133 .name("drop_thread".to_string())
134 .spawn(move || for _ in rx {})
135 .expect("failed to spawn drop thread");
136 tx
137});
138
139type BlockExecutionPipelineResult = (
141 BlockExecutionResult,
142 AccountUpdatesList,
143 Option<Vec<AccountUpdate>>,
144 Option<BlockAccessList>, usize, [Instant; 7], Duration, );
149
150type AddBlockPipelineInnerResult = (
151 Option<BlockAccessList>,
152 Option<ExecutionWitness>,
153 Result<(), ChainError>,
154);
155
156#[derive(Debug, Clone, Default)]
161pub enum BlockchainType {
162 #[default]
164 L1,
165 L2(L2Config),
167}
168
169#[derive(Debug, Clone, Default)]
171pub struct L2Config {
172 pub fee_config: Arc<RwLock<FeeConfig>>,
176}
177
178#[derive(Debug)]
205pub struct Blockchain {
206 storage: Store,
208 pub mempool: Mempool,
210 is_synced: AtomicBool,
215 pub options: BlockchainOptions,
217 pub payloads: Arc<TokioMutex<Vec<(u64, PayloadOrTask)>>>,
222 merkle_pool: Arc<rayon::ThreadPool>,
229}
230
231#[derive(Debug, Clone)]
233pub struct BlockchainOptions {
234 pub max_mempool_size: usize,
236 pub perf_logs_enabled: bool,
238 pub r#type: BlockchainType,
240 pub max_blobs_per_block: Option<u32>,
243 pub precompute_witnesses: bool,
245 pub precompile_cache_enabled: bool,
249 pub bal_parallel_exec_enabled: bool,
253 pub bal_prefetch_enabled: bool,
257 pub bal_parallel_trie_enabled: bool,
262}
263
264impl Default for BlockchainOptions {
265 fn default() -> Self {
266 Self {
267 max_mempool_size: MAX_MEMPOOL_SIZE_DEFAULT,
268 perf_logs_enabled: false,
269 r#type: BlockchainType::default(),
270 max_blobs_per_block: None,
271 precompute_witnesses: false,
272 precompile_cache_enabled: true,
273 bal_parallel_exec_enabled: true,
274 bal_prefetch_enabled: true,
275 bal_parallel_trie_enabled: true,
276 }
277 }
278}
279
280#[derive(Debug, Clone)]
281pub struct BatchBlockProcessingFailure {
282 pub last_valid_hash: H256,
283 pub failed_block_hash: H256,
284}
285
286fn log_batch_progress(batch_size: u32, current_block: u32) {
287 let progress_needed = batch_size > 10;
288 const PERCENT_MARKS: [u32; 4] = [20, 40, 60, 80];
289 if progress_needed {
290 PERCENT_MARKS.iter().for_each(|mark| {
291 if (batch_size * mark) / 100 == current_block {
292 info!("[SYNCING] {mark}% of batch processed");
293 }
294 });
295 }
296}
297
298enum WorkerRequest {
299 ProcessAccount {
301 prefix: H256,
302 info: Option<AccountInfo>,
303 storage: FxHashMap<H256, U256>,
304 removed: bool,
305 removed_storage: bool,
306 },
307 FinishRouting,
309 MerklizeAccounts {
310 accounts: Vec<H256>,
311 },
312 CollectState {
313 tx: Sender<CollectedStateMsg>,
314 },
315 MerklizeStorage {
317 prefix: H256,
318 key: H256,
319 value: U256,
320 storage_root: H256,
321 },
322 DeleteStorage(H256),
323 RoutingDone {
325 from: u8,
326 },
327 StorageShard {
329 prefix: H256,
330 index: u8,
331 subroot: Box<BranchNode>,
332 nodes: Vec<TrieNode>,
333 },
334}
335
336struct CollectedStateMsg {
337 index: u8,
338 subroot: Box<BranchNode>,
339 state_nodes: Vec<TrieNode>,
340 storage_nodes: Vec<(H256, Vec<TrieNode>)>,
341}
342
343#[derive(Default)]
344struct PreMerkelizedAccountState {
345 storage_root: Option<Box<BranchNode>>,
346 nodes: Vec<TrieNode>,
347}
348
349struct BalStateWorkItem {
351 hashed_address: H256,
352 nonce: Option<u64>,
353 balance: Option<U256>,
354 code_hash: Option<H256>,
355 storage_root: Option<H256>,
357}
358
359impl Blockchain {
360 pub fn build_merkle_pool() -> Arc<rayon::ThreadPool> {
364 Arc::new(
365 rayon::ThreadPoolBuilder::new()
366 .num_threads(17)
367 .thread_name(|i| format!("merkle-worker-{i}"))
368 .build()
369 .expect("Failed to create merkle thread pool"),
370 )
371 }
372
373 pub fn new(store: Store, blockchain_opts: BlockchainOptions) -> Self {
374 Self {
375 storage: store,
376 mempool: Mempool::new(blockchain_opts.max_mempool_size),
377 is_synced: AtomicBool::new(false),
378 payloads: Arc::new(TokioMutex::new(Vec::new())),
379 options: blockchain_opts,
380 merkle_pool: Self::build_merkle_pool(),
381 }
382 }
383
384 pub fn default_with_store_and_pool(store: Store, pool: Arc<rayon::ThreadPool>) -> Self {
394 Self {
395 storage: store,
396 mempool: Mempool::new(MAX_MEMPOOL_SIZE_DEFAULT),
397 is_synced: AtomicBool::new(false),
398 payloads: Arc::new(TokioMutex::new(Vec::new())),
399 options: BlockchainOptions::default(),
400 merkle_pool: pool,
401 }
402 }
403
404 pub fn default_with_store(store: Store) -> Self {
405 Self {
406 storage: store,
407 mempool: Mempool::new(MAX_MEMPOOL_SIZE_DEFAULT),
408 is_synced: AtomicBool::new(false),
409 payloads: Arc::new(TokioMutex::new(Vec::new())),
410 options: BlockchainOptions::default(),
411 merkle_pool: Self::build_merkle_pool(),
412 }
413 }
414
415 fn validate_l1_transaction_types(&self, block: &Block) -> Result<(), ChainError> {
422 if !matches!(self.options.r#type, BlockchainType::L1) {
423 return Ok(());
424 }
425 for tx in &block.body.transactions {
426 if tx.tx_type().is_l2_only() {
427 return Err(ChainError::InvalidBlock(
428 InvalidBlockError::UnsupportedTransactionType(tx.tx_type() as u8),
429 ));
430 }
431 }
432 Ok(())
433 }
434
435 fn execute_block(
437 &self,
438 block: &Block,
439 ) -> Result<(BlockExecutionResult, Vec<AccountUpdate>), ChainError> {
440 let Ok(parent_header) = find_parent_header(&block.header, &self.storage) else {
442 self.storage.add_pending_block(block.clone())?;
444 return Err(ChainError::ParentNotFound);
445 };
446
447 let chain_config = self.storage.get_chain_config();
448
449 validate_block_pre_execution(block, &parent_header, &chain_config, ELASTICITY_MULTIPLIER)?;
451 self.validate_l1_transaction_types(block)?;
452
453 let vm_db = StoreVmDatabase::new(self.storage.clone(), parent_header)?;
454 let mut vm = self.new_evm(vm_db)?;
455
456 let (execution_result, bal) = vm.execute_block(block)?;
457 let account_updates = vm.get_state_transitions()?;
458
459 if let Err(e) = validate_gas_used(execution_result.block_gas_used, &block.header) {
461 ethrex_vm::log_gas_used_mismatch(
462 &execution_result.tx_gas_breakdowns,
463 block.header.number,
464 execution_result.block_gas_used,
465 block.header.gas_used,
466 );
467 return Err(e.into());
468 }
469 validate_receipts_root_and_logs_bloom(
470 &block.header,
471 &execution_result.receipts,
472 &NativeCrypto,
473 )?;
474 validate_requests_hash(&block.header, &chain_config, &execution_result.requests)?;
475 if let Some(bal) = &bal {
476 validate_block_access_list_hash(
477 &block.header,
478 &chain_config,
479 bal,
480 block.body.transactions.len(),
481 &NativeCrypto,
482 )?;
483 }
484
485 Ok((execution_result, account_updates))
486 }
487
488 pub fn generate_bal_for_block(
492 &self,
493 block: &Block,
494 ) -> Result<Option<BlockAccessList>, ChainError> {
495 let chain_config = self.storage.get_chain_config();
496
497 if !chain_config.is_amsterdam_activated(block.header.timestamp) {
499 return Ok(None);
500 }
501
502 let parent_header = find_parent_header(&block.header, &self.storage)?;
504
505 let vm_db = StoreVmDatabase::new(self.storage.clone(), parent_header)?;
507 let mut vm = self.new_evm(vm_db)?;
508
509 let (_execution_result, bal) = vm.execute_block(block)?;
510
511 Ok(bal)
512 }
513
514 #[instrument(
516 level = "trace",
517 name = "Execute Block",
518 skip_all,
519 fields(namespace = "block_execution")
520 )]
521 fn execute_block_pipeline(
522 &self,
523 block: &Block,
524 parent_header: &BlockHeader,
525 vm: &mut Evm,
526 bal: Option<Arc<BlockAccessList>>,
527 collect_witness: bool,
528 ) -> Result<BlockExecutionPipelineResult, ChainError> {
529 let start_instant = Instant::now();
530
531 let chain_config = self.storage.get_chain_config();
532
533 validate_block_pre_execution(block, parent_header, &chain_config, ELASTICITY_MULTIPLIER)?;
535 self.validate_l1_transaction_types(block)?;
536 validate_block_body(&block.header, &block.body, &NativeCrypto)
537 .map_err(|e| ChainError::InvalidBlock(InvalidBlockError::InvalidBody(e)))?;
538 let block_validated_instant = Instant::now();
539
540 let exec_merkle_start = Instant::now();
541 let queue_length = AtomicUsize::new(0);
542 let queue_length_ref = &queue_length;
543 let mut max_queue_length = 0;
544
545 let original_store = vm.db.store.clone();
548 let caching_store: Arc<dyn ethrex_vm::backends::LevmDatabase> = Arc::new(
549 CachingDatabase::new(original_store, self.options.precompile_cache_enabled),
550 );
551
552 vm.db.store = caching_store.clone();
554
555 let cancelled = AtomicBool::new(false);
556 let bal_parallel_exec_enabled = self.options.bal_parallel_exec_enabled && !collect_witness;
561
562 let optimistic_updates: Option<FxHashMap<Address, BalSynthesisItem>> =
572 if self.options.bal_parallel_trie_enabled && !collect_witness {
573 bal.as_deref().map(synthesize_bal_updates)
574 } else {
575 None
576 };
577
578 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
613 if self.options.bal_prefetch_enabled
614 && !collect_witness
615 && let Some(bal_ref) = bal.as_ref()
616 {
617 let slots = LEVM::bal_storage_slots(bal_ref);
618 if !slots.is_empty() {
619 let _ = caching_store.prefetch_storage(&slots);
620 }
621 }
622
623 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
625 let bal_warmer = bal.clone();
626
627 let (execution_result, merkleization_result, warmer_duration) = std::thread::scope(
628 |s| -> Result<_, ChainError> {
629 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
630 let vm_type = vm.vm_type;
631 let cancelled_ref = &cancelled;
632 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
633 let bal_prefetch_enabled = self.options.bal_prefetch_enabled;
634 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
635 let warm_handle = (!collect_witness)
636 .then(|| {
637 std::thread::Builder::new()
638 .name("block_executor_warmer".to_string())
639 .spawn_scoped(s, move || {
640 let start = Instant::now();
643 if let Some(bal) = bal_warmer {
644 if bal_prefetch_enabled {
645 if let Err(e) = LEVM::warm_block_from_bal(
647 &bal,
648 caching_store,
649 cancelled_ref,
650 ) {
651 debug!("BAL warming failed (non-fatal): {e}");
652 }
653 } else if !bal_parallel_exec_enabled {
654 if let Err(e) = LEVM::warm_block(
660 block,
661 caching_store,
662 vm_type,
663 &NativeCrypto,
664 cancelled_ref,
665 ) {
666 debug!("Block warming failed (non-fatal): {e}");
667 }
668 }
669 } else {
670 if let Err(e) = LEVM::warm_block(
672 block,
673 caching_store,
674 vm_type,
675 &NativeCrypto,
676 cancelled_ref,
677 ) {
678 debug!("Block warming failed (non-fatal): {e}");
679 }
680 }
681 start.elapsed()
682 })
683 .map_err(|e| {
684 ChainError::Custom(format!("Failed to spawn warmer thread: {e}"))
685 })
686 })
687 .transpose()?;
688 let max_queue_length_ref = &mut max_queue_length;
689 let (tx, rx_for_merkle) =
699 if optimistic_updates.is_some() && bal_parallel_exec_enabled {
700 (None, None)
705 } else {
706 let (tx, rx) = channel();
707 (Some(tx), Some(rx))
708 };
709
710 let execution_handle = std::thread::Builder::new()
711 .name("block_executor_execution".to_string())
712 .spawn_scoped(s, move || -> Result<_, ChainError> {
713 let header_bal = bal.clone();
717 let result = vm.execute_block_pipeline(
718 block,
719 tx,
720 queue_length_ref,
721 bal,
722 bal_parallel_exec_enabled,
723 );
724 cancelled_ref.store(true, Ordering::Relaxed);
725 let (execution_result, produced_bal) = result?;
726
727 if let Err(e) =
729 validate_gas_used(execution_result.block_gas_used, &block.header)
730 {
731 ethrex_vm::log_gas_used_mismatch(
732 &execution_result.tx_gas_breakdowns,
733 block.header.number,
734 execution_result.block_gas_used,
735 block.header.gas_used,
736 );
737 return Err(e.into());
738 }
739 validate_receipts_root_and_logs_bloom(
740 &block.header,
741 &execution_result.receipts,
742 &NativeCrypto,
743 )?;
744 validate_requests_hash(
745 &block.header,
746 &chain_config,
747 &execution_result.requests,
748 )?;
749 if let Some(bal) = &produced_bal {
770 validate_block_access_list_hash(
771 &block.header,
772 &chain_config,
773 bal,
774 block.body.transactions.len(),
775 &NativeCrypto,
776 )?;
777 } else if let Some(header_bal) = header_bal.as_deref()
778 && chain_config.is_amsterdam_activated(block.header.timestamp)
779 && !header_bal.matches_commitment(
780 block.header.block_access_list_hash,
781 &NativeCrypto,
782 )
783 {
784 return Err(InvalidBlockError::BlockAccessListHashMismatch.into());
785 }
786
787 let exec_end_instant = Instant::now();
788 Ok((execution_result, produced_bal, exec_end_instant))
789 })
790 .map_err(|e| {
791 ChainError::Custom(format!("Failed to spawn execution thread: {e}"))
792 })?;
793 let parent_header_ref = &parent_header; type MerkleResult = Result<
796 (
797 AccountUpdatesList,
798 Option<Vec<AccountUpdate>>,
799 Instant,
800 Instant,
801 ),
802 StoreError,
803 >;
804 let merkleize_handle = std::thread::Builder::new()
805 .name("block_executor_merkleizer".to_string())
806 .spawn_scoped(s, move || -> MerkleResult {
807 let merkle_start_instant = Instant::now();
808 let (account_updates_list, streaming_witness) = match rx_for_merkle {
817 None => {
818 let prepared = optimistic_updates.expect(
819 "optimistic updates are present when the streaming channel is absent",
820 );
821 let list = self.handle_merkleization_bal_from_updates(
822 prepared,
823 parent_header_ref,
824 )?;
825 if let Some(rx) = rx_for_merkle {
834 for _ in rx {}
835 }
836 (list, None)
837 }
838 Some(rx) => self.handle_merkleization(
839 rx,
840 parent_header_ref,
841 queue_length_ref,
842 max_queue_length_ref,
843 collect_witness,
844 )?,
845 };
846 let merkle_end_instant = Instant::now();
847 Ok((
848 account_updates_list,
849 streaming_witness,
850 merkle_start_instant,
851 merkle_end_instant,
852 ))
853 })
854 .map_err(|e| {
855 ChainError::Custom(format!("Failed to spawn merkleizer thread: {e}"))
856 })?;
857 let execution_result = execution_handle.join().unwrap_or_else(|_| {
858 Err(ChainError::Custom("execution thread panicked".to_string()))
859 });
860 let merkleization_result = merkleize_handle.join().unwrap_or_else(|_| {
861 Err(StoreError::Custom(
862 "merkleization thread panicked".to_string(),
863 ))
864 });
865 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
866 let warmer_duration = warm_handle
867 .map(|handle| {
868 handle
869 .join()
870 .inspect_err(|e| warn!("Warming thread error: {e:?}"))
871 .ok()
872 .unwrap_or(Duration::ZERO)
873 })
874 .unwrap_or(Duration::ZERO);
875 #[cfg(any(not(feature = "rayon"), feature = "eip-8025"))]
876 let warmer_duration = Duration::ZERO;
877 Ok((execution_result, merkleization_result, warmer_duration))
878 },
879 )?;
880 let (account_updates_list, streaming_witness, merkle_start_instant, merkle_end_instant) =
881 merkleization_result?;
882 let (execution_result, produced_bal, exec_end_instant) = execution_result?;
883
884 let accumulated_updates = streaming_witness;
888
889 let exec_merkle_end_instant = Instant::now();
890
891 Ok((
892 execution_result,
893 account_updates_list,
894 accumulated_updates,
895 produced_bal,
896 max_queue_length,
897 [
898 start_instant,
899 block_validated_instant,
900 exec_merkle_start,
901 merkle_start_instant,
902 exec_end_instant,
903 merkle_end_instant,
904 exec_merkle_end_instant,
905 ],
906 warmer_duration,
907 ))
908 }
909
910 #[instrument(
911 level = "trace",
912 name = "Trie update",
913 skip_all,
914 fields(namespace = "block_execution")
915 )]
916 fn handle_merkleization(
917 &self,
918 rx: Receiver<Vec<AccountUpdate>>,
919 parent_header: &BlockHeader,
920 queue_length: &AtomicUsize,
921 max_queue_length: &mut usize,
922 collect_witness: bool,
923 ) -> Result<(AccountUpdatesList, Option<Vec<AccountUpdate>>), StoreError> {
924 let parent_state_root = parent_header.state_root;
925
926 let mut workers_tx = Vec::with_capacity(16);
928 let mut workers_rx = Vec::with_capacity(16);
929 for _ in 0..16 {
930 let (tx, rx) = cb::unbounded();
931 workers_tx.push(tx);
932 workers_rx.push(rx);
933 }
934
935 let (shutdown_tx, shutdown_rx) = cb::bounded::<()>(0);
937 let (done_tx, done_rx) = cb::unbounded::<Result<(), StoreError>>();
939
940 let watcher_error: Arc<std::sync::Mutex<Option<StoreError>>> = Default::default();
945 let result = self.merkle_pool.in_place_scope(|s| {
946 for (i, rx) in workers_rx.into_iter().enumerate() {
948 let all_senders = workers_tx.clone();
949 let storage_clone = self.storage.clone();
950 let shutdown_rx = shutdown_rx.clone();
951 let done_tx = done_tx.clone();
952 s.spawn(move |_| {
953 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
954 handle_subtrie(
955 storage_clone,
956 rx,
957 parent_state_root,
958 i as u8,
959 all_senders,
960 shutdown_rx,
961 )
962 }));
963 let result = match result {
964 Ok(r) => r,
965 Err(_) => Err(StoreError::Custom(format!("shard worker {i} panicked"))),
966 };
967 if let Err(cb::SendError(Err(e))) = done_tx.send(result) {
968 error!("Failed to send worker {i} error to watcher: {e}");
969 }
970 });
971 }
972 drop(done_tx); drop(shutdown_rx); let watcher_error = watcher_error.clone();
978 s.spawn(move |_| {
979 let _shutdown = shutdown_tx;
980 for result in done_rx {
981 if let Err(e) = result {
982 *watcher_error.lock().expect("watcher mutex poisoned") = Some(e);
984 return;
985 }
986 }
987 });
988
989 let mut code_updates: Vec<(H256, Code)> = vec![];
991 let mut hashed_address_cache: FxHashMap<Address, H256> = Default::default();
992 let mut has_storage: FxHashSet<H256> = Default::default();
993
994 let mut accumulator: Option<FxHashMap<Address, AccountUpdate>> =
995 collect_witness.then(FxHashMap::default);
996
997 for updates in rx {
998 let current_length = queue_length.fetch_sub(1, Ordering::Acquire);
999 *max_queue_length = current_length.max(*max_queue_length);
1000 if let Some(acc) = &mut accumulator {
1002 for update in updates.clone() {
1003 match acc.entry(update.address) {
1004 Entry::Vacant(e) => {
1005 e.insert(update);
1006 }
1007 Entry::Occupied(mut e) => {
1008 e.get_mut().merge(update);
1009 }
1010 }
1011 }
1012 }
1013
1014 for update in updates {
1015 let hashed_address = *hashed_address_cache
1016 .entry(update.address)
1017 .or_insert_with(|| keccak(update.address));
1018
1019 let (info, code, storage) = if update.removed {
1020 (Some(Default::default()), None, Default::default())
1021 } else {
1022 (update.info, update.code, update.added_storage)
1023 };
1024
1025 if let Some(ref info) = info
1027 && let Some(code) = code
1028 {
1029 code_updates.push((info.code_hash, code));
1030 }
1031
1032 if update.removed || update.removed_storage || !storage.is_empty() {
1033 has_storage.insert(hashed_address);
1034 }
1035
1036 let bucket = hashed_address.as_fixed_bytes()[0] >> 4;
1037 workers_tx[bucket as usize]
1038 .send(WorkerRequest::ProcessAccount {
1039 prefix: hashed_address,
1040 info,
1041 storage,
1042 removed: update.removed,
1043 removed_storage: update.removed_storage,
1044 })
1045 .map_err(|e| StoreError::Custom(format!("send error: {e}")))?;
1046 }
1047 }
1048
1049 for tx in &workers_tx {
1051 tx.send(WorkerRequest::FinishRouting)
1052 .map_err(|e| StoreError::Custom(format!("send error: {e}")))?;
1053 }
1054
1055 let mut early_batches: [Vec<H256>; 16] = Default::default();
1057 for hashed_account in hashed_address_cache.values() {
1058 if !has_storage.contains(hashed_account) {
1059 let bucket = hashed_account.as_fixed_bytes()[0] >> 4;
1060 early_batches[bucket as usize].push(*hashed_account);
1061 }
1062 }
1063 for (i, batch) in early_batches.into_iter().enumerate() {
1064 if !batch.is_empty() {
1065 workers_tx[i]
1066 .send(WorkerRequest::MerklizeAccounts { accounts: batch })
1067 .map_err(|e| StoreError::Custom(format!("send error: {e}")))?;
1068 }
1069 }
1070
1071 let mut storage_updates: Vec<(H256, Vec<TrieNode>)> = Default::default();
1073 let (gatherer_tx, gatherer_rx) = channel();
1074 for tx in &workers_tx {
1075 tx.send(WorkerRequest::CollectState {
1076 tx: gatherer_tx.clone(),
1077 })
1078 .map_err(|e| StoreError::Custom(format!("send error: {e}")))?;
1079 }
1080 drop(gatherer_tx);
1081 drop(workers_tx);
1082
1083 let mut root = BranchNode::default();
1084 let mut state_updates = Vec::new();
1085 for CollectedStateMsg {
1086 index,
1087 subroot,
1088 state_nodes,
1089 storage_nodes,
1090 } in gatherer_rx
1091 {
1092 storage_updates.extend(storage_nodes);
1093 state_updates.extend(state_nodes);
1094 root.choices[index as usize] = subroot.choices[index as usize].clone();
1095 }
1096
1097 let collapsed = self.collapse_root_node(parent_header, None, root)?;
1098 let state_trie_hash = if let Some(root) = collapsed {
1099 let mut root = NodeRef::from(root);
1100 let hash = root.commit(Nibbles::default(), &mut state_updates, &NativeCrypto);
1101 let _ = DROP_SENDER.send(Box::new(root));
1102 hash.finalize(&NativeCrypto)
1103 } else {
1104 state_updates.push((Nibbles::default(), vec![RLP_NULL]));
1105 *EMPTY_TRIE_HASH
1106 };
1107
1108 let accumulated_updates = accumulator.map(|acc| acc.into_values().collect());
1109
1110 Ok((
1111 AccountUpdatesList {
1112 state_trie_hash,
1113 state_updates,
1114 storage_updates,
1115 code_updates,
1116 },
1117 accumulated_updates,
1118 ))
1119 });
1120
1121 if let Some(err) = watcher_error.lock().expect("watcher mutex poisoned").take() {
1123 return Err(err);
1124 }
1125
1126 result
1127 }
1128
1129 #[instrument(
1139 level = "trace",
1140 name = "Trie update (BAL)",
1141 skip_all,
1142 fields(namespace = "block_execution")
1143 )]
1144 fn handle_merkleization_bal_from_updates(
1145 &self,
1146 prepared: FxHashMap<Address, BalSynthesisItem>,
1147 parent_header: &BlockHeader,
1148 ) -> Result<AccountUpdatesList, StoreError> {
1149 const NUM_WORKERS: usize = 16;
1150 const STORAGE_SHARD_THRESHOLD: usize = 2048;
1155 let parent_state_root = parent_header.state_root;
1156
1157 let mut code_updates: Vec<(H256, Code)> = Vec::new();
1161 let mut accounts: Vec<(H256, BalSynthesisItem)> = Vec::with_capacity(prepared.len());
1162 for (addr, item) in prepared {
1163 let hashed = keccak(addr);
1164 if let Some(ch) = item.code_hash
1165 && let Some(ref code) = item.code
1166 {
1167 code_updates.push((ch, code.clone()));
1168 }
1169 accounts.push((hashed, item));
1170 }
1171
1172 let mut normal_indices: Vec<usize> = Vec::new();
1179 let mut hot_indices: Vec<usize> = Vec::new();
1180 for (i, (_, item)) in accounts.iter().enumerate() {
1181 if item.added_storage.len() >= STORAGE_SHARD_THRESHOLD {
1182 hot_indices.push(i);
1183 } else {
1184 normal_indices.push(i);
1185 }
1186 }
1187
1188 let mut work_indices: Vec<(usize, usize)> = normal_indices
1196 .iter()
1197 .map(|&i| {
1198 let item = &accounts[i].1;
1199 let weight = if !item.added_storage.is_empty() {
1200 1.max(item.added_storage.len())
1201 } else {
1202 0
1203 };
1204 (i, weight)
1205 })
1206 .collect();
1207 work_indices.sort_unstable_by(|a, b| b.1.cmp(&a.1));
1208
1209 let mut bins: Vec<Vec<usize>> = (0..NUM_WORKERS).map(|_| Vec::new()).collect();
1211 let mut bin_weights: Vec<usize> = vec![0; NUM_WORKERS];
1212 for (idx, weight) in work_indices {
1213 let min_bin = bin_weights
1214 .iter()
1215 .enumerate()
1216 .min_by_key(|(_, w)| **w)
1217 .expect("bin_weights is non-empty")
1218 .0;
1219 bins[min_bin].push(idx);
1220 bin_weights[min_bin] += weight;
1221 }
1222
1223 let mut storage_roots: Vec<Option<H256>> = vec![None; accounts.len()];
1225 let mut storage_updates: Vec<(H256, Vec<TrieNode>)> = Vec::new();
1226
1227 std::thread::scope(|s| -> Result<(), StoreError> {
1228 let accounts_ref = &accounts;
1229 let handles: Vec<_> = bins
1230 .into_iter()
1231 .enumerate()
1232 .filter_map(|(worker_id, bin)| {
1233 if bin.is_empty() {
1234 return None;
1235 }
1236 Some(
1237 std::thread::Builder::new()
1238 .name(format!("bal_storage_worker_{worker_id}"))
1239 .spawn_scoped(
1240 s,
1241 move || -> Result<Vec<(usize, H256, Vec<TrieNode>)>, StoreError> {
1242 let mut results: Vec<(usize, H256, Vec<TrieNode>)> = Vec::new();
1243 let state_trie =
1245 self.storage.open_state_trie(parent_state_root)?;
1246 for idx in bin {
1247 let (hashed_address, item) = &accounts_ref[idx];
1248 if item.added_storage.is_empty() {
1249 continue;
1250 }
1251
1252 let storage_root = match state_trie
1253 .get(hashed_address.as_bytes())?
1254 {
1255 Some(rlp) => AccountState::decode(&rlp)?.storage_root,
1256 None => *EMPTY_TRIE_HASH,
1257 };
1258 let mut trie = self.storage.open_storage_trie(
1259 *hashed_address,
1260 parent_state_root,
1261 storage_root,
1262 )?;
1263
1264 let mut hashed_storage: Vec<(H256, U256)> = item
1267 .added_storage
1268 .iter()
1269 .map(|(k, v)| (keccak(k), *v))
1270 .collect();
1271 hashed_storage.sort_unstable_by(|a, b| a.0.cmp(&b.0));
1272 for (hashed_key, value) in &hashed_storage {
1273 if value.is_zero() {
1274 trie.remove(hashed_key.as_bytes())?;
1275 } else {
1276 trie.insert(
1277 hashed_key.as_bytes().to_vec(),
1278 value.encode_to_vec(),
1279 )?;
1280 }
1281 }
1282
1283 let (root_hash, nodes) =
1284 trie.collect_changes_since_last_hash(&NativeCrypto);
1285 results.push((idx, root_hash, nodes));
1286 }
1287 Ok(results)
1288 },
1289 )
1290 .map_err(|e| StoreError::Custom(format!("spawn failed: {e}"))),
1291 )
1292 })
1293 .collect::<Result<Vec<_>, _>>()?;
1294
1295 for handle in handles {
1296 let results = handle
1297 .join()
1298 .map_err(|_| StoreError::Custom("storage worker panicked".to_string()))??;
1299 for (idx, root_hash, nodes) in results {
1300 storage_roots[idx] = Some(root_hash);
1301 storage_updates.push((accounts_ref[idx].0, nodes));
1302 }
1303 }
1304 Ok(())
1305 })?;
1306
1307 if !hot_indices.is_empty() {
1313 let state_trie = self.storage.open_state_trie(parent_state_root)?;
1314 for idx in &hot_indices {
1315 let idx = *idx;
1316 let (hashed_address, item) = &accounts[idx];
1317 let storage_root = match state_trie.get(hashed_address.as_bytes())? {
1318 Some(rlp) => AccountState::decode(&rlp)?.storage_root,
1319 None => *EMPTY_TRIE_HASH,
1320 };
1321 let hashed_storage: Vec<(H256, U256)> = item
1324 .added_storage
1325 .iter()
1326 .map(|(k, v)| (keccak(k), *v))
1327 .collect();
1328 let (root_hash, nodes) = compute_sharded_storage_root(
1329 &self.storage,
1330 parent_state_root,
1331 *hashed_address,
1332 storage_root,
1333 &hashed_storage,
1334 )?;
1335 storage_roots[idx] = Some(root_hash);
1336 storage_updates.push((*hashed_address, nodes));
1337 }
1338 }
1339
1340 let mut shards: Vec<Vec<BalStateWorkItem>> = (0..NUM_WORKERS).map(|_| Vec::new()).collect();
1344 for (idx, (hashed_address, item)) in accounts.iter().enumerate() {
1345 let bucket = (hashed_address.as_fixed_bytes()[0] >> 4) as usize;
1346 shards[bucket].push(BalStateWorkItem {
1347 hashed_address: *hashed_address,
1348 nonce: item.nonce,
1349 balance: item.balance,
1350 code_hash: item.code_hash,
1351 storage_root: storage_roots[idx],
1352 });
1353 }
1354
1355 let mut root = BranchNode::default();
1356 let mut state_updates = Vec::new();
1357
1358 std::thread::scope(|s| -> Result<(), StoreError> {
1363 let handles: Vec<_> = shards
1364 .into_iter()
1365 .enumerate()
1366 .map(|(index, shard_items)| {
1367 std::thread::Builder::new()
1368 .name(format!("bal_state_shard_{index}"))
1369 .spawn_scoped(
1370 s,
1371 move || -> Result<(Box<BranchNode>, Vec<TrieNode>), StoreError> {
1372 let mut state_trie =
1373 self.storage.open_state_trie(parent_state_root)?;
1374
1375 for item in &shard_items {
1376 let path = item.hashed_address.as_bytes();
1377
1378 let mut account_state = match state_trie.get(path)? {
1380 Some(rlp) => {
1381 let state = AccountState::decode(&rlp)?;
1382 state_trie.insert(path.to_vec(), rlp)?;
1387 state
1388 }
1389 None => AccountState::default(),
1390 };
1391
1392 if let Some(n) = item.nonce {
1393 account_state.nonce = n;
1394 }
1395 if let Some(b) = item.balance {
1396 account_state.balance = b;
1397 }
1398 if let Some(ch) = item.code_hash {
1399 account_state.code_hash = ch;
1400 }
1401 if let Some(storage_root) = item.storage_root {
1402 account_state.storage_root = storage_root;
1403 }
1404
1405 if account_state != AccountState::default() {
1408 state_trie
1409 .insert(path.to_vec(), account_state.encode_to_vec())?;
1410 } else {
1411 state_trie.remove(path)?;
1412 }
1413 }
1414
1415 collect_trie(index as u8, state_trie)
1416 .map_err(|e| StoreError::Custom(format!("{e}")))
1417 },
1418 )
1419 .map_err(|e| StoreError::Custom(format!("spawn failed: {e}")))
1420 })
1421 .collect::<Result<Vec<_>, _>>()?;
1422
1423 for (i, handle) in handles.into_iter().enumerate() {
1424 let (subroot, state_nodes) = handle
1425 .join()
1426 .map_err(|_| StoreError::Custom("state shard worker panicked".to_string()))??;
1427 state_updates.extend(state_nodes);
1428 root.choices[i] = subroot.choices[i].clone();
1429 }
1430 Ok(())
1431 })?;
1432
1433 let state_trie_hash =
1435 if let Some(root) = self.collapse_root_node(parent_header, None, root)? {
1436 let mut root = NodeRef::from(root);
1437 let hash = root.commit(Nibbles::default(), &mut state_updates, &NativeCrypto);
1438 let _ = DROP_SENDER.send(Box::new(root));
1439 hash.finalize(&NativeCrypto)
1440 } else {
1441 state_updates.push((Nibbles::default(), vec![RLP_NULL]));
1442 *EMPTY_TRIE_HASH
1443 };
1444
1445 Ok(AccountUpdatesList {
1446 state_trie_hash,
1447 state_updates,
1448 storage_updates,
1449 code_updates,
1450 })
1451 }
1452
1453 fn collapse_root_node(
1454 &self,
1455 parent_header: &BlockHeader,
1456 prefix: Option<H256>,
1457 root: BranchNode,
1458 ) -> Result<Option<Node>, StoreError> {
1459 collapse_root_node(&self.storage, parent_header.state_root, prefix, root)
1460 }
1461
1462 fn execute_block_from_state(
1464 &self,
1465 parent_header: &BlockHeader,
1466 block: &Block,
1467 chain_config: &ChainConfig,
1468 vm: &mut Evm,
1469 ) -> Result<BlockExecutionResult, ChainError> {
1470 validate_block_pre_execution(block, parent_header, chain_config, ELASTICITY_MULTIPLIER)?;
1472 self.validate_l1_transaction_types(block)?;
1473 let (execution_result, bal) = vm.execute_block(block)?;
1474 if let Err(e) = validate_gas_used(execution_result.block_gas_used, &block.header) {
1476 ethrex_vm::log_gas_used_mismatch(
1477 &execution_result.tx_gas_breakdowns,
1478 block.header.number,
1479 execution_result.block_gas_used,
1480 block.header.gas_used,
1481 );
1482 return Err(e.into());
1483 }
1484 validate_receipts_root_and_logs_bloom(
1485 &block.header,
1486 &execution_result.receipts,
1487 &NativeCrypto,
1488 )?;
1489 validate_requests_hash(&block.header, chain_config, &execution_result.requests)?;
1490 if let Some(bal) = &bal {
1491 validate_block_access_list_hash(
1492 &block.header,
1493 chain_config,
1494 bal,
1495 block.body.transactions.len(),
1496 &NativeCrypto,
1497 )?;
1498 }
1499
1500 Ok(execution_result)
1501 }
1502
1503 pub async fn generate_witness_for_blocks(
1504 &self,
1505 blocks: &[Block],
1506 ) -> Result<ExecutionWitness, ChainError> {
1507 self.generate_witness_for_blocks_with_fee_configs(blocks, None)
1508 .await
1509 }
1510
1511 pub async fn generate_witness_for_blocks_with_fee_configs(
1512 &self,
1513 blocks: &[Block],
1514 fee_configs: Option<&[FeeConfig]>,
1515 ) -> Result<ExecutionWitness, ChainError> {
1516 let first_block_header = &blocks
1517 .first()
1518 .ok_or(ChainError::WitnessGeneration(
1519 "Empty block batch".to_string(),
1520 ))?
1521 .header;
1522
1523 let trie = self
1525 .storage
1526 .state_trie(first_block_header.parent_hash)
1527 .map_err(|_| ChainError::ParentStateNotFound)?
1528 .ok_or(ChainError::ParentStateNotFound)?;
1529 let initial_state_root = trie.hash_no_commit(&NativeCrypto);
1530
1531 let (mut current_trie_witness, mut trie) = TrieLogger::open_trie(trie);
1532
1533 let mut accumulated_state_trie_witness = current_trie_witness
1537 .lock()
1538 .map_err(|_| {
1539 ChainError::WitnessGeneration("Failed to lock state trie witness".to_string())
1540 })?
1541 .clone();
1542
1543 let mut touched_account_storage_slots = BTreeMap::new();
1544 let mut used_trie_nodes = Vec::new();
1546
1547 let root_node = trie.root_node().map_err(|_| {
1549 ChainError::WitnessGeneration("Failed to get root state node".to_string())
1550 })?;
1551
1552 let mut blockhash_opcode_references = HashMap::new();
1553 let mut codes = Vec::new();
1554
1555 for (i, block) in blocks.iter().enumerate() {
1556 let parent_hash = block.header.parent_hash;
1557 let parent_header = self
1558 .storage
1559 .get_block_header_by_hash(parent_hash)
1560 .map_err(ChainError::StoreError)?
1561 .ok_or(ChainError::ParentNotFound)?;
1562
1563 let vm_db: DynVmDatabase =
1569 Box::new(StoreVmDatabase::new(self.storage.clone(), parent_header)?);
1570
1571 let logger = Arc::new(DatabaseLogger::new(Arc::new(vm_db)));
1572
1573 let mut vm = match self.options.r#type {
1574 BlockchainType::L1 => {
1575 Evm::new_from_db_for_l1(logger.clone(), Arc::new(NativeCrypto))
1576 }
1577 BlockchainType::L2(_) => {
1578 let l2_config = match fee_configs {
1579 Some(fee_configs) => {
1580 fee_configs.get(i).ok_or(ChainError::WitnessGeneration(
1581 "FeeConfig not found for witness generation".to_string(),
1582 ))?
1583 }
1584 None => Err(ChainError::WitnessGeneration(
1585 "L2Config not found for witness generation".to_string(),
1586 ))?,
1587 };
1588 Evm::new_from_db_for_l2(logger.clone(), *l2_config, Arc::new(NativeCrypto))
1589 }
1590 };
1591
1592 let (execution_result, _bal) = vm.execute_block(block)?;
1594
1595 let account_updates = vm.get_state_transitions()?;
1597
1598 let mut state_accessed = logger
1599 .state_accessed
1600 .lock()
1601 .map_err(|_e| {
1602 ChainError::WitnessGeneration("Failed to execute with witness".to_string())
1603 })?
1604 .clone();
1605
1606 for keys in state_accessed.values_mut() {
1608 let mut seen = HashSet::new();
1609 keys.retain(|k| seen.insert(*k));
1610 }
1611
1612 for (account, acc_keys) in state_accessed.iter() {
1613 let slots: &mut Vec<H256> =
1614 touched_account_storage_slots.entry(*account).or_default();
1615 slots.extend(acc_keys.iter().copied());
1616 }
1617
1618 let logger_block_hashes = logger
1620 .block_hashes_accessed
1621 .lock()
1622 .map_err(|_e| {
1623 ChainError::WitnessGeneration("Failed to get block hashes".to_string())
1624 })?
1625 .clone();
1626
1627 blockhash_opcode_references.extend(logger_block_hashes);
1628
1629 if let Some(withdrawals) = block.body.withdrawals.as_ref() {
1631 for withdrawal in withdrawals {
1632 trie.get(&hash_address(&withdrawal.address)).map_err(|_e| {
1633 ChainError::Custom("Failed to access account from trie".to_string())
1634 })?;
1635 }
1636 }
1637
1638 let mut used_storage_tries = HashMap::new();
1639
1640 for (account, acc_keys) in state_accessed.iter() {
1643 trie.get(&hash_address(account)).map_err(|_e| {
1645 ChainError::WitnessGeneration("Failed to access account from trie".to_string())
1646 })?;
1647 if !acc_keys.is_empty()
1649 && let Ok(Some(storage_trie)) = self.storage.storage_trie(parent_hash, *account)
1650 {
1651 let (storage_trie_witness, storage_trie) = TrieLogger::open_trie(storage_trie);
1652 for storage_key in acc_keys {
1654 let hashed_key = hash_key(storage_key);
1655 storage_trie.get(&hashed_key).map_err(|_e| {
1656 ChainError::WitnessGeneration(
1657 "Failed to access storage key".to_string(),
1658 )
1659 })?;
1660 }
1661 used_storage_tries.insert(*account, (storage_trie_witness, storage_trie));
1663 }
1664 }
1665
1666 for code_hash in logger
1668 .code_accessed
1669 .lock()
1670 .map_err(|_e| {
1671 ChainError::WitnessGeneration("Failed to gather used bytecodes".to_string())
1672 })?
1673 .iter()
1674 {
1675 let code = self
1676 .storage
1677 .get_account_code(*code_hash)
1678 .map_err(|_e| {
1679 ChainError::WitnessGeneration("Failed to get account code".to_string())
1680 })?
1681 .ok_or(ChainError::WitnessGeneration(
1682 "Failed to get account code".to_string(),
1683 ))?;
1684 codes.push(code.code().to_vec());
1685 }
1686
1687 let (storage_tries_after_update, account_updates_list) =
1689 self.storage.apply_account_updates_from_trie_with_witness(
1690 trie,
1691 &account_updates,
1692 used_storage_tries,
1693 )?;
1694
1695 self.store_block(block.clone(), account_updates_list, execution_result)?;
1699
1700 for (address, (witness, _storage_trie)) in storage_tries_after_update {
1701 let mut witness = witness.lock().map_err(|_| {
1702 ChainError::WitnessGeneration("Failed to lock storage trie witness".to_string())
1703 })?;
1704 let witness = std::mem::take(&mut *witness);
1705 let witness = witness.into_values().collect::<Vec<_>>();
1706 used_trie_nodes.extend_from_slice(&witness);
1707 touched_account_storage_slots.entry(address).or_default();
1708 }
1709
1710 let (new_state_trie_witness, updated_trie) = TrieLogger::open_trie(
1711 self.storage
1712 .state_trie(block.header.hash())
1713 .map_err(|_| ChainError::ParentStateNotFound)?
1714 .ok_or(ChainError::ParentStateNotFound)?,
1715 );
1716
1717 trie = updated_trie;
1719
1720 for state_trie_witness in current_trie_witness
1721 .lock()
1722 .map_err(|_| {
1723 ChainError::WitnessGeneration("Failed to lock state trie witness".to_string())
1724 })?
1725 .iter()
1726 {
1727 accumulated_state_trie_witness
1728 .insert(*state_trie_witness.0, state_trie_witness.1.clone());
1729 }
1730
1731 current_trie_witness = new_state_trie_witness;
1732 }
1733
1734 used_trie_nodes.extend_from_slice(&Vec::from_iter(
1735 accumulated_state_trie_witness.into_values(),
1736 ));
1737
1738 if used_trie_nodes.is_empty()
1740 && let Some(root) = root_node
1741 {
1742 used_trie_nodes.push((*root).clone());
1743 }
1744
1745 let mut block_headers_bytes = Vec::new();
1747
1748 let first_blockhash_opcode_number = blockhash_opcode_references.keys().min();
1749 let first_needed_block_hash = first_blockhash_opcode_number
1750 .and_then(|n| {
1751 (*n < first_block_header.number.saturating_sub(1))
1752 .then(|| blockhash_opcode_references.get(n))?
1753 .copied()
1754 })
1755 .unwrap_or(first_block_header.parent_hash);
1756
1757 let mut current_header = blocks
1759 .last()
1760 .ok_or_else(|| ChainError::WitnessGeneration("Empty batch".to_string()))?
1761 .header
1762 .clone();
1763
1764 while current_header.hash() != first_needed_block_hash {
1767 let parent_hash = current_header.parent_hash;
1768 let current_number = current_header.number - 1;
1769
1770 current_header = self
1771 .storage
1772 .get_block_header_by_hash(parent_hash)?
1773 .ok_or_else(|| {
1774 ChainError::WitnessGeneration(format!(
1775 "Failed to get block {current_number} header"
1776 ))
1777 })?;
1778
1779 block_headers_bytes.push(current_header.encode_to_vec());
1780 }
1781
1782 let nodes: BTreeMap<H256, Node> = used_trie_nodes
1784 .into_iter()
1785 .map(|node| {
1786 (
1787 node.compute_hash(&NativeCrypto).finalize(&NativeCrypto),
1788 node,
1789 )
1790 })
1791 .collect();
1792 let state_trie_root = if let NodeRef::Node(state_trie_root, _) =
1793 Trie::get_embedded_root(&nodes, initial_state_root)?
1794 {
1795 Some((*state_trie_root).clone())
1796 } else {
1797 None
1798 };
1799
1800 let state_trie = if let Some(state_trie_root) = &state_trie_root {
1802 Trie::new_temp_with_root(state_trie_root.clone().into())
1803 } else {
1804 Trie::new_temp()
1805 };
1806 let mut storage_trie_roots = BTreeMap::new();
1807 for address in touched_account_storage_slots.keys() {
1808 let hashed_address = hash_address(address);
1809 let hashed_address_h256 = H256::from_slice(&hashed_address);
1810 let Some(encoded_account) = state_trie.get(&hashed_address)? else {
1811 continue; };
1813 let storage_root_hash = AccountState::decode(&encoded_account)?.storage_root;
1814 if storage_root_hash == *EMPTY_TRIE_HASH {
1815 continue; }
1817 if !nodes.contains_key(&storage_root_hash) {
1818 continue; }
1820 let node = Trie::get_embedded_root(&nodes, storage_root_hash)?;
1821 let NodeRef::Node(node, _) = node else {
1822 return Err(ChainError::Custom(
1823 "execution witness does not contain non-empty storage trie".to_string(),
1824 ));
1825 };
1826 storage_trie_roots.insert(hashed_address_h256, (*node).clone());
1827 }
1828
1829 Ok(ExecutionWitness {
1830 codes,
1831 block_headers_bytes,
1832 first_block_number: first_block_header.number,
1833 chain_config: self.storage.get_chain_config(),
1834 state_trie_root,
1835 storage_trie_roots,
1836 })
1837 }
1838
1839 pub fn generate_witness_from_account_updates(
1840 &self,
1841 account_updates: Vec<AccountUpdate>,
1842 block: &Block,
1843 parent_header: BlockHeader,
1844 logger: &DatabaseLogger,
1845 ) -> Result<ExecutionWitness, ChainError> {
1846 let trie = self
1848 .storage
1849 .state_trie(parent_header.hash())
1850 .map_err(|_| ChainError::ParentStateNotFound)?
1851 .ok_or(ChainError::ParentStateNotFound)?;
1852 let initial_state_root = trie.hash_no_commit(&NativeCrypto);
1853
1854 let (trie_witness, trie) = TrieLogger::open_trie(trie);
1855
1856 let mut touched_account_storage_slots = BTreeMap::new();
1857 let mut used_trie_nodes = Vec::new();
1859
1860 let root_node = trie.root_node().map_err(|_| {
1862 ChainError::WitnessGeneration("Failed to get root state node".to_string())
1863 })?;
1864
1865 let mut codes = Vec::new();
1866
1867 for account_update in &account_updates {
1868 touched_account_storage_slots.insert(
1869 account_update.address,
1870 account_update
1871 .added_storage
1872 .keys()
1873 .cloned()
1874 .collect::<Vec<H256>>(),
1875 );
1876 }
1877
1878 let blockhash_opcode_references = logger
1880 .block_hashes_accessed
1881 .lock()
1882 .map_err(|_e| ChainError::WitnessGeneration("Failed to get block hashes".to_string()))?
1883 .clone();
1884
1885 if let Some(withdrawals) = block.body.withdrawals.as_ref() {
1887 for withdrawal in withdrawals {
1888 trie.get(&hash_address(&withdrawal.address)).map_err(|_e| {
1889 ChainError::Custom("Failed to access account from trie".to_string())
1890 })?;
1891 }
1892 }
1893
1894 let mut used_storage_tries = HashMap::new();
1895
1896 for (account, acc_keys) in logger
1899 .state_accessed
1900 .lock()
1901 .map_err(|_e| {
1902 ChainError::WitnessGeneration("Failed to execute with witness".to_string())
1903 })?
1904 .iter()
1905 {
1906 trie.get(&hash_address(account)).map_err(|_e| {
1908 ChainError::WitnessGeneration("Failed to access account from trie".to_string())
1909 })?;
1910 if !acc_keys.is_empty()
1912 && let Ok(Some(storage_trie)) =
1913 self.storage.storage_trie(parent_header.hash(), *account)
1914 {
1915 let (storage_trie_witness, storage_trie) = TrieLogger::open_trie(storage_trie);
1916 for storage_key in acc_keys {
1918 let hashed_key = hash_key(storage_key);
1919 storage_trie.get(&hashed_key).map_err(|_e| {
1920 ChainError::WitnessGeneration("Failed to access storage key".to_string())
1921 })?;
1922 }
1923 used_storage_tries.insert(*account, (storage_trie_witness, storage_trie));
1925 }
1926 }
1927
1928 for code_hash in logger
1930 .code_accessed
1931 .lock()
1932 .map_err(|_e| {
1933 ChainError::WitnessGeneration("Failed to gather used bytecodes".to_string())
1934 })?
1935 .iter()
1936 {
1937 let code = self
1938 .storage
1939 .get_account_code(*code_hash)
1940 .map_err(|_e| {
1941 ChainError::WitnessGeneration("Failed to get account code".to_string())
1942 })?
1943 .ok_or(ChainError::WitnessGeneration(
1944 "Failed to get account code".to_string(),
1945 ))?;
1946 codes.push(code.code().to_vec());
1947 }
1948
1949 let (storage_tries_after_update, _account_updates_list) =
1951 self.storage.apply_account_updates_from_trie_with_witness(
1952 trie,
1953 &account_updates,
1954 used_storage_tries,
1955 )?;
1956
1957 for (address, (witness, _storage_trie)) in storage_tries_after_update {
1958 let mut witness = witness.lock().map_err(|_| {
1959 ChainError::WitnessGeneration("Failed to lock storage trie witness".to_string())
1960 })?;
1961 let witness = std::mem::take(&mut *witness);
1962 let witness = witness.into_values().collect::<Vec<_>>();
1963 used_trie_nodes.extend_from_slice(&witness);
1964 touched_account_storage_slots.entry(address).or_default();
1965 }
1966
1967 used_trie_nodes.extend_from_slice(&Vec::from_iter(
1968 trie_witness
1969 .lock()
1970 .map_err(|_| {
1971 ChainError::WitnessGeneration("Failed to lock state trie witness".to_string())
1972 })?
1973 .clone()
1974 .into_values(),
1975 ));
1976
1977 if used_trie_nodes.is_empty()
1979 && let Some(root) = root_node
1980 {
1981 used_trie_nodes.push((*root).clone());
1982 }
1983
1984 let mut block_headers_bytes = Vec::new();
1986
1987 let first_blockhash_opcode_number = blockhash_opcode_references.keys().min();
1988 let first_needed_block_hash = first_blockhash_opcode_number
1989 .and_then(|n| {
1990 (*n < block.header.number.saturating_sub(1))
1991 .then(|| blockhash_opcode_references.get(n))?
1992 .copied()
1993 })
1994 .unwrap_or(block.header.parent_hash);
1995
1996 let mut current_header = block.header.clone();
1997
1998 while current_header.hash() != first_needed_block_hash {
2001 let parent_hash = current_header.parent_hash;
2002 let current_number = current_header.number - 1;
2003
2004 current_header = self
2005 .storage
2006 .get_block_header_by_hash(parent_hash)?
2007 .ok_or_else(|| {
2008 ChainError::WitnessGeneration(format!(
2009 "Failed to get block {current_number} header"
2010 ))
2011 })?;
2012
2013 block_headers_bytes.push(current_header.encode_to_vec());
2014 }
2015
2016 let nodes: BTreeMap<H256, Node> = used_trie_nodes
2018 .into_iter()
2019 .map(|node| {
2020 (
2021 node.compute_hash(&NativeCrypto).finalize(&NativeCrypto),
2022 node,
2023 )
2024 })
2025 .collect();
2026 let state_trie_root = if let NodeRef::Node(state_trie_root, _) =
2027 Trie::get_embedded_root(&nodes, initial_state_root)?
2028 {
2029 Some((*state_trie_root).clone())
2030 } else {
2031 None
2032 };
2033
2034 let state_trie = if let Some(state_trie_root) = &state_trie_root {
2036 Trie::new_temp_with_root(state_trie_root.clone().into())
2037 } else {
2038 Trie::new_temp()
2039 };
2040 let mut storage_trie_roots = BTreeMap::new();
2041 for address in touched_account_storage_slots.keys() {
2042 let hashed_address = hash_address(address);
2043 let hashed_address_h256 = H256::from_slice(&hashed_address);
2044 let Some(encoded_account) = state_trie.get(&hashed_address)? else {
2045 continue; };
2047 let storage_root_hash = AccountState::decode(&encoded_account)?.storage_root;
2048 if storage_root_hash == *EMPTY_TRIE_HASH {
2049 continue; }
2051 if !nodes.contains_key(&storage_root_hash) {
2052 continue; }
2054 let node = Trie::get_embedded_root(&nodes, storage_root_hash)?;
2055 let NodeRef::Node(node, _) = node else {
2056 return Err(ChainError::Custom(
2057 "execution witness does not contain non-empty storage trie".to_string(),
2058 ));
2059 };
2060 storage_trie_roots.insert(hashed_address_h256, (*node).clone());
2061 }
2062
2063 Ok(ExecutionWitness {
2064 codes,
2065 block_headers_bytes,
2066 first_block_number: parent_header.number,
2067 chain_config: self.storage.get_chain_config(),
2068 state_trie_root,
2069 storage_trie_roots,
2070 })
2071 }
2072
2073 #[instrument(
2074 level = "trace",
2075 name = "Block DB update",
2076 skip_all,
2077 fields(namespace = "block_execution")
2078 )]
2079 pub fn store_block(
2080 &self,
2081 block: Block,
2082 account_updates_list: AccountUpdatesList,
2083 execution_result: BlockExecutionResult,
2084 ) -> Result<(), ChainError> {
2085 validate_state_root(&block.header, account_updates_list.state_trie_hash)?;
2087
2088 let update_batch = UpdateBatch {
2089 account_updates: account_updates_list.state_updates,
2090 storage_updates: account_updates_list.storage_updates,
2091 receipts: vec![(block.hash(), execution_result.receipts)],
2092 blocks: vec![block],
2093 code_updates: account_updates_list.code_updates,
2094 batch_mode: false,
2095 };
2096
2097 self.storage
2098 .store_block_updates(update_batch)
2099 .map_err(|e| e.into())
2100 }
2101
2102 pub fn add_block(&self, block: Block) -> Result<(), ChainError> {
2103 let since = Instant::now();
2104 let (res, updates) = self.execute_block(&block)?;
2105 let executed = Instant::now();
2106
2107 let account_updates_list = self
2109 .storage
2110 .apply_account_updates_batch(block.header.parent_hash, &updates)?
2111 .ok_or(ChainError::ParentStateNotFound)?;
2112
2113 let (gas_used, gas_limit, block_number, transactions_count) = (
2114 block.header.gas_used,
2115 block.header.gas_limit,
2116 block.header.number,
2117 block.body.transactions.len(),
2118 );
2119
2120 let merkleized = Instant::now();
2121 let result = self.store_block(block, account_updates_list, res);
2122 let stored = Instant::now();
2123
2124 if self.options.perf_logs_enabled {
2125 Self::print_add_block_logs(
2126 gas_used,
2127 gas_limit,
2128 block_number,
2129 transactions_count,
2130 since,
2131 executed,
2132 merkleized,
2133 stored,
2134 );
2135 }
2136 result
2137 }
2138
2139 pub fn add_block_pipeline(
2140 &self,
2141 block: Block,
2142 bal: Option<Arc<BlockAccessList>>,
2143 ) -> Result<(), ChainError> {
2144 let (_, _, result) = self.add_block_pipeline_inner(block, bal, false)?;
2145 result
2146 }
2147
2148 pub fn add_block_pipeline_bal(
2155 &self,
2156 block: Block,
2157 bal: Option<Arc<BlockAccessList>>,
2158 ) -> Result<Option<BlockAccessList>, ChainError> {
2159 let (produced_bal, _, result) = self.add_block_pipeline_inner(block, bal, false)?;
2160 result?;
2161 Ok(produced_bal)
2162 }
2163
2164 pub fn add_block_pipeline_with_witness(
2167 &self,
2168 block: Block,
2169 bal: Option<Arc<BlockAccessList>>,
2170 ) -> Result<ExecutionWitness, ChainError> {
2171 let (_, witness, result) = self.add_block_pipeline_inner(block, bal, true)?;
2172 result?;
2173 witness.ok_or_else(|| {
2174 ChainError::WitnessGeneration(
2175 "forced witness collection completed without producing a witness".to_string(),
2176 )
2177 })
2178 }
2179
2180 fn add_block_pipeline_inner(
2189 &self,
2190 block: Block,
2191 bal: Option<Arc<BlockAccessList>>,
2192 force_witness: bool,
2193 ) -> Result<AddBlockPipelineInnerResult, ChainError> {
2194 let Ok(parent_header) = find_parent_header(&block.header, &self.storage) else {
2196 self.storage.add_pending_block(block)?;
2198 return Err(ChainError::ParentNotFound);
2199 };
2200
2201 let should_store_witness = self.options.precompute_witnesses && self.is_synced();
2202 let collect_witness = should_store_witness || force_witness;
2203
2204 let (mut vm, logger) = if collect_witness {
2205 let vm_db: DynVmDatabase = Box::new(StoreVmDatabase::new(
2209 self.storage.clone(),
2210 parent_header.clone(),
2211 )?);
2212
2213 let logger = Arc::new(DatabaseLogger::new(Arc::new(vm_db)));
2214
2215 let vm = match self.options.r#type.clone() {
2216 BlockchainType::L1 => {
2217 Evm::new_from_db_for_l1(logger.clone(), Arc::new(NativeCrypto))
2218 }
2219 BlockchainType::L2(l2_config) => Evm::new_from_db_for_l2(
2220 logger.clone(),
2221 *l2_config.fee_config.read().map_err(|_| {
2222 EvmError::Custom("Fee config lock was poisoned".to_string())
2223 })?,
2224 Arc::new(NativeCrypto),
2225 ),
2226 };
2227 (vm, Some(logger))
2228 } else {
2229 let vm_db = StoreVmDatabase::new(self.storage.clone(), parent_header.clone())?;
2230 let vm = self.new_evm(vm_db)?;
2231 (vm, None)
2232 };
2233
2234 let input_bal = bal.clone();
2237 let (
2238 res,
2239 account_updates_list,
2240 accumulated_updates,
2241 produced_bal,
2242 merkle_queue_length,
2243 instants,
2244 warmer_duration,
2245 ) = { self.execute_block_pipeline(&block, &parent_header, &mut vm, bal, collect_witness)? };
2246
2247 let (gas_used, gas_limit, block_number, transactions_count) = (
2248 block.header.gas_used,
2249 block.header.gas_limit,
2250 block.header.number,
2251 block.body.transactions.len(),
2252 );
2253 let block_hash = block.hash();
2254
2255 let mut witness = None;
2256 if let Some(logger) = logger
2257 && let Some(account_updates) = accumulated_updates
2258 {
2259 let block_hash = block.hash();
2260 let generated_witness = self.generate_witness_from_account_updates(
2261 account_updates,
2262 &block,
2263 parent_header,
2264 &logger,
2265 )?;
2266 match (should_store_witness, force_witness) {
2267 (true, true) => {
2268 witness = Some(generated_witness.clone());
2269 self.storage
2270 .store_witness(block_hash, block_number, generated_witness)?;
2271 }
2272 (true, false) => {
2273 self.storage
2274 .store_witness(block_hash, block_number, generated_witness)?;
2275 }
2276 (false, true) => {
2277 witness = Some(generated_witness);
2278 }
2279 (false, false) => {}
2280 }
2281 };
2282
2283 if let Some(bal) = produced_bal.as_ref().or(input_bal.as_deref())
2288 && let Err(err) = self.storage.store_block_access_list(block_hash, bal)
2289 {
2290 warn!("Failed to store block access list for block {block_hash}: {err}");
2291 }
2292
2293 let result = self.store_block(block, account_updates_list, res);
2294
2295 let stored = Instant::now();
2296
2297 let instants = std::array::from_fn(move |i| {
2298 if i < instants.len() {
2299 instants[i]
2300 } else {
2301 stored
2302 }
2303 });
2304
2305 if self.options.perf_logs_enabled {
2306 Self::print_add_block_pipeline_logs(
2307 gas_used,
2308 gas_limit,
2309 block_number,
2310 block_hash,
2311 transactions_count,
2312 merkle_queue_length,
2313 warmer_duration,
2314 instants,
2315 );
2316 }
2317
2318 metrics!(
2319 if let Some(bal_ref) = produced_bal.as_ref().or(input_bal.as_deref()) {
2320 let account_count = bal_ref.accounts().len() as u64;
2321 let slot_count = bal_ref.item_count().saturating_sub(account_count);
2322 let size_bytes = bal_ref.length() as f64;
2323 METRICS_BAL.blocks_total.inc();
2324 METRICS_BAL.size_bytes.set(size_bytes);
2325 METRICS_BAL.size_bytes_histogram.observe(size_bytes);
2326 METRICS_BAL.account_count.set(account_count as i64);
2327 METRICS_BAL.slot_count.set(slot_count as i64);
2328 }
2329 );
2330
2331 Ok((produced_bal, witness, result))
2332 }
2333
2334 #[allow(clippy::too_many_arguments)]
2335 fn print_add_block_logs(
2336 gas_used: u64,
2337 gas_limit: u64,
2338 block_number: u64,
2339 transactions_count: usize,
2340 since: Instant,
2341 executed: Instant,
2342 merkleized: Instant,
2343 stored: Instant,
2344 ) {
2345 let interval = stored.duration_since(since).as_millis() as f64;
2346 if interval != 0f64 {
2347 let as_gigas = gas_used as f64 / 10_f64.powf(9_f64);
2348 let throughput = as_gigas / interval * 1000_f64;
2349
2350 metrics!(
2351 METRICS_BLOCKS.set_block_number(block_number);
2352 METRICS_BLOCKS.set_latest_gas_used(gas_used as f64);
2353 METRICS_BLOCKS.set_latest_block_gas_limit(gas_limit as f64);
2354 METRICS_BLOCKS.set_latest_gigagas(throughput);
2355 METRICS_BLOCKS.set_execution_ms(executed.duration_since(since).as_secs_f64() * 1000.0);
2356 METRICS_BLOCKS.set_merkle_ms(merkleized.duration_since(executed).as_secs_f64() * 1000.0);
2357 METRICS_BLOCKS.set_store_ms(stored.duration_since(merkleized).as_secs_f64() * 1000.0);
2358 METRICS_BLOCKS.set_transaction_count(transactions_count as i64);
2359 );
2360
2361 let base_log = format!(
2362 "[METRIC] BLOCK EXECUTION THROUGHPUT ({}): {:.3} Ggas/s TIME SPENT: {:.0} ms. Gas Used: {:.3} ({:.0}%), #Txs: {}.",
2363 block_number,
2364 throughput,
2365 interval,
2366 as_gigas,
2367 (gas_used as f64 / gas_limit as f64) * 100.0,
2368 transactions_count
2369 );
2370
2371 fn percentage(init: Instant, end: Instant, total: f64) -> f64 {
2372 (end.duration_since(init).as_millis() as f64 / total * 100.0).round()
2373 }
2374 let extra_log = if as_gigas > 0.0 {
2375 format!(
2376 " exec: {}% merkle: {}% store: {}%",
2377 percentage(since, executed, interval),
2378 percentage(executed, merkleized, interval),
2379 percentage(merkleized, stored, interval)
2380 )
2381 } else {
2382 "".to_string()
2383 };
2384 info!("{}{}", base_log, extra_log);
2385 }
2386 }
2387
2388 #[allow(clippy::too_many_arguments)]
2389 fn print_add_block_pipeline_logs(
2390 gas_used: u64,
2391 gas_limit: u64,
2392 block_number: u64,
2393 block_hash: H256,
2394 transactions_count: usize,
2395 merkle_queue_length: usize,
2396 warmer_duration: Duration,
2397 [
2398 start_instant,
2399 block_validated_instant,
2400 exec_merkle_start,
2401 merkle_start_instant,
2402 exec_end_instant,
2403 merkle_end_instant,
2404 exec_merkle_end_instant,
2405 stored_instant,
2406 ]: [Instant; 8],
2407 ) {
2408 let total_ms = stored_instant.duration_since(start_instant).as_secs_f64() * 1000.0;
2409 if total_ms == 0.0 {
2410 return;
2411 }
2412
2413 let as_mgas = gas_used as f64 / 1e6;
2414 let throughput = (gas_used as f64 / 1e9) / (total_ms / 1000.0);
2415
2416 let validate_ms = block_validated_instant
2418 .duration_since(start_instant)
2419 .as_secs_f64()
2420 * 1000.0;
2421 let exec_ms = exec_end_instant
2422 .duration_since(exec_merkle_start)
2423 .as_secs_f64()
2424 * 1000.0;
2425 let store_ms = stored_instant
2426 .duration_since(exec_merkle_end_instant)
2427 .as_secs_f64()
2428 * 1000.0;
2429 let warmer_ms = warmer_duration.as_secs_f64() * 1000.0;
2430
2431 let _merkle_total_ms = exec_merkle_end_instant
2435 .duration_since(exec_merkle_start)
2436 .as_secs_f64()
2437 * 1000.0;
2438
2439 let merkle_concurrent_ms = (merkle_end_instant
2441 .duration_since(exec_merkle_start)
2442 .as_secs_f64()
2443 * 1000.0)
2444 .min(exec_ms);
2445
2446 let merkle_drain_ms = exec_merkle_end_instant
2448 .saturating_duration_since(exec_end_instant)
2449 .as_secs_f64()
2450 * 1000.0;
2451
2452 let actual_merkle_ms = merkle_concurrent_ms + merkle_drain_ms;
2454 let overlap_pct = if actual_merkle_ms > 0.0 {
2455 (merkle_concurrent_ms / actual_merkle_ms) * 100.0
2456 } else {
2457 0.0
2458 };
2459
2460 let warmer_early_ms = exec_ms - warmer_ms;
2462
2463 let phases = [
2466 ("validate", validate_ms),
2467 ("exec", exec_ms),
2468 ("merkle", merkle_drain_ms),
2469 ("store", store_ms),
2470 ];
2471 let bottleneck = phases
2472 .iter()
2473 .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
2474 .map(|(name, _)| *name)
2475 .unwrap_or("exec");
2476
2477 let pct = |ms: f64| (ms / total_ms * 100.0).round() as u64;
2479
2480 let header = format!(
2482 "[METRIC] BLOCK {} {:#x} | {:.3} Ggas/s | {:.2} ms | {} txs | {:.0} Mgas ({}%)",
2483 block_number,
2484 block_hash,
2485 throughput,
2486 total_ms,
2487 transactions_count,
2488 as_mgas,
2489 (gas_used as f64 / gas_limit as f64 * 100.0).round() as u64
2490 );
2491
2492 let bottleneck_marker = |name: &str| {
2493 if name == bottleneck {
2494 " << BOTTLENECK"
2495 } else {
2496 ""
2497 }
2498 };
2499
2500 let warmer_relation = if warmer_early_ms >= 0.0 {
2501 "before exec"
2502 } else {
2503 "after exec"
2504 };
2505
2506 let merkle_start_delay_ms = merkle_start_instant
2507 .duration_since(exec_merkle_start)
2508 .as_secs_f64()
2509 * 1000.0;
2510
2511 info!("{}", header);
2512 info!(
2513 " |- validate: {:>7.2} ms ({:>2}%){}",
2514 validate_ms,
2515 pct(validate_ms),
2516 bottleneck_marker("validate")
2517 );
2518 info!(
2519 " |- exec: {:>7.2} ms ({:>2}%){}",
2520 exec_ms,
2521 pct(exec_ms),
2522 bottleneck_marker("exec")
2523 );
2524 info!(
2525 " |- merkle: {:>7.2} ms ({:>2}%){} [concurrent: {:.2} ms, drain: {:.2} ms, overlap: {:.0}%, queue: {}, start_delay: {:.2} ms]",
2526 merkle_drain_ms,
2527 pct(merkle_drain_ms),
2528 bottleneck_marker("merkle"),
2529 merkle_concurrent_ms,
2530 merkle_drain_ms,
2531 overlap_pct,
2532 merkle_queue_length,
2533 merkle_start_delay_ms,
2534 );
2535 info!(
2536 " |- store: {:>7.2} ms ({:>2}%){}",
2537 store_ms,
2538 pct(store_ms),
2539 bottleneck_marker("store")
2540 );
2541 info!(
2542 " `- warmer: {:>7.2} ms [finished: {:.2} ms {}]",
2543 warmer_ms,
2544 warmer_early_ms.abs(),
2545 warmer_relation,
2546 );
2547
2548 metrics!(
2550 METRICS_BLOCKS.set_block_number(block_number);
2551 METRICS_BLOCKS.set_latest_gas_used(gas_used as f64);
2552 METRICS_BLOCKS.set_latest_block_gas_limit(gas_limit as f64);
2553 METRICS_BLOCKS.set_latest_gigagas(throughput);
2554 METRICS_BLOCKS.set_transaction_count(transactions_count as i64);
2555 METRICS_BLOCKS.set_validate_ms(validate_ms);
2556 METRICS_BLOCKS.set_execution_ms(exec_ms);
2557 METRICS_BLOCKS.set_merkle_concurrent_ms(merkle_concurrent_ms);
2558 METRICS_BLOCKS.set_merkle_drain_ms(merkle_drain_ms);
2559 METRICS_BLOCKS.set_merkle_ms(_merkle_total_ms);
2560 METRICS_BLOCKS.set_merkle_overlap_pct(overlap_pct);
2561 METRICS_BLOCKS.set_store_ms(store_ms);
2562 METRICS_BLOCKS.set_warmer_ms(warmer_ms);
2563 METRICS_BLOCKS.set_warmer_early_ms(warmer_early_ms);
2564 );
2565 }
2566
2567 pub async fn add_blocks_in_batch(
2579 &self,
2580 blocks: Vec<Block>,
2581 bals: &[Option<BlockAccessList>],
2582 cancellation_token: CancellationToken,
2583 ) -> Result<(), (ChainError, Option<BatchBlockProcessingFailure>)> {
2584 let mut last_valid_hash = H256::default();
2585
2586 debug_assert!(
2590 bals.is_empty() || bals.len() == blocks.len(),
2591 "bals must be empty or aligned with blocks (bals={}, blocks={})",
2592 bals.len(),
2593 blocks.len(),
2594 );
2595
2596 let Some(first_block_header) = blocks.first().map(|e| e.header.clone()) else {
2597 return Err((ChainError::Custom("First block not found".into()), None));
2598 };
2599
2600 let chain_config: ChainConfig = self.storage.get_chain_config();
2601
2602 let mut block_hash_cache: BTreeMap<BlockNumber, BlockHash> =
2605 blocks.iter().map(|b| (b.header.number, b.hash())).collect();
2606
2607 let parent_header = self
2608 .storage
2609 .get_block_header_by_hash(first_block_header.parent_hash)
2610 .map_err(|e| (ChainError::StoreError(e), None))?
2611 .ok_or((ChainError::ParentNotFound, None))?;
2612
2613 block_hash_cache
2617 .entry(parent_header.number)
2618 .or_insert_with(|| parent_header.hash());
2619 let mut hash = parent_header.parent_hash;
2620 let mut number = parent_header.number.saturating_sub(1);
2621 let lookback = first_block_header.number.saturating_sub(256);
2622 while number > lookback {
2623 block_hash_cache.entry(number).or_insert(hash);
2624 match self.storage.get_block_header_by_hash(hash) {
2625 Ok(Some(header)) => {
2626 hash = header.parent_hash;
2627 number = number.saturating_sub(1);
2628 }
2629 Ok(None) => break,
2630 Err(e) => {
2631 warn!("Failed to fetch block header by hash during BLOCKHASH cache walk: {e}");
2632 break;
2633 }
2634 }
2635 }
2636 let vm_db = StoreVmDatabase::new_with_block_hash_cache(
2637 self.storage.clone(),
2638 parent_header,
2639 block_hash_cache,
2640 )
2641 .map_err(|e| (ChainError::EvmError(e), None))?;
2642 let mut vm = self.new_evm(vm_db).map_err(|e| (e.into(), None))?;
2643
2644 let blocks_len = blocks.len();
2645 let mut all_receipts: Vec<(BlockHash, Vec<Receipt>)> = Vec::with_capacity(blocks_len);
2646 let mut total_gas_used = 0;
2647 let mut transactions_count = 0;
2648
2649 let interval = Instant::now();
2650 for (i, block) in blocks.iter().enumerate() {
2651 if cancellation_token.is_cancelled() {
2652 info!("Received shutdown signal, aborting");
2653 return Err((ChainError::Custom(String::from("shutdown signal")), None));
2654 }
2655 let parent_header = if i == 0 {
2657 find_parent_header(&block.header, &self.storage).map_err(|err| {
2658 (
2659 err,
2660 Some(BatchBlockProcessingFailure {
2661 failed_block_hash: block.hash(),
2662 last_valid_hash,
2663 }),
2664 )
2665 })?
2666 } else {
2667 blocks[i - 1].header.clone()
2669 };
2670
2671 let BlockExecutionResult { receipts, .. } = self
2672 .execute_block_from_state(&parent_header, block, &chain_config, &mut vm)
2673 .map_err(|err| {
2674 (
2675 err,
2676 Some(BatchBlockProcessingFailure {
2677 failed_block_hash: block.hash(),
2678 last_valid_hash,
2679 }),
2680 )
2681 })?;
2682 debug!("Executed block with hash {}", block.hash());
2683 last_valid_hash = block.hash();
2684 total_gas_used += block.header.gas_used;
2685 transactions_count += block.body.transactions.len();
2686 all_receipts.push((block.hash(), receipts));
2687
2688 log_batch_progress(blocks_len as u32, i as u32);
2690 tokio::task::yield_now().await;
2691 }
2692
2693 let account_updates = vm
2694 .get_state_transitions()
2695 .map_err(|err| (ChainError::EvmError(err), None))?;
2696
2697 let last_block = blocks
2698 .last()
2699 .ok_or_else(|| (ChainError::Custom("Last block not found".into()), None))?;
2700
2701 let last_block_number = last_block.header.number;
2702 let last_block_gas_limit = last_block.header.gas_limit;
2703
2704 let account_updates_list = self
2706 .storage
2707 .apply_account_updates_batch(first_block_header.parent_hash, &account_updates)
2708 .map_err(|e| (e.into(), None))?
2709 .ok_or((ChainError::ParentStateNotFound, None))?;
2710
2711 let new_state_root = account_updates_list.state_trie_hash;
2712 let state_updates = account_updates_list.state_updates;
2713 let accounts_updates = account_updates_list.storage_updates;
2714 let code_updates = account_updates_list.code_updates;
2715
2716 validate_state_root(&last_block.header, new_state_root).map_err(|e| (e, None))?;
2718
2719 let bals_to_store: Vec<(BlockHash, BlockAccessList)> = blocks
2726 .iter()
2727 .zip(bals.iter())
2728 .filter_map(|(block, bal)| {
2729 let bal = bal.as_ref()?;
2730 bal.matches_commitment(block.header.block_access_list_hash, &NativeCrypto)
2731 .then(|| (block.hash(), bal.clone()))
2732 })
2733 .collect();
2734
2735 let update_batch = UpdateBatch {
2736 account_updates: state_updates,
2737 storage_updates: accounts_updates,
2738 blocks,
2739 receipts: all_receipts,
2740 code_updates,
2741 batch_mode: true,
2742 };
2743
2744 self.storage
2745 .store_block_updates(update_batch)
2746 .map_err(|e| (e.into(), None))?;
2747
2748 for (block_hash, bal) in &bals_to_store {
2749 if let Err(err) = self.storage.store_block_access_list(*block_hash, bal) {
2750 warn!(
2751 "Failed to persist block access list for {block_hash} during batch sync: {err}"
2752 );
2753 }
2754 }
2755
2756 let elapsed_seconds = interval.elapsed().as_secs_f64();
2757 let throughput = if elapsed_seconds > 0.0 && total_gas_used != 0 {
2758 let as_gigas = (total_gas_used as f64) / 1e9;
2759 as_gigas / elapsed_seconds
2760 } else {
2761 0.0
2762 };
2763
2764 metrics!(
2765 METRICS_BLOCKS.set_block_number(last_block_number);
2766 METRICS_BLOCKS.set_latest_block_gas_limit(last_block_gas_limit as f64);
2767 METRICS_BLOCKS.set_latest_gas_used(total_gas_used as f64 / blocks_len as f64);
2769 METRICS_BLOCKS.set_latest_gigagas(throughput);
2770 );
2771
2772 if self.options.perf_logs_enabled {
2773 info!(
2774 "[METRICS] Executed and stored: Range: {}, Last block num: {}, Last block gas limit: {}, Total transactions: {}, Total Gas: {}, Throughput: {} Gigagas/s",
2775 blocks_len,
2776 last_block_number,
2777 last_block_gas_limit,
2778 transactions_count,
2779 total_gas_used,
2780 throughput
2781 );
2782 }
2783
2784 Ok(())
2785 }
2786
2787 #[cfg(feature = "c-kzg")]
2789 pub async fn add_blob_transaction_to_pool(
2790 &self,
2791 transaction: EIP4844Transaction,
2792 blobs_bundle: BlobsBundle,
2793 ) -> Result<H256, MempoolError> {
2794 let fork = self.current_fork().await?;
2795
2796 let transaction = Transaction::EIP4844Transaction(transaction);
2797 let hash = transaction.hash(&NativeCrypto);
2798 if self.mempool.contains_tx(hash)? {
2799 return Ok(hash);
2800 }
2801
2802 let wrapper_len = transaction.encode_canonical_len() + blobs_bundle.length();
2809 if wrapper_len > MAX_BLOB_TX_SIZE {
2810 return Err(MempoolError::TxSizeExceeded {
2811 actual: wrapper_len,
2812 limit: MAX_BLOB_TX_SIZE,
2813 });
2814 }
2815
2816 if let Transaction::EIP4844Transaction(transaction) = &transaction {
2818 blobs_bundle.validate(transaction, fork)?;
2819 }
2820
2821 let sender = transaction.sender(&NativeCrypto)?;
2822
2823 if let Some(tx_to_replace) = self.validate_transaction(&transaction, sender).await? {
2825 self.remove_transaction_from_pool(&tx_to_replace)?;
2826 }
2827
2828 self.mempool.add_blobs_bundle(hash, blobs_bundle)?;
2831 self.mempool
2832 .add_transaction(hash, sender, MempoolTransaction::new(transaction, sender))?;
2833 Ok(hash)
2834 }
2835
2836 pub async fn add_transaction_to_pool(
2838 &self,
2839 transaction: Transaction,
2840 ) -> Result<H256, MempoolError> {
2841 if matches!(transaction, Transaction::EIP4844Transaction(_)) {
2843 return Err(MempoolError::BlobTxNoBlobsBundle);
2844 }
2845 let encoded_len = transaction.encode_canonical_len();
2851 if encoded_len > MAX_TX_SIZE {
2852 return Err(MempoolError::TxSizeExceeded {
2853 actual: encoded_len,
2854 limit: MAX_TX_SIZE,
2855 });
2856 }
2857 let hash = transaction.hash(&NativeCrypto);
2858 if self.mempool.contains_tx(hash)? {
2859 return Ok(hash);
2860 }
2861 let sender = transaction.sender(&NativeCrypto)?;
2862 if let Some(tx_to_replace) = self.validate_transaction(&transaction, sender).await? {
2864 self.remove_transaction_from_pool(&tx_to_replace)?;
2865 }
2866
2867 self.mempool
2869 .add_transaction(hash, sender, MempoolTransaction::new(transaction, sender))?;
2870
2871 Ok(hash)
2872 }
2873
2874 pub fn remove_transaction_from_pool(&self, hash: &H256) -> Result<(), StoreError> {
2876 self.mempool.remove_transaction(hash)
2877 }
2878
2879 pub fn remove_block_transactions_from_pool(&self, block: &Block) -> Result<(), StoreError> {
2881 for tx in &block.body.transactions {
2882 self.mempool.remove_transaction(&tx.hash(&NativeCrypto))?;
2883 }
2884 Ok(())
2885 }
2886
2887 pub async fn remove_stale_blob_txs(&self, head_hash: BlockHash) -> Result<(), StoreError> {
2892 let blob_txs = self.mempool.blob_txs()?;
2893 if blob_txs.is_empty() {
2894 return Ok(());
2895 }
2896 let mut nonce_by_sender: HashMap<Address, u64> = HashMap::new();
2898 for (hash, sender, tx_nonce) in blob_txs {
2899 let state_nonce = match nonce_by_sender.entry(sender) {
2900 Entry::Occupied(e) => *e.get(),
2901 Entry::Vacant(e) => {
2902 let nonce = self
2903 .storage
2904 .get_account_info_by_hash(head_hash, sender)?
2905 .map(|info| info.nonce)
2906 .unwrap_or(0);
2907 *e.insert(nonce)
2908 }
2909 };
2910 if tx_nonce < state_nonce {
2911 self.mempool.remove_transaction(&hash)?;
2912 }
2913 }
2914 Ok(())
2915 }
2916
2917 pub async fn validate_transaction(
2950 &self,
2951 tx: &Transaction,
2952 sender: Address,
2953 ) -> Result<Option<H256>, MempoolError> {
2954 let nonce = tx.nonce();
2955
2956 if matches!(tx, &Transaction::PrivilegedL2Transaction(_)) {
2957 return Ok(None);
2958 }
2959
2960 let header_no = self.storage.get_latest_block_number().await?;
2961 let header = self
2962 .storage
2963 .get_block_header(header_no)?
2964 .ok_or(MempoolError::NoBlockHeaderError)?;
2965 let config = self.storage.get_chain_config();
2966
2967 if !matches!(tx, Transaction::EIP4844Transaction(_)) {
2974 let encoded_len = tx.encode_canonical_len();
2975 if encoded_len > MAX_TX_SIZE {
2976 return Err(MempoolError::TxSizeExceeded {
2977 actual: encoded_len,
2978 limit: MAX_TX_SIZE,
2979 });
2980 }
2981 }
2982
2983 let max_initcode_size = if config.is_amsterdam_activated(header.timestamp) {
2986 AMSTERDAM_MAX_INITCODE_SIZE
2987 } else {
2988 MAX_INITCODE_SIZE
2989 };
2990 if config.is_shanghai_activated(header.timestamp)
2991 && tx.is_contract_creation()
2992 && tx.data().len() > max_initcode_size as usize
2993 {
2994 return Err(MempoolError::TxMaxInitCodeSizeError);
2995 }
2996
2997 if config.is_osaka_activated(header.timestamp)
2998 && !config.is_amsterdam_activated(header.timestamp)
2999 && tx.gas_limit() > POST_OSAKA_GAS_LIMIT_CAP
3000 {
3001 return Err(MempoolError::TxMaxGasLimitExceededError(
3003 tx.hash(&NativeCrypto),
3004 tx.gas_limit(),
3005 ));
3006 }
3007
3008 if header.gas_limit < tx.gas_limit() {
3010 return Err(MempoolError::TxGasLimitExceededError);
3011 }
3012
3013 if tx.max_priority_fee().unwrap_or(0) > tx.max_fee_per_gas().unwrap_or(0) {
3015 return Err(MempoolError::TxTipAboveFeeCapError);
3016 }
3017
3018 if let Transaction::EIP7702Transaction(eip7702) = tx {
3023 if !config.is_prague_activated(header.timestamp) {
3025 return Err(MempoolError::Eip7702TxPreFork);
3026 }
3027 if eip7702.authorization_list.is_empty() {
3029 return Err(MempoolError::EmptyAuthorizationList);
3030 }
3031 }
3032
3033 if tx.gas_limit() < mempool::transaction_intrinsic_gas(tx, sender, &header, &config)? {
3035 return Err(MempoolError::TxIntrinsicGasCostAboveLimitError);
3036 }
3037
3038 if let Some(fee) = tx.max_fee_per_blob_gas() {
3040 if fee < MIN_BASE_FEE_PER_BLOB_GAS.into() {
3042 return Err(MempoolError::TxBlobBaseFeeTooLowError);
3043 }
3044 };
3045
3046 let maybe_sender_acc_info = self.storage.get_account_info(header_no, sender).await?;
3047
3048 if let Some(sender_acc_info) = maybe_sender_acc_info {
3049 if nonce < sender_acc_info.nonce || nonce == u64::MAX {
3050 return Err(MempoolError::NonceTooLow);
3051 }
3052
3053 if sender_acc_info.code_hash != *EMPTY_KECCAK_HASH {
3064 let metadata_len = self
3065 .storage
3066 .get_code_metadata(sender_acc_info.code_hash)?
3067 .map(|m| m.length);
3068 let is_delegation = if metadata_len == Some(EIP7702_DELEGATED_CODE_LEN as u64) {
3069 let code = self
3075 .storage
3076 .get_account_code(sender_acc_info.code_hash)?
3077 .ok_or_else(|| {
3078 StoreError::Custom(format!(
3079 "code missing for hash {:?} despite present metadata",
3080 sender_acc_info.code_hash
3081 ))
3082 })?;
3083 is_eip7702_delegation(code.code())
3084 } else {
3085 false
3086 };
3087 if !is_delegation {
3088 return Err(MempoolError::SenderIsContract);
3089 }
3090 }
3091
3092 let tx_cost = tx
3093 .cost_without_base_fee()
3094 .ok_or(MempoolError::InvalidTxGasvalues)?;
3095
3096 if tx_cost > sender_acc_info.balance {
3097 return Err(MempoolError::NotEnoughBalance);
3098 }
3099 } else {
3100 return Err(MempoolError::NotEnoughBalance);
3102 }
3103
3104 let tx_to_replace_hash = self.mempool.find_tx_to_replace(sender, nonce, tx)?;
3107
3108 if tx
3109 .chain_id()
3110 .is_some_and(|chain_id| chain_id != config.chain_id)
3111 {
3112 return Err(MempoolError::InvalidChainId(config.chain_id));
3113 }
3114
3115 Ok(tx_to_replace_hash)
3116 }
3117
3118 pub fn set_synced(&self) {
3121 self.is_synced.store(true, Ordering::Relaxed);
3122 }
3123
3124 pub fn set_not_synced(&self) {
3127 self.is_synced.store(false, Ordering::Relaxed);
3128 }
3129
3130 pub fn is_synced(&self) -> bool {
3134 self.is_synced.load(Ordering::Relaxed)
3135 }
3136
3137 pub fn get_p2p_transaction_by_hash(&self, hash: &H256) -> Result<P2PTransaction, StoreError> {
3138 let Some(tx) = self.mempool.get_transaction_by_hash(*hash)? else {
3139 return Err(StoreError::Custom(format!(
3140 "Hash {hash} not found in the mempool",
3141 )));
3142 };
3143 let result = match tx {
3144 Transaction::LegacyTransaction(itx) => P2PTransaction::LegacyTransaction(itx),
3145 Transaction::EIP2930Transaction(itx) => P2PTransaction::EIP2930Transaction(itx),
3146 Transaction::EIP1559Transaction(itx) => P2PTransaction::EIP1559Transaction(itx),
3147 Transaction::EIP4844Transaction(itx) => {
3148 let Some(bundle) = self.mempool.get_blobs_bundle(*hash)? else {
3149 return Err(StoreError::Custom(format!(
3150 "Blob transaction present without its bundle: hash {hash}",
3151 )));
3152 };
3153
3154 P2PTransaction::EIP4844TransactionWithBlobs(WrappedEIP4844Transaction {
3155 tx: itx,
3156 wrapper_version: (bundle.version != 0).then_some(bundle.version),
3157 blobs_bundle: bundle,
3158 })
3159 }
3160 Transaction::EIP7702Transaction(itx) => P2PTransaction::EIP7702Transaction(itx),
3161 Transaction::PrivilegedL2Transaction(_) => {
3165 return Err(StoreError::Custom(
3166 "Privileged Transactions are not supported in P2P".to_string(),
3167 ));
3168 }
3169 Transaction::FeeTokenTransaction(itx) => P2PTransaction::FeeTokenTransaction(itx),
3170 };
3171
3172 Ok(result)
3173 }
3174
3175 pub fn new_evm(&self, vm_db: StoreVmDatabase) -> Result<Evm, EvmError> {
3176 new_evm(&self.options.r#type, vm_db)
3177 }
3178
3179 pub async fn current_fork(&self) -> Result<Fork, StoreError> {
3181 let chain_config = self.storage.get_chain_config();
3182 let latest_block_number = self.storage.get_latest_block_number().await?;
3183 let latest_block = self
3184 .storage
3185 .get_block_header(latest_block_number)?
3186 .ok_or(StoreError::Custom("Latest block not in DB".to_string()))?;
3187 Ok(chain_config.fork(latest_block.timestamp))
3188 }
3189}
3190
3191fn load_trie(
3193 storage: &Store,
3194 parent_state_root: H256,
3195 prefix: Option<H256>,
3196) -> Result<Trie, StoreError> {
3197 Ok(match prefix {
3198 Some(account_hash) => {
3199 let state_trie = storage.open_state_trie(parent_state_root)?;
3200 let storage_root = match state_trie.get(account_hash.as_bytes())? {
3201 Some(rlp) => AccountState::decode(&rlp)?.storage_root,
3202 None => *EMPTY_TRIE_HASH,
3203 };
3204 storage.open_storage_trie(account_hash, parent_state_root, storage_root)?
3205 }
3206 None => storage.open_state_trie(parent_state_root)?,
3207 })
3208}
3209
3210fn collapse_root_node(
3213 storage: &Store,
3214 parent_state_root: H256,
3215 prefix: Option<H256>,
3216 root: BranchNode,
3217) -> Result<Option<Node>, StoreError> {
3218 let children: Vec<(usize, &NodeRef)> = root
3219 .choices
3220 .iter()
3221 .enumerate()
3222 .filter(|(_, choice)| choice.is_valid())
3223 .take(2)
3224 .collect();
3225 if children.len() > 1 {
3226 return Ok(Some(Node::Branch(Box::from(root))));
3227 }
3228 let Some((choice, only_child)) = children.first() else {
3229 return Ok(None);
3230 };
3231 let only_child = Arc::unwrap_or_clone(match only_child {
3232 NodeRef::Node(node, _) => node.clone(),
3233 noderef @ NodeRef::Hash(_) => {
3234 let trie = load_trie(storage, parent_state_root, prefix)?;
3235 let Some(node) = noderef.get_node(trie.db(), Nibbles::from_hex(vec![*choice as u8]))?
3236 else {
3237 return Ok(None);
3238 };
3239 node
3240 }
3241 });
3242 Ok(Some(match only_child {
3243 Node::Branch(_) => {
3244 ExtensionNode::new(Nibbles::from_hex(vec![*choice as u8]), only_child.into()).into()
3245 }
3246 Node::Extension(mut extension_node) => {
3247 extension_node.prefix.prepend(*choice as u8);
3248 extension_node.into()
3249 }
3250 Node::Leaf(mut leaf) => {
3251 leaf.partial.prepend(*choice as u8);
3252 leaf.into()
3253 }
3254 }))
3255}
3256
3257fn collect_and_send(
3259 index: u8,
3260 state_trie: &mut Trie,
3261 pre_collected_state: &mut Vec<TrieNode>,
3262 storage_nodes: &mut Vec<(H256, Vec<TrieNode>)>,
3263 tx: Sender<CollectedStateMsg>,
3264) -> Result<(), StoreError> {
3265 let (subroot, mut state_nodes) = collect_trie(index, std::mem::take(state_trie))?;
3266 if !pre_collected_state.is_empty() {
3267 let mut pre = std::mem::take(pre_collected_state);
3268 pre.extend(state_nodes);
3269 state_nodes = pre;
3270 }
3271 tx.send(CollectedStateMsg {
3272 index,
3273 subroot,
3274 state_nodes,
3275 storage_nodes: std::mem::take(storage_nodes),
3276 })
3277 .map_err(|e| StoreError::Custom(format!("send error: {e}")))?;
3278 Ok(())
3279}
3280
3281fn get_or_open_storage_trie<'a>(
3283 storage_tries: &'a mut FxHashMap<H256, Trie>,
3284 storage: &Store,
3285 parent_state_root: H256,
3286 prefix: H256,
3287 storage_root: H256,
3288) -> Result<&'a mut Trie, StoreError> {
3289 match storage_tries.entry(prefix) {
3290 Entry::Occupied(e) => Ok(e.into_mut()),
3291 Entry::Vacant(e) => {
3292 Ok(e.insert(storage.open_storage_trie(prefix, parent_state_root, storage_root)?))
3293 }
3294 }
3295}
3296
3297fn handle_subtrie(
3298 storage: Store,
3299 rx: cb::Receiver<WorkerRequest>,
3300 parent_state_root: H256,
3301 index: u8,
3302 worker_senders: Vec<cb::Sender<WorkerRequest>>,
3303 shutdown_rx: cb::Receiver<()>,
3304) -> Result<(), StoreError> {
3305 let mut state_trie = storage.open_state_trie(parent_state_root)?;
3306 let mut storage_nodes: Vec<(H256, Vec<TrieNode>)> = vec![];
3307 let mut accounts: FxHashMap<H256, AccountState> = Default::default();
3308 let mut expected_shards: FxHashMap<H256, u16> = Default::default();
3309 let mut storage_state: FxHashMap<H256, PreMerkelizedAccountState> = Default::default();
3310 let mut received_shards: FxHashMap<H256, u16> = Default::default();
3311 let mut pending_storage_accounts: usize = 0;
3312 let mut pending_collect_tx: Option<Sender<CollectedStateMsg>> = None;
3313 let mut pre_collected_state: Vec<TrieNode> = vec![];
3314 let mut storage_tries: FxHashMap<H256, Trie> = Default::default();
3315 let mut pre_collected_storage: FxHashMap<H256, Vec<TrieNode>> = Default::default();
3316
3317 let mut worker_senders: Option<Vec<cb::Sender<WorkerRequest>>> = Some(worker_senders);
3319 let mut dirty = false;
3320 let mut collecting_storages = false;
3323 let mut routing_complete = false;
3324 let mut routing_done_mask: u16 = 0;
3325 let mut storage_to_collect: Vec<(H256, Trie)> = vec![];
3326
3327 loop {
3328 if collecting_storages {
3331 if let Some((prefix, trie)) = storage_to_collect.pop() {
3332 let senders = worker_senders
3333 .as_ref()
3334 .expect("collecting after senders dropped");
3335 let (root, mut nodes) = collect_trie(index, trie)?;
3336 if let Some(mut pre_nodes) = pre_collected_storage.remove(&prefix) {
3337 pre_nodes.extend(nodes);
3338 nodes = pre_nodes;
3339 }
3340 let bucket = prefix.as_fixed_bytes()[0] >> 4;
3341 senders[bucket as usize]
3342 .send(WorkerRequest::StorageShard {
3343 prefix,
3344 index,
3345 subroot: root,
3346 nodes,
3347 })
3348 .map_err(|e| StoreError::Custom(format!("send error: {e}")))?;
3349 } else {
3350 worker_senders = None;
3352 collecting_storages = false;
3353 if pending_storage_accounts == 0
3355 && let Some(tx) = pending_collect_tx.take()
3356 {
3357 collect_and_send(
3358 index,
3359 &mut state_trie,
3360 &mut pre_collected_state,
3361 &mut storage_nodes,
3362 tx,
3363 )?;
3364 break;
3365 }
3366 }
3367 }
3368
3369 let msg = if collecting_storages || dirty {
3372 match rx.try_recv() {
3373 Ok(msg) => msg,
3374 Err(TryRecvError::Disconnected) => break,
3375 Err(TryRecvError::Empty) => {
3376 if matches!(shutdown_rx.try_recv(), Err(TryRecvError::Disconnected)) {
3378 return Err(StoreError::Custom("shard worker shutdown".into()));
3379 }
3380 if dirty {
3381 let mut nodes = state_trie.commit_without_storing(&NativeCrypto);
3385 nodes.retain(|(nib, _)| nib.as_ref().first() == Some(&index));
3386 pre_collected_state.extend(nodes);
3387 if !collecting_storages {
3388 for (prefix, trie) in storage_tries.iter_mut() {
3390 let mut nodes = trie.commit_without_storing(&NativeCrypto);
3391 nodes.retain(|(nib, _)| nib.as_ref().first() == Some(&index));
3392 if !nodes.is_empty() {
3393 pre_collected_storage
3394 .entry(*prefix)
3395 .or_default()
3396 .extend(nodes);
3397 }
3398 }
3399 }
3400 dirty = false;
3401 }
3402 continue;
3403 }
3404 }
3405 } else {
3406 select! {
3407 recv(rx) -> msg => match msg {
3408 Ok(msg) => msg,
3409 Err(_) => break,
3410 },
3411 recv(shutdown_rx) -> _ => {
3412 return Err(StoreError::Custom("shard worker shutdown".into()));
3413 }
3414 }
3415 };
3416
3417 match msg {
3418 WorkerRequest::ProcessAccount {
3419 prefix,
3420 info,
3421 storage: account_storage,
3422 removed,
3423 removed_storage,
3424 } => {
3425 let senders = worker_senders
3426 .as_ref()
3427 .expect("ProcessAccount after collection started");
3428
3429 match accounts.entry(prefix) {
3431 Entry::Occupied(_) => {}
3432 Entry::Vacant(vacant_entry) => {
3433 let account_state = match state_trie.get(prefix.as_bytes())? {
3434 Some(rlp) => {
3435 let state = AccountState::decode(&rlp)?;
3436 state_trie.insert(prefix.as_bytes().to_vec(), rlp)?;
3437 state
3438 }
3439 None => AccountState::default(),
3440 };
3441 vacant_entry.insert(account_state);
3442 }
3443 }
3444
3445 if let Some(info) = info {
3447 let acct = accounts.get_mut(&prefix).expect("just loaded");
3448 acct.nonce = info.nonce;
3449 acct.balance = info.balance;
3450 acct.code_hash = info.code_hash;
3451 let path = prefix.as_bytes();
3452 if *acct != AccountState::default() {
3453 state_trie.insert(path.to_vec(), acct.encode_to_vec())?;
3454 } else {
3455 state_trie.remove(path)?;
3456 }
3457 }
3458
3459 if removed || removed_storage {
3460 pre_collected_storage.remove(&prefix);
3462 storage_tries.insert(prefix, Trie::new_temp());
3463 for (i, tx) in senders.iter().enumerate() {
3464 if i as u8 != index {
3465 tx.send(WorkerRequest::DeleteStorage(prefix))
3466 .map_err(|e| StoreError::Custom(format!("send error: {e}")))?;
3467 }
3468 }
3469 accounts.get_mut(&prefix).expect("just loaded").storage_root = *EMPTY_TRIE_HASH;
3470 if expected_shards.insert(prefix, 0xFFFF).is_none() {
3471 pending_storage_accounts += 1;
3472 }
3473 if removed {
3474 dirty = true;
3475 continue;
3476 }
3477 }
3478
3479 if !account_storage.is_empty() {
3480 let storage_root = accounts
3481 .get(&prefix)
3482 .map(|a| a.storage_root)
3483 .unwrap_or(*EMPTY_TRIE_HASH);
3484
3485 let is_new = !expected_shards.contains_key(&prefix);
3486 for (key, value) in account_storage {
3487 let hashed_key = keccak(key);
3488 let bucket = hashed_key.as_fixed_bytes()[0] >> 4;
3489 *expected_shards.entry(prefix).or_insert(0u16) |= 1 << bucket;
3490 if bucket == index {
3491 let trie = get_or_open_storage_trie(
3493 &mut storage_tries,
3494 &storage,
3495 parent_state_root,
3496 prefix,
3497 storage_root,
3498 )?;
3499 if value.is_zero() {
3500 trie.remove(hashed_key.as_bytes())?;
3501 } else {
3502 trie.insert(hashed_key.as_bytes().to_vec(), value.encode_to_vec())?;
3503 }
3504 } else {
3505 senders[bucket as usize]
3506 .send(WorkerRequest::MerklizeStorage {
3507 prefix,
3508 key: hashed_key,
3509 value,
3510 storage_root,
3511 })
3512 .map_err(|e| StoreError::Custom(format!("send error: {e}")))?;
3513 }
3514 }
3515 if is_new {
3516 pending_storage_accounts += 1;
3517 }
3518 }
3519 dirty = true;
3520 }
3521 WorkerRequest::MerklizeStorage {
3522 prefix,
3523 key,
3524 value,
3525 storage_root,
3526 } => {
3527 let trie = get_or_open_storage_trie(
3528 &mut storage_tries,
3529 &storage,
3530 parent_state_root,
3531 prefix,
3532 storage_root,
3533 )?;
3534 if value.is_zero() {
3535 trie.remove(key.as_bytes())?;
3536 } else {
3537 trie.insert(key.as_bytes().to_vec(), value.encode_to_vec())?;
3538 }
3539 dirty = true;
3540 }
3541 WorkerRequest::DeleteStorage(prefix) => {
3542 pre_collected_storage.remove(&prefix);
3543 storage_tries.insert(prefix, Trie::new_temp());
3544 dirty = true;
3545 }
3546 WorkerRequest::FinishRouting => {
3547 let senders = worker_senders
3549 .as_ref()
3550 .expect("FinishRouting after senders dropped");
3551 for i in 0..16u8 {
3552 senders[i as usize]
3553 .send(WorkerRequest::RoutingDone { from: index })
3554 .map_err(|e| StoreError::Custom(format!("send error: {e}")))?;
3555 }
3556 }
3557 WorkerRequest::RoutingDone { from } => {
3558 routing_done_mask |= 1u16 << from;
3559 if routing_done_mask == 0xFFFF && !collecting_storages && !routing_complete {
3560 collecting_storages = true;
3561 routing_complete = true;
3562 storage_to_collect = storage_tries.drain().collect();
3563 }
3564 }
3565 WorkerRequest::MerklizeAccounts { accounts: batch } => {
3566 for hashed_account in batch {
3568 storage_nodes.push((hashed_account, vec![]));
3569 }
3570 }
3571 WorkerRequest::StorageShard {
3572 prefix,
3573 index: shard_index,
3574 mut subroot,
3575 nodes,
3576 } => {
3577 let state = storage_state.entry(prefix).or_default();
3578 match &mut state.storage_root {
3579 Some(root) => {
3580 root.choices[shard_index as usize] =
3581 std::mem::take(&mut subroot.choices[shard_index as usize]);
3582 }
3583 rootptr => {
3584 *rootptr = Some(subroot);
3585 }
3586 }
3587 state.nodes.extend(nodes);
3588
3589 let received = received_shards.entry(prefix).or_insert(0u16);
3590 *received |= 1 << shard_index;
3591 if *received == expected_shards.get(&prefix).copied().unwrap_or(0) {
3592 let mut state = storage_state.remove(&prefix).expect("shard without state");
3594 let new_storage_root = if let Some(mut root) = state.storage_root {
3595 root.choices.iter_mut().for_each(NodeRef::clear_hash);
3597 let collapsed =
3598 collapse_root_node(&storage, parent_state_root, Some(prefix), *root)?;
3599 if let Some(root) = collapsed {
3600 let mut root = NodeRef::from(root);
3601 let hash =
3602 root.commit(Nibbles::default(), &mut state.nodes, &NativeCrypto);
3603 let _ = DROP_SENDER.send(Box::new(root));
3604 hash.finalize(&NativeCrypto)
3605 } else {
3606 state.nodes.push((Nibbles::default(), vec![RLP_NULL]));
3607 *EMPTY_TRIE_HASH
3608 }
3609 } else {
3610 *EMPTY_TRIE_HASH
3611 };
3612 storage_nodes.push((prefix, state.nodes));
3613
3614 let old_state = accounts.get_mut(&prefix).expect("loaded in ProcessAccount");
3616 old_state.storage_root = new_storage_root;
3617 let path = prefix.as_bytes();
3618 if *old_state != AccountState::default() {
3619 state_trie.insert(path.to_vec(), old_state.encode_to_vec())?;
3620 } else {
3621 state_trie.remove(path)?;
3622 }
3623
3624 dirty = true;
3625 pending_storage_accounts -= 1;
3626 if pending_storage_accounts == 0
3627 && !collecting_storages
3628 && routing_complete
3629 && let Some(tx) = pending_collect_tx.take()
3630 {
3631 collect_and_send(
3632 index,
3633 &mut state_trie,
3634 &mut pre_collected_state,
3635 &mut storage_nodes,
3636 tx,
3637 )?;
3638 break;
3639 }
3640 }
3641 }
3642 WorkerRequest::CollectState { tx } => {
3643 if pending_storage_accounts == 0 && !collecting_storages && routing_complete {
3644 collect_and_send(
3645 index,
3646 &mut state_trie,
3647 &mut pre_collected_state,
3648 &mut storage_nodes,
3649 tx,
3650 )?;
3651 break;
3652 }
3653 pending_collect_tx = Some(tx);
3655 }
3656 }
3657 }
3658 Ok(())
3659}
3660
3661pub fn new_evm(blockchain_type: &BlockchainType, vm_db: StoreVmDatabase) -> Result<Evm, EvmError> {
3662 let evm = match blockchain_type {
3663 BlockchainType::L1 => Evm::new_for_l1(vm_db, Arc::new(NativeCrypto)),
3664 BlockchainType::L2(l2_config) => {
3665 let fee_config = *l2_config
3666 .fee_config
3667 .read()
3668 .map_err(|_| EvmError::Custom("Fee config lock was poisoned".to_string()))?;
3669
3670 Evm::new_for_l2(vm_db, fee_config, Arc::new(NativeCrypto))?
3671 }
3672 };
3673 Ok(evm)
3674}
3675
3676pub fn validate_state_root(
3678 block_header: &BlockHeader,
3679 new_state_root: H256,
3680) -> Result<(), ChainError> {
3681 if new_state_root == block_header.state_root {
3683 Ok(())
3684 } else {
3685 Err(ChainError::InvalidBlock(
3686 InvalidBlockError::StateRootMismatch,
3687 ))
3688 }
3689}
3690
3691pub async fn latest_canonical_block_hash(storage: &Store) -> Result<H256, ChainError> {
3693 let latest_block_number = storage.get_latest_block_number().await?;
3694 if let Some(latest_valid_header) = storage.get_block_header(latest_block_number)? {
3695 let latest_valid_hash = latest_valid_header.hash();
3696 return Ok(latest_valid_hash);
3697 }
3698 Err(ChainError::StoreError(StoreError::Custom(
3699 "Could not find latest valid hash".to_string(),
3700 )))
3701}
3702
3703pub fn find_parent_header(
3706 block_header: &BlockHeader,
3707 storage: &Store,
3708) -> Result<BlockHeader, ChainError> {
3709 match storage.get_block_header_by_hash(block_header.parent_hash)? {
3710 Some(parent_header) => Ok(parent_header),
3711 None => Err(ChainError::ParentNotFound),
3712 }
3713}
3714
3715pub async fn is_canonical(
3716 store: &Store,
3717 block_number: BlockNumber,
3718 block_hash: BlockHash,
3719) -> Result<bool, StoreError> {
3720 match store.get_canonical_block_hash(block_number).await? {
3721 Some(hash) if hash == block_hash => Ok(true),
3722 _ => Ok(false),
3723 }
3724}
3725
3726fn branchify(node: Node) -> Box<BranchNode> {
3727 match node {
3728 Node::Branch(branch_node) => branch_node,
3729 Node::Extension(extension_node) => {
3730 let index = extension_node.prefix.as_ref()[0];
3731 let noderef = if extension_node.prefix.len() == 1 {
3732 extension_node.child
3733 } else {
3734 let prefix = extension_node.prefix.offset(1);
3735 let node = ExtensionNode::new(prefix, extension_node.child);
3736 NodeRef::from(Arc::new(node.into()))
3737 };
3738 let mut choices = BranchNode::EMPTY_CHOICES;
3739 choices[index as usize] = noderef;
3740 Box::new(BranchNode::new(choices))
3741 }
3742 Node::Leaf(leaf_node) => {
3743 let index = leaf_node.partial.as_ref()[0];
3744 let node = LeafNode::new(leaf_node.partial.offset(1), leaf_node.value);
3745 let mut choices = BranchNode::EMPTY_CHOICES;
3746 choices[index as usize] = NodeRef::from(Arc::new(node.into()));
3747 Box::new(BranchNode::new(choices))
3748 }
3749 }
3750}
3751
3752fn collect_trie(index: u8, mut trie: Trie) -> Result<(Box<BranchNode>, Vec<TrieNode>), TrieError> {
3753 let root = branchify(
3754 trie.root_node()?
3755 .map(Arc::unwrap_or_clone)
3756 .unwrap_or_else(|| Node::Branch(Box::default())),
3757 );
3758 trie.root = Node::Branch(root).into();
3759 let (_, mut nodes) = trie.collect_changes_since_last_hash(&NativeCrypto);
3760 nodes.retain(|(nib, _)| nib.as_ref().first() == Some(&index));
3761
3762 let Some(Node::Branch(root)) = trie.root_node()?.map(Arc::unwrap_or_clone) else {
3763 return Err(TrieError::InvalidInput);
3764 };
3765 Ok((root, nodes))
3766}
3767
3768fn serial_storage_root(
3775 storage: &Store,
3776 parent_state_root: H256,
3777 hashed_address: H256,
3778 storage_root: H256,
3779 hashed_storage: &[(H256, U256)],
3780) -> Result<(H256, Vec<TrieNode>), StoreError> {
3781 let mut trie = storage.open_storage_trie(hashed_address, parent_state_root, storage_root)?;
3782 for (hashed_key, value) in hashed_storage {
3783 if value.is_zero() {
3784 trie.remove(hashed_key.as_bytes())?;
3785 } else {
3786 trie.insert(hashed_key.as_bytes().to_vec(), value.encode_to_vec())?;
3787 }
3788 }
3789 Ok(trie.collect_changes_since_last_hash(&NativeCrypto))
3790}
3791
3792#[doc(hidden)]
3815pub fn compute_sharded_storage_root(
3816 storage: &Store,
3817 parent_state_root: H256,
3818 hashed_address: H256,
3819 storage_root: H256,
3820 hashed_storage: &[(H256, U256)],
3821) -> Result<(H256, Vec<TrieNode>), StoreError> {
3822 let mut sorted = hashed_storage.to_vec();
3831 sorted.sort_by(|a, b| a.0.cmp(&b.0));
3832 let hashed_storage = sorted.as_slice();
3833
3834 let mut buckets: [Vec<(H256, U256)>; 16] = Default::default();
3836 for &(key, value) in hashed_storage {
3837 let nibble = (key.as_fixed_bytes()[0] >> 4) as usize;
3838 buckets[nibble].push((key, value));
3839 }
3840
3841 #[cfg(debug_assertions)]
3844 for (nibble, bucket) in buckets.iter().enumerate() {
3845 for (key, _) in bucket {
3846 debug_assert_eq!(
3847 (key.as_fixed_bytes()[0] >> 4) as usize,
3848 nibble,
3849 "storage slot in wrong shard bucket"
3850 );
3851 }
3852 }
3853
3854 let occupied = buckets.iter().filter(|b| !b.is_empty()).count();
3859 if occupied <= 1 {
3860 return serial_storage_root(
3861 storage,
3862 parent_state_root,
3863 hashed_address,
3864 storage_root,
3865 hashed_storage,
3866 );
3867 }
3868
3869 let mut root = BranchNode::default();
3870 let mut nodes: Vec<TrieNode> = Vec::new();
3871
3872 std::thread::scope(|s| -> Result<(), StoreError> {
3876 let handles: Vec<_> = buckets
3877 .into_iter()
3878 .enumerate()
3879 .map(|(nibble, bucket)| {
3880 std::thread::Builder::new()
3881 .name(format!("storage_shard_{nibble}"))
3882 .spawn_scoped(
3883 s,
3884 move || -> Result<(Box<BranchNode>, Vec<TrieNode>), StoreError> {
3885 let mut trie = storage.open_storage_trie(
3886 hashed_address,
3887 parent_state_root,
3888 storage_root,
3889 )?;
3890 for (hashed_key, value) in &bucket {
3891 if value.is_zero() {
3892 trie.remove(hashed_key.as_bytes())?;
3893 } else {
3894 trie.insert(
3895 hashed_key.as_bytes().to_vec(),
3896 value.encode_to_vec(),
3897 )?;
3898 }
3899 }
3900 collect_trie(nibble as u8, trie)
3901 .map_err(|e| StoreError::Custom(format!("{e}")))
3902 },
3903 )
3904 .map_err(|e| StoreError::Custom(format!("spawn failed: {e}")))
3905 })
3906 .collect::<Result<Vec<_>, _>>()?;
3907
3908 for (i, handle) in handles.into_iter().enumerate() {
3909 let (subroot, shard_nodes) = handle
3910 .join()
3911 .map_err(|_| StoreError::Custom("storage shard worker panicked".to_string()))??;
3912 nodes.extend(shard_nodes);
3913 root.choices[i] = subroot.choices[i].clone();
3914 }
3915 Ok(())
3916 })?;
3917
3918 let Some(root_node) =
3921 collapse_root_node(storage, parent_state_root, Some(hashed_address), root)?
3922 else {
3923 return serial_storage_root(
3927 storage,
3928 parent_state_root,
3929 hashed_address,
3930 storage_root,
3931 hashed_storage,
3932 );
3933 };
3934
3935 let mut root_ref = NodeRef::from(root_node);
3936 let root_hash = root_ref.commit(Nibbles::default(), &mut nodes, &NativeCrypto);
3937 let _ = DROP_SENDER.send(Box::new(root_ref));
3938
3939 Ok((root_hash.finalize(&NativeCrypto), nodes))
3940}