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_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::{ELASTICITY_MULTIPLIER, P2PTransaction};
75use ethrex_common::types::{Fork, MempoolTransaction};
76use ethrex_common::utils::keccak;
77use ethrex_common::{Address, H256, TrieLogger, U256};
78pub use ethrex_common::{
79 get_total_blob_gas, validate_block_access_list_hash, validate_block_pre_execution,
80 validate_gas_used, validate_receipts_root_and_logs_bloom, validate_requests_hash,
81};
82use ethrex_crypto::NativeCrypto;
83use ethrex_metrics::metrics;
84use ethrex_rlp::constants::RLP_NULL;
85use ethrex_rlp::decode::RLPDecode;
86use ethrex_rlp::encode::RLPEncode;
87use ethrex_storage::{
88 AccountUpdatesList, Store, UpdateBatch, error::StoreError, hash_address, hash_key,
89};
90use ethrex_trie::node::{BranchNode, ExtensionNode, LeafNode};
91use ethrex_trie::{Nibbles, Node, NodeRef, Trie, TrieError, TrieNode};
92use ethrex_vm::backends::CachingDatabase;
93#[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
94use ethrex_vm::backends::levm::LEVM;
95use ethrex_vm::backends::levm::db::DatabaseLogger;
96use ethrex_vm::{BlockExecutionResult, DynVmDatabase, Evm, EvmError};
97use mempool::Mempool;
98use payload::PayloadOrTask;
99use rustc_hash::{FxHashMap, FxHashSet};
100use std::collections::hash_map::Entry;
101use std::collections::{BTreeMap, HashMap, HashSet};
102use std::sync::LazyLock;
103use std::sync::mpsc::Sender;
104use std::sync::{
105 Arc, RwLock,
106 atomic::{AtomicBool, AtomicUsize, Ordering},
107 mpsc::{Receiver, channel},
108};
109use std::time::{Duration, Instant};
110use tokio::sync::Mutex as TokioMutex;
111use tokio_util::sync::CancellationToken;
112
113use vm::StoreVmDatabase;
114
115#[cfg(feature = "metrics")]
116use ethrex_metrics::bal::METRICS_BAL;
117#[cfg(feature = "metrics")]
118use ethrex_metrics::blocks::METRICS_BLOCKS;
119
120#[cfg(feature = "c-kzg")]
121use ethrex_common::types::BlobsBundle;
122
123const MAX_PAYLOADS: usize = 10;
124const MAX_MEMPOOL_SIZE_DEFAULT: usize = 10_000;
125
126static DROP_SENDER: LazyLock<Sender<Box<dyn Send>>> = LazyLock::new(|| {
130 let (tx, rx) = channel::<Box<dyn Send>>();
131 std::thread::Builder::new()
132 .name("drop_thread".to_string())
133 .spawn(move || for _ in rx {})
134 .expect("failed to spawn drop thread");
135 tx
136});
137
138type BlockExecutionPipelineResult = (
140 BlockExecutionResult,
141 AccountUpdatesList,
142 Option<Vec<AccountUpdate>>,
143 Option<BlockAccessList>, usize, [Instant; 7], Duration, );
148
149type AddBlockPipelineInnerResult = (
150 Option<BlockAccessList>,
151 Option<ExecutionWitness>,
152 Result<(), ChainError>,
153);
154
155#[derive(Debug, Clone, Default)]
160pub enum BlockchainType {
161 #[default]
163 L1,
164 L2(L2Config),
166}
167
168#[derive(Debug, Clone, Default)]
170pub struct L2Config {
171 pub fee_config: Arc<RwLock<FeeConfig>>,
175}
176
177#[derive(Debug)]
204pub struct Blockchain {
205 storage: Store,
207 pub mempool: Mempool,
209 is_synced: AtomicBool,
214 pub options: BlockchainOptions,
216 pub payloads: Arc<TokioMutex<Vec<(u64, PayloadOrTask)>>>,
221 merkle_pool: Arc<rayon::ThreadPool>,
228}
229
230#[derive(Debug, Clone)]
232pub struct BlockchainOptions {
233 pub max_mempool_size: usize,
235 pub perf_logs_enabled: bool,
237 pub r#type: BlockchainType,
239 pub max_blobs_per_block: Option<u32>,
242 pub precompute_witnesses: bool,
244 pub precompile_cache_enabled: bool,
248 pub bal_parallel_exec_enabled: bool,
252 pub bal_prefetch_enabled: bool,
256 pub bal_parallel_trie_enabled: bool,
261}
262
263impl Default for BlockchainOptions {
264 fn default() -> Self {
265 Self {
266 max_mempool_size: MAX_MEMPOOL_SIZE_DEFAULT,
267 perf_logs_enabled: false,
268 r#type: BlockchainType::default(),
269 max_blobs_per_block: None,
270 precompute_witnesses: false,
271 precompile_cache_enabled: true,
272 bal_parallel_exec_enabled: true,
273 bal_prefetch_enabled: true,
274 bal_parallel_trie_enabled: true,
275 }
276 }
277}
278
279#[derive(Debug, Clone)]
280pub struct BatchBlockProcessingFailure {
281 pub last_valid_hash: H256,
282 pub failed_block_hash: H256,
283}
284
285fn log_batch_progress(batch_size: u32, current_block: u32) {
286 let progress_needed = batch_size > 10;
287 const PERCENT_MARKS: [u32; 4] = [20, 40, 60, 80];
288 if progress_needed {
289 PERCENT_MARKS.iter().for_each(|mark| {
290 if (batch_size * mark) / 100 == current_block {
291 info!("[SYNCING] {mark}% of batch processed");
292 }
293 });
294 }
295}
296
297enum WorkerRequest {
298 ProcessAccount {
300 prefix: H256,
301 info: Option<AccountInfo>,
302 storage: FxHashMap<H256, U256>,
303 removed: bool,
304 removed_storage: bool,
305 },
306 FinishRouting,
308 MerklizeAccounts {
309 accounts: Vec<H256>,
310 },
311 CollectState {
312 tx: Sender<CollectedStateMsg>,
313 },
314 MerklizeStorage {
316 prefix: H256,
317 key: H256,
318 value: U256,
319 storage_root: H256,
320 },
321 DeleteStorage(H256),
322 RoutingDone {
324 from: u8,
325 },
326 StorageShard {
328 prefix: H256,
329 index: u8,
330 subroot: Box<BranchNode>,
331 nodes: Vec<TrieNode>,
332 },
333}
334
335struct CollectedStateMsg {
336 index: u8,
337 subroot: Box<BranchNode>,
338 state_nodes: Vec<TrieNode>,
339 storage_nodes: Vec<(H256, Vec<TrieNode>)>,
340}
341
342#[derive(Default)]
343struct PreMerkelizedAccountState {
344 storage_root: Option<Box<BranchNode>>,
345 nodes: Vec<TrieNode>,
346}
347
348struct BalStateWorkItem {
350 hashed_address: H256,
351 nonce: Option<u64>,
352 balance: Option<U256>,
353 code_hash: Option<H256>,
354 storage_root: Option<H256>,
356}
357
358impl Blockchain {
359 pub fn build_merkle_pool() -> Arc<rayon::ThreadPool> {
363 Arc::new(
364 rayon::ThreadPoolBuilder::new()
365 .num_threads(17)
366 .thread_name(|i| format!("merkle-worker-{i}"))
367 .build()
368 .expect("Failed to create merkle thread pool"),
369 )
370 }
371
372 pub fn new(store: Store, blockchain_opts: BlockchainOptions) -> Self {
373 Self {
374 storage: store,
375 mempool: Mempool::new(blockchain_opts.max_mempool_size),
376 is_synced: AtomicBool::new(false),
377 payloads: Arc::new(TokioMutex::new(Vec::new())),
378 options: blockchain_opts,
379 merkle_pool: Self::build_merkle_pool(),
380 }
381 }
382
383 pub fn default_with_store_and_pool(store: Store, pool: Arc<rayon::ThreadPool>) -> Self {
393 Self {
394 storage: store,
395 mempool: Mempool::new(MAX_MEMPOOL_SIZE_DEFAULT),
396 is_synced: AtomicBool::new(false),
397 payloads: Arc::new(TokioMutex::new(Vec::new())),
398 options: BlockchainOptions::default(),
399 merkle_pool: pool,
400 }
401 }
402
403 pub fn default_with_store(store: Store) -> Self {
404 Self {
405 storage: store,
406 mempool: Mempool::new(MAX_MEMPOOL_SIZE_DEFAULT),
407 is_synced: AtomicBool::new(false),
408 payloads: Arc::new(TokioMutex::new(Vec::new())),
409 options: BlockchainOptions::default(),
410 merkle_pool: Self::build_merkle_pool(),
411 }
412 }
413
414 fn validate_l1_transaction_types(&self, block: &Block) -> Result<(), ChainError> {
421 if !matches!(self.options.r#type, BlockchainType::L1) {
422 return Ok(());
423 }
424 for tx in &block.body.transactions {
425 if tx.tx_type().is_l2_only() {
426 return Err(ChainError::InvalidBlock(
427 InvalidBlockError::UnsupportedTransactionType(tx.tx_type() as u8),
428 ));
429 }
430 }
431 Ok(())
432 }
433
434 fn execute_block(
436 &self,
437 block: &Block,
438 ) -> Result<(BlockExecutionResult, Vec<AccountUpdate>), ChainError> {
439 let Ok(parent_header) = find_parent_header(&block.header, &self.storage) else {
441 self.storage.add_pending_block(block.clone())?;
443 return Err(ChainError::ParentNotFound);
444 };
445
446 let chain_config = self.storage.get_chain_config();
447
448 validate_block_pre_execution(block, &parent_header, &chain_config, ELASTICITY_MULTIPLIER)?;
450 self.validate_l1_transaction_types(block)?;
451
452 let vm_db = StoreVmDatabase::new(self.storage.clone(), parent_header)?;
453 let mut vm = self.new_evm(vm_db)?;
454
455 let (execution_result, bal) = vm.execute_block(block)?;
456 let account_updates = vm.get_state_transitions()?;
457
458 if let Err(e) = validate_gas_used(execution_result.block_gas_used, &block.header) {
460 ethrex_vm::log_gas_used_mismatch(
461 &execution_result.tx_gas_breakdowns,
462 block.header.number,
463 execution_result.block_gas_used,
464 block.header.gas_used,
465 );
466 return Err(e.into());
467 }
468 validate_receipts_root_and_logs_bloom(
469 &block.header,
470 &execution_result.receipts,
471 &NativeCrypto,
472 )?;
473 validate_requests_hash(&block.header, &chain_config, &execution_result.requests)?;
474 if let Some(bal) = &bal {
475 validate_block_access_list_hash(
476 &block.header,
477 &chain_config,
478 bal,
479 block.body.transactions.len(),
480 &NativeCrypto,
481 )?;
482 }
483
484 Ok((execution_result, account_updates))
485 }
486
487 pub fn generate_bal_for_block(
491 &self,
492 block: &Block,
493 ) -> Result<Option<BlockAccessList>, ChainError> {
494 let chain_config = self.storage.get_chain_config();
495
496 if !chain_config.is_amsterdam_activated(block.header.timestamp) {
498 return Ok(None);
499 }
500
501 let parent_header = find_parent_header(&block.header, &self.storage)?;
503
504 let vm_db = StoreVmDatabase::new(self.storage.clone(), parent_header)?;
506 let mut vm = self.new_evm(vm_db)?;
507
508 let (_execution_result, bal) = vm.execute_block(block)?;
509
510 Ok(bal)
511 }
512
513 #[instrument(
515 level = "trace",
516 name = "Execute Block",
517 skip_all,
518 fields(namespace = "block_execution")
519 )]
520 fn execute_block_pipeline(
521 &self,
522 block: &Block,
523 parent_header: &BlockHeader,
524 vm: &mut Evm,
525 bal: Option<&BlockAccessList>,
526 collect_witness: bool,
527 ) -> Result<BlockExecutionPipelineResult, ChainError> {
528 let start_instant = Instant::now();
529
530 let chain_config = self.storage.get_chain_config();
531
532 validate_block_pre_execution(block, parent_header, &chain_config, ELASTICITY_MULTIPLIER)?;
534 self.validate_l1_transaction_types(block)?;
535 validate_block_body(&block.header, &block.body, &NativeCrypto)
536 .map_err(|e| ChainError::InvalidBlock(InvalidBlockError::InvalidBody(e)))?;
537 let block_validated_instant = Instant::now();
538
539 let exec_merkle_start = Instant::now();
540 let queue_length = AtomicUsize::new(0);
541 let queue_length_ref = &queue_length;
542 let mut max_queue_length = 0;
543
544 let original_store = vm.db.store.clone();
547 let caching_store: Arc<dyn ethrex_vm::backends::LevmDatabase> = Arc::new(
548 CachingDatabase::new(original_store, self.options.precompile_cache_enabled),
549 );
550
551 vm.db.store = caching_store.clone();
553
554 let cancelled = AtomicBool::new(false);
555 let bal_parallel_exec_enabled = self.options.bal_parallel_exec_enabled && !collect_witness;
560
561 let optimistic_updates: Option<FxHashMap<Address, BalSynthesisItem>> =
571 if self.options.bal_parallel_trie_enabled && !collect_witness {
572 bal.map(synthesize_bal_updates)
573 } else {
574 None
575 };
576
577 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
612 if self.options.bal_prefetch_enabled
613 && !collect_witness
614 && let Some(bal) = bal
615 {
616 let slots = LEVM::bal_storage_slots(bal);
617 if !slots.is_empty() {
618 let _ = caching_store.prefetch_storage(&slots);
619 }
620 }
621
622 let (execution_result, merkleization_result, warmer_duration) =
623 std::thread::scope(|s| -> Result<_, ChainError> {
624 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
625 let vm_type = vm.vm_type;
626 let cancelled_ref = &cancelled;
627 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
628 let bal_prefetch_enabled = self.options.bal_prefetch_enabled;
629 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
630 let warm_handle = (!collect_witness)
631 .then(|| {
632 std::thread::Builder::new()
633 .name("block_executor_warmer".to_string())
634 .spawn_scoped(s, move || {
635 let start = Instant::now();
638 if let Some(bal) = bal {
639 if bal_prefetch_enabled {
640 if let Err(e) = LEVM::warm_block_from_bal(
642 bal,
643 caching_store,
644 cancelled_ref,
645 ) {
646 debug!("BAL warming failed (non-fatal): {e}");
647 }
648 } else if !bal_parallel_exec_enabled {
649 if let Err(e) = LEVM::warm_block(
655 block,
656 caching_store,
657 vm_type,
658 &NativeCrypto,
659 cancelled_ref,
660 ) {
661 debug!("Block warming failed (non-fatal): {e}");
662 }
663 }
664 } else {
665 if let Err(e) = LEVM::warm_block(
667 block,
668 caching_store,
669 vm_type,
670 &NativeCrypto,
671 cancelled_ref,
672 ) {
673 debug!("Block warming failed (non-fatal): {e}");
674 }
675 }
676 start.elapsed()
677 })
678 .map_err(|e| {
679 ChainError::Custom(format!("Failed to spawn warmer thread: {e}"))
680 })
681 })
682 .transpose()?;
683 let max_queue_length_ref = &mut max_queue_length;
684 let (tx, rx_for_merkle) =
694 if optimistic_updates.is_some() && bal_parallel_exec_enabled {
695 (None, None)
696 } else {
697 let (tx, rx) = channel();
698 (Some(tx), Some(rx))
699 };
700
701 let execution_handle = std::thread::Builder::new()
702 .name("block_executor_execution".to_string())
703 .spawn_scoped(s, move || -> Result<_, ChainError> {
704 let result = vm.execute_block_pipeline(
705 block,
706 tx,
707 queue_length_ref,
708 bal,
709 bal_parallel_exec_enabled,
710 );
711 cancelled_ref.store(true, Ordering::Relaxed);
712 let (execution_result, produced_bal) = result?;
713
714 if let Err(e) =
716 validate_gas_used(execution_result.block_gas_used, &block.header)
717 {
718 ethrex_vm::log_gas_used_mismatch(
719 &execution_result.tx_gas_breakdowns,
720 block.header.number,
721 execution_result.block_gas_used,
722 block.header.gas_used,
723 );
724 return Err(e.into());
725 }
726 validate_receipts_root_and_logs_bloom(
727 &block.header,
728 &execution_result.receipts,
729 &NativeCrypto,
730 )?;
731 validate_requests_hash(
732 &block.header,
733 &chain_config,
734 &execution_result.requests,
735 )?;
736 if let Some(bal) = &produced_bal {
757 validate_block_access_list_hash(
758 &block.header,
759 &chain_config,
760 bal,
761 block.body.transactions.len(),
762 &NativeCrypto,
763 )?;
764 } else if let Some(header_bal) = bal
765 && chain_config.is_amsterdam_activated(block.header.timestamp)
766 && !header_bal.matches_commitment(
767 block.header.block_access_list_hash,
768 &NativeCrypto,
769 )
770 {
771 return Err(InvalidBlockError::BlockAccessListHashMismatch.into());
772 }
773
774 let exec_end_instant = Instant::now();
775 Ok((execution_result, produced_bal, exec_end_instant))
776 })
777 .map_err(|e| {
778 ChainError::Custom(format!("Failed to spawn execution thread: {e}"))
779 })?;
780 let parent_header_ref = &parent_header; type MerkleResult = Result<
783 (
784 AccountUpdatesList,
785 Option<Vec<AccountUpdate>>,
786 Instant,
787 Instant,
788 ),
789 StoreError,
790 >;
791 let merkleize_handle = std::thread::Builder::new()
792 .name("block_executor_merkleizer".to_string())
793 .spawn_scoped(s, move || -> MerkleResult {
794 let merkle_start_instant = Instant::now();
795 let (account_updates_list, streaming_witness) =
796 if let Some(prepared) = optimistic_updates {
797 let list = self.handle_merkleization_bal_from_updates(
798 prepared,
799 parent_header_ref,
800 )?;
801 (list, None)
802 } else {
803 self.handle_merkleization(
804 rx_for_merkle.expect("rx is Some on non-BAL path"),
805 parent_header_ref,
806 queue_length_ref,
807 max_queue_length_ref,
808 collect_witness,
809 )?
810 };
811 let merkle_end_instant = Instant::now();
812 Ok((
813 account_updates_list,
814 streaming_witness,
815 merkle_start_instant,
816 merkle_end_instant,
817 ))
818 })
819 .map_err(|e| {
820 ChainError::Custom(format!("Failed to spawn merkleizer thread: {e}"))
821 })?;
822 let execution_result = execution_handle.join().unwrap_or_else(|_| {
823 Err(ChainError::Custom("execution thread panicked".to_string()))
824 });
825 let merkleization_result = merkleize_handle.join().unwrap_or_else(|_| {
826 Err(StoreError::Custom(
827 "merkleization thread panicked".to_string(),
828 ))
829 });
830 #[cfg(all(feature = "rayon", not(feature = "eip-8025")))]
831 let warmer_duration = warm_handle
832 .map(|handle| {
833 handle
834 .join()
835 .inspect_err(|e| warn!("Warming thread error: {e:?}"))
836 .ok()
837 .unwrap_or(Duration::ZERO)
838 })
839 .unwrap_or(Duration::ZERO);
840 #[cfg(any(not(feature = "rayon"), feature = "eip-8025"))]
841 let warmer_duration = Duration::ZERO;
842 Ok((execution_result, merkleization_result, warmer_duration))
843 })?;
844 let (account_updates_list, streaming_witness, merkle_start_instant, merkle_end_instant) =
845 merkleization_result?;
846 let (execution_result, produced_bal, exec_end_instant) = execution_result?;
847
848 let accumulated_updates = streaming_witness;
852
853 let exec_merkle_end_instant = Instant::now();
854
855 Ok((
856 execution_result,
857 account_updates_list,
858 accumulated_updates,
859 produced_bal,
860 max_queue_length,
861 [
862 start_instant,
863 block_validated_instant,
864 exec_merkle_start,
865 merkle_start_instant,
866 exec_end_instant,
867 merkle_end_instant,
868 exec_merkle_end_instant,
869 ],
870 warmer_duration,
871 ))
872 }
873
874 #[instrument(
875 level = "trace",
876 name = "Trie update",
877 skip_all,
878 fields(namespace = "block_execution")
879 )]
880 fn handle_merkleization(
881 &self,
882 rx: Receiver<Vec<AccountUpdate>>,
883 parent_header: &BlockHeader,
884 queue_length: &AtomicUsize,
885 max_queue_length: &mut usize,
886 collect_witness: bool,
887 ) -> Result<(AccountUpdatesList, Option<Vec<AccountUpdate>>), StoreError> {
888 let parent_state_root = parent_header.state_root;
889
890 let mut workers_tx = Vec::with_capacity(16);
892 let mut workers_rx = Vec::with_capacity(16);
893 for _ in 0..16 {
894 let (tx, rx) = cb::unbounded();
895 workers_tx.push(tx);
896 workers_rx.push(rx);
897 }
898
899 let (shutdown_tx, shutdown_rx) = cb::bounded::<()>(0);
901 let (done_tx, done_rx) = cb::unbounded::<Result<(), StoreError>>();
903
904 let watcher_error: Arc<std::sync::Mutex<Option<StoreError>>> = Default::default();
909 let result = self.merkle_pool.in_place_scope(|s| {
910 for (i, rx) in workers_rx.into_iter().enumerate() {
912 let all_senders = workers_tx.clone();
913 let storage_clone = self.storage.clone();
914 let shutdown_rx = shutdown_rx.clone();
915 let done_tx = done_tx.clone();
916 s.spawn(move |_| {
917 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
918 handle_subtrie(
919 storage_clone,
920 rx,
921 parent_state_root,
922 i as u8,
923 all_senders,
924 shutdown_rx,
925 )
926 }));
927 let result = match result {
928 Ok(r) => r,
929 Err(_) => Err(StoreError::Custom(format!("shard worker {i} panicked"))),
930 };
931 if let Err(cb::SendError(Err(e))) = done_tx.send(result) {
932 error!("Failed to send worker {i} error to watcher: {e}");
933 }
934 });
935 }
936 drop(done_tx); drop(shutdown_rx); let watcher_error = watcher_error.clone();
942 s.spawn(move |_| {
943 let _shutdown = shutdown_tx;
944 for result in done_rx {
945 if let Err(e) = result {
946 *watcher_error.lock().expect("watcher mutex poisoned") = Some(e);
948 return;
949 }
950 }
951 });
952
953 let mut code_updates: Vec<(H256, Code)> = vec![];
955 let mut hashed_address_cache: FxHashMap<Address, H256> = Default::default();
956 let mut has_storage: FxHashSet<H256> = Default::default();
957
958 let mut accumulator: Option<FxHashMap<Address, AccountUpdate>> =
959 collect_witness.then(FxHashMap::default);
960
961 for updates in rx {
962 let current_length = queue_length.fetch_sub(1, Ordering::Acquire);
963 *max_queue_length = current_length.max(*max_queue_length);
964 if let Some(acc) = &mut accumulator {
966 for update in updates.clone() {
967 match acc.entry(update.address) {
968 Entry::Vacant(e) => {
969 e.insert(update);
970 }
971 Entry::Occupied(mut e) => {
972 e.get_mut().merge(update);
973 }
974 }
975 }
976 }
977
978 for update in updates {
979 let hashed_address = *hashed_address_cache
980 .entry(update.address)
981 .or_insert_with(|| keccak(update.address));
982
983 let (info, code, storage) = if update.removed {
984 (Some(Default::default()), None, Default::default())
985 } else {
986 (update.info, update.code, update.added_storage)
987 };
988
989 if let Some(ref info) = info
991 && let Some(code) = code
992 {
993 code_updates.push((info.code_hash, code));
994 }
995
996 if update.removed || update.removed_storage || !storage.is_empty() {
997 has_storage.insert(hashed_address);
998 }
999
1000 let bucket = hashed_address.as_fixed_bytes()[0] >> 4;
1001 workers_tx[bucket as usize]
1002 .send(WorkerRequest::ProcessAccount {
1003 prefix: hashed_address,
1004 info,
1005 storage,
1006 removed: update.removed,
1007 removed_storage: update.removed_storage,
1008 })
1009 .map_err(|e| StoreError::Custom(format!("send error: {e}")))?;
1010 }
1011 }
1012
1013 for tx in &workers_tx {
1015 tx.send(WorkerRequest::FinishRouting)
1016 .map_err(|e| StoreError::Custom(format!("send error: {e}")))?;
1017 }
1018
1019 let mut early_batches: [Vec<H256>; 16] = Default::default();
1021 for hashed_account in hashed_address_cache.values() {
1022 if !has_storage.contains(hashed_account) {
1023 let bucket = hashed_account.as_fixed_bytes()[0] >> 4;
1024 early_batches[bucket as usize].push(*hashed_account);
1025 }
1026 }
1027 for (i, batch) in early_batches.into_iter().enumerate() {
1028 if !batch.is_empty() {
1029 workers_tx[i]
1030 .send(WorkerRequest::MerklizeAccounts { accounts: batch })
1031 .map_err(|e| StoreError::Custom(format!("send error: {e}")))?;
1032 }
1033 }
1034
1035 let mut storage_updates: Vec<(H256, Vec<TrieNode>)> = Default::default();
1037 let (gatherer_tx, gatherer_rx) = channel();
1038 for tx in &workers_tx {
1039 tx.send(WorkerRequest::CollectState {
1040 tx: gatherer_tx.clone(),
1041 })
1042 .map_err(|e| StoreError::Custom(format!("send error: {e}")))?;
1043 }
1044 drop(gatherer_tx);
1045 drop(workers_tx);
1046
1047 let mut root = BranchNode::default();
1048 let mut state_updates = Vec::new();
1049 for CollectedStateMsg {
1050 index,
1051 subroot,
1052 state_nodes,
1053 storage_nodes,
1054 } in gatherer_rx
1055 {
1056 storage_updates.extend(storage_nodes);
1057 state_updates.extend(state_nodes);
1058 root.choices[index as usize] = subroot.choices[index as usize].clone();
1059 }
1060
1061 let collapsed = self.collapse_root_node(parent_header, None, root)?;
1062 let state_trie_hash = if let Some(root) = collapsed {
1063 let mut root = NodeRef::from(root);
1064 let hash = root.commit(Nibbles::default(), &mut state_updates, &NativeCrypto);
1065 let _ = DROP_SENDER.send(Box::new(root));
1066 hash.finalize(&NativeCrypto)
1067 } else {
1068 state_updates.push((Nibbles::default(), vec![RLP_NULL]));
1069 *EMPTY_TRIE_HASH
1070 };
1071
1072 let accumulated_updates = accumulator.map(|acc| acc.into_values().collect());
1073
1074 Ok((
1075 AccountUpdatesList {
1076 state_trie_hash,
1077 state_updates,
1078 storage_updates,
1079 code_updates,
1080 },
1081 accumulated_updates,
1082 ))
1083 });
1084
1085 if let Some(err) = watcher_error.lock().expect("watcher mutex poisoned").take() {
1087 return Err(err);
1088 }
1089
1090 result
1091 }
1092
1093 #[instrument(
1103 level = "trace",
1104 name = "Trie update (BAL)",
1105 skip_all,
1106 fields(namespace = "block_execution")
1107 )]
1108 fn handle_merkleization_bal_from_updates(
1109 &self,
1110 prepared: FxHashMap<Address, BalSynthesisItem>,
1111 parent_header: &BlockHeader,
1112 ) -> Result<AccountUpdatesList, StoreError> {
1113 const NUM_WORKERS: usize = 16;
1114 let parent_state_root = parent_header.state_root;
1115
1116 let mut code_updates: Vec<(H256, Code)> = Vec::new();
1120 let mut accounts: Vec<(H256, BalSynthesisItem)> = Vec::with_capacity(prepared.len());
1121 for (addr, item) in prepared {
1122 let hashed = keccak(addr);
1123 if let Some(ch) = item.code_hash
1124 && let Some(ref code) = item.code
1125 {
1126 code_updates.push((ch, code.clone()));
1127 }
1128 accounts.push((hashed, item));
1129 }
1130
1131 let mut work_indices: Vec<(usize, usize)> = accounts
1141 .iter()
1142 .enumerate()
1143 .map(|(i, (_, item))| {
1144 let weight = if !item.added_storage.is_empty() {
1145 1.max(item.added_storage.len())
1146 } else {
1147 0
1148 };
1149 (i, weight)
1150 })
1151 .collect();
1152 work_indices.sort_unstable_by(|a, b| b.1.cmp(&a.1));
1153
1154 let mut bins: Vec<Vec<usize>> = (0..NUM_WORKERS).map(|_| Vec::new()).collect();
1156 let mut bin_weights: Vec<usize> = vec![0; NUM_WORKERS];
1157 for (idx, weight) in work_indices {
1158 let min_bin = bin_weights
1159 .iter()
1160 .enumerate()
1161 .min_by_key(|(_, w)| **w)
1162 .expect("bin_weights is non-empty")
1163 .0;
1164 bins[min_bin].push(idx);
1165 bin_weights[min_bin] += weight;
1166 }
1167
1168 let mut storage_roots: Vec<Option<H256>> = vec![None; accounts.len()];
1170 let mut storage_updates: Vec<(H256, Vec<TrieNode>)> = Vec::new();
1171
1172 std::thread::scope(|s| -> Result<(), StoreError> {
1173 let accounts_ref = &accounts;
1174 let handles: Vec<_> = bins
1175 .into_iter()
1176 .enumerate()
1177 .filter_map(|(worker_id, bin)| {
1178 if bin.is_empty() {
1179 return None;
1180 }
1181 Some(
1182 std::thread::Builder::new()
1183 .name(format!("bal_storage_worker_{worker_id}"))
1184 .spawn_scoped(
1185 s,
1186 move || -> Result<Vec<(usize, H256, Vec<TrieNode>)>, StoreError> {
1187 let mut results: Vec<(usize, H256, Vec<TrieNode>)> = Vec::new();
1188 let state_trie =
1190 self.storage.open_state_trie(parent_state_root)?;
1191 for idx in bin {
1192 let (hashed_address, item) = &accounts_ref[idx];
1193 if item.added_storage.is_empty() {
1194 continue;
1195 }
1196
1197 let storage_root = match state_trie
1198 .get(hashed_address.as_bytes())?
1199 {
1200 Some(rlp) => AccountState::decode(&rlp)?.storage_root,
1201 None => *EMPTY_TRIE_HASH,
1202 };
1203 let mut trie = self.storage.open_storage_trie(
1204 *hashed_address,
1205 parent_state_root,
1206 storage_root,
1207 )?;
1208
1209 let mut hashed_storage: Vec<(H256, U256)> = item
1212 .added_storage
1213 .iter()
1214 .map(|(k, v)| (keccak(k), *v))
1215 .collect();
1216 hashed_storage.sort_unstable_by(|a, b| a.0.cmp(&b.0));
1217 for (hashed_key, value) in &hashed_storage {
1218 if value.is_zero() {
1219 trie.remove(hashed_key.as_bytes())?;
1220 } else {
1221 trie.insert(
1222 hashed_key.as_bytes().to_vec(),
1223 value.encode_to_vec(),
1224 )?;
1225 }
1226 }
1227
1228 let (root_hash, nodes) =
1229 trie.collect_changes_since_last_hash(&NativeCrypto);
1230 results.push((idx, root_hash, nodes));
1231 }
1232 Ok(results)
1233 },
1234 )
1235 .map_err(|e| StoreError::Custom(format!("spawn failed: {e}"))),
1236 )
1237 })
1238 .collect::<Result<Vec<_>, _>>()?;
1239
1240 for handle in handles {
1241 let results = handle
1242 .join()
1243 .map_err(|_| StoreError::Custom("storage worker panicked".to_string()))??;
1244 for (idx, root_hash, nodes) in results {
1245 storage_roots[idx] = Some(root_hash);
1246 storage_updates.push((accounts_ref[idx].0, nodes));
1247 }
1248 }
1249 Ok(())
1250 })?;
1251
1252 let mut shards: Vec<Vec<BalStateWorkItem>> = (0..NUM_WORKERS).map(|_| Vec::new()).collect();
1256 for (idx, (hashed_address, item)) in accounts.iter().enumerate() {
1257 let bucket = (hashed_address.as_fixed_bytes()[0] >> 4) as usize;
1258 shards[bucket].push(BalStateWorkItem {
1259 hashed_address: *hashed_address,
1260 nonce: item.nonce,
1261 balance: item.balance,
1262 code_hash: item.code_hash,
1263 storage_root: storage_roots[idx],
1264 });
1265 }
1266
1267 let mut root = BranchNode::default();
1268 let mut state_updates = Vec::new();
1269
1270 std::thread::scope(|s| -> Result<(), StoreError> {
1275 let handles: Vec<_> = shards
1276 .into_iter()
1277 .enumerate()
1278 .map(|(index, shard_items)| {
1279 std::thread::Builder::new()
1280 .name(format!("bal_state_shard_{index}"))
1281 .spawn_scoped(
1282 s,
1283 move || -> Result<(Box<BranchNode>, Vec<TrieNode>), StoreError> {
1284 let mut state_trie =
1285 self.storage.open_state_trie(parent_state_root)?;
1286
1287 for item in &shard_items {
1288 let path = item.hashed_address.as_bytes();
1289
1290 let mut account_state = match state_trie.get(path)? {
1292 Some(rlp) => {
1293 let state = AccountState::decode(&rlp)?;
1294 state_trie.insert(path.to_vec(), rlp)?;
1299 state
1300 }
1301 None => AccountState::default(),
1302 };
1303
1304 if let Some(n) = item.nonce {
1305 account_state.nonce = n;
1306 }
1307 if let Some(b) = item.balance {
1308 account_state.balance = b;
1309 }
1310 if let Some(ch) = item.code_hash {
1311 account_state.code_hash = ch;
1312 }
1313 if let Some(storage_root) = item.storage_root {
1314 account_state.storage_root = storage_root;
1315 }
1316
1317 if account_state != AccountState::default() {
1320 state_trie
1321 .insert(path.to_vec(), account_state.encode_to_vec())?;
1322 } else {
1323 state_trie.remove(path)?;
1324 }
1325 }
1326
1327 collect_trie(index as u8, state_trie)
1328 .map_err(|e| StoreError::Custom(format!("{e}")))
1329 },
1330 )
1331 .map_err(|e| StoreError::Custom(format!("spawn failed: {e}")))
1332 })
1333 .collect::<Result<Vec<_>, _>>()?;
1334
1335 for (i, handle) in handles.into_iter().enumerate() {
1336 let (subroot, state_nodes) = handle
1337 .join()
1338 .map_err(|_| StoreError::Custom("state shard worker panicked".to_string()))??;
1339 state_updates.extend(state_nodes);
1340 root.choices[i] = subroot.choices[i].clone();
1341 }
1342 Ok(())
1343 })?;
1344
1345 let state_trie_hash =
1347 if let Some(root) = self.collapse_root_node(parent_header, None, root)? {
1348 let mut root = NodeRef::from(root);
1349 let hash = root.commit(Nibbles::default(), &mut state_updates, &NativeCrypto);
1350 let _ = DROP_SENDER.send(Box::new(root));
1351 hash.finalize(&NativeCrypto)
1352 } else {
1353 state_updates.push((Nibbles::default(), vec![RLP_NULL]));
1354 *EMPTY_TRIE_HASH
1355 };
1356
1357 Ok(AccountUpdatesList {
1358 state_trie_hash,
1359 state_updates,
1360 storage_updates,
1361 code_updates,
1362 })
1363 }
1364
1365 fn collapse_root_node(
1366 &self,
1367 parent_header: &BlockHeader,
1368 prefix: Option<H256>,
1369 root: BranchNode,
1370 ) -> Result<Option<Node>, StoreError> {
1371 collapse_root_node(&self.storage, parent_header.state_root, prefix, root)
1372 }
1373
1374 fn execute_block_from_state(
1376 &self,
1377 parent_header: &BlockHeader,
1378 block: &Block,
1379 chain_config: &ChainConfig,
1380 vm: &mut Evm,
1381 ) -> Result<BlockExecutionResult, ChainError> {
1382 validate_block_pre_execution(block, parent_header, chain_config, ELASTICITY_MULTIPLIER)?;
1384 self.validate_l1_transaction_types(block)?;
1385 let (execution_result, bal) = vm.execute_block(block)?;
1386 if let Err(e) = validate_gas_used(execution_result.block_gas_used, &block.header) {
1388 ethrex_vm::log_gas_used_mismatch(
1389 &execution_result.tx_gas_breakdowns,
1390 block.header.number,
1391 execution_result.block_gas_used,
1392 block.header.gas_used,
1393 );
1394 return Err(e.into());
1395 }
1396 validate_receipts_root_and_logs_bloom(
1397 &block.header,
1398 &execution_result.receipts,
1399 &NativeCrypto,
1400 )?;
1401 validate_requests_hash(&block.header, chain_config, &execution_result.requests)?;
1402 if let Some(bal) = &bal {
1403 validate_block_access_list_hash(
1404 &block.header,
1405 chain_config,
1406 bal,
1407 block.body.transactions.len(),
1408 &NativeCrypto,
1409 )?;
1410 }
1411
1412 Ok(execution_result)
1413 }
1414
1415 pub async fn generate_witness_for_blocks(
1416 &self,
1417 blocks: &[Block],
1418 ) -> Result<ExecutionWitness, ChainError> {
1419 self.generate_witness_for_blocks_with_fee_configs(blocks, None)
1420 .await
1421 }
1422
1423 pub async fn generate_witness_for_blocks_with_fee_configs(
1424 &self,
1425 blocks: &[Block],
1426 fee_configs: Option<&[FeeConfig]>,
1427 ) -> Result<ExecutionWitness, ChainError> {
1428 let first_block_header = &blocks
1429 .first()
1430 .ok_or(ChainError::WitnessGeneration(
1431 "Empty block batch".to_string(),
1432 ))?
1433 .header;
1434
1435 let trie = self
1437 .storage
1438 .state_trie(first_block_header.parent_hash)
1439 .map_err(|_| ChainError::ParentStateNotFound)?
1440 .ok_or(ChainError::ParentStateNotFound)?;
1441 let initial_state_root = trie.hash_no_commit(&NativeCrypto);
1442
1443 let (mut current_trie_witness, mut trie) = TrieLogger::open_trie(trie);
1444
1445 let mut accumulated_state_trie_witness = current_trie_witness
1449 .lock()
1450 .map_err(|_| {
1451 ChainError::WitnessGeneration("Failed to lock state trie witness".to_string())
1452 })?
1453 .clone();
1454
1455 let mut touched_account_storage_slots = BTreeMap::new();
1456 let mut used_trie_nodes = Vec::new();
1458
1459 let root_node = trie.root_node().map_err(|_| {
1461 ChainError::WitnessGeneration("Failed to get root state node".to_string())
1462 })?;
1463
1464 let mut blockhash_opcode_references = HashMap::new();
1465 let mut codes = Vec::new();
1466
1467 for (i, block) in blocks.iter().enumerate() {
1468 let parent_hash = block.header.parent_hash;
1469 let parent_header = self
1470 .storage
1471 .get_block_header_by_hash(parent_hash)
1472 .map_err(ChainError::StoreError)?
1473 .ok_or(ChainError::ParentNotFound)?;
1474
1475 let vm_db: DynVmDatabase =
1481 Box::new(StoreVmDatabase::new(self.storage.clone(), parent_header)?);
1482
1483 let logger = Arc::new(DatabaseLogger::new(Arc::new(vm_db)));
1484
1485 let mut vm = match self.options.r#type {
1486 BlockchainType::L1 => {
1487 Evm::new_from_db_for_l1(logger.clone(), Arc::new(NativeCrypto))
1488 }
1489 BlockchainType::L2(_) => {
1490 let l2_config = match fee_configs {
1491 Some(fee_configs) => {
1492 fee_configs.get(i).ok_or(ChainError::WitnessGeneration(
1493 "FeeConfig not found for witness generation".to_string(),
1494 ))?
1495 }
1496 None => Err(ChainError::WitnessGeneration(
1497 "L2Config not found for witness generation".to_string(),
1498 ))?,
1499 };
1500 Evm::new_from_db_for_l2(logger.clone(), *l2_config, Arc::new(NativeCrypto))
1501 }
1502 };
1503
1504 let (execution_result, _bal) = vm.execute_block(block)?;
1506
1507 let account_updates = vm.get_state_transitions()?;
1509
1510 let mut state_accessed = logger
1511 .state_accessed
1512 .lock()
1513 .map_err(|_e| {
1514 ChainError::WitnessGeneration("Failed to execute with witness".to_string())
1515 })?
1516 .clone();
1517
1518 for keys in state_accessed.values_mut() {
1520 let mut seen = HashSet::new();
1521 keys.retain(|k| seen.insert(*k));
1522 }
1523
1524 for (account, acc_keys) in state_accessed.iter() {
1525 let slots: &mut Vec<H256> =
1526 touched_account_storage_slots.entry(*account).or_default();
1527 slots.extend(acc_keys.iter().copied());
1528 }
1529
1530 let logger_block_hashes = logger
1532 .block_hashes_accessed
1533 .lock()
1534 .map_err(|_e| {
1535 ChainError::WitnessGeneration("Failed to get block hashes".to_string())
1536 })?
1537 .clone();
1538
1539 blockhash_opcode_references.extend(logger_block_hashes);
1540
1541 if let Some(withdrawals) = block.body.withdrawals.as_ref() {
1543 for withdrawal in withdrawals {
1544 trie.get(&hash_address(&withdrawal.address)).map_err(|_e| {
1545 ChainError::Custom("Failed to access account from trie".to_string())
1546 })?;
1547 }
1548 }
1549
1550 let mut used_storage_tries = HashMap::new();
1551
1552 for (account, acc_keys) in state_accessed.iter() {
1555 trie.get(&hash_address(account)).map_err(|_e| {
1557 ChainError::WitnessGeneration("Failed to access account from trie".to_string())
1558 })?;
1559 if !acc_keys.is_empty()
1561 && let Ok(Some(storage_trie)) = self.storage.storage_trie(parent_hash, *account)
1562 {
1563 let (storage_trie_witness, storage_trie) = TrieLogger::open_trie(storage_trie);
1564 for storage_key in acc_keys {
1566 let hashed_key = hash_key(storage_key);
1567 storage_trie.get(&hashed_key).map_err(|_e| {
1568 ChainError::WitnessGeneration(
1569 "Failed to access storage key".to_string(),
1570 )
1571 })?;
1572 }
1573 used_storage_tries.insert(*account, (storage_trie_witness, storage_trie));
1575 }
1576 }
1577
1578 for code_hash in logger
1580 .code_accessed
1581 .lock()
1582 .map_err(|_e| {
1583 ChainError::WitnessGeneration("Failed to gather used bytecodes".to_string())
1584 })?
1585 .iter()
1586 {
1587 let code = self
1588 .storage
1589 .get_account_code(*code_hash)
1590 .map_err(|_e| {
1591 ChainError::WitnessGeneration("Failed to get account code".to_string())
1592 })?
1593 .ok_or(ChainError::WitnessGeneration(
1594 "Failed to get account code".to_string(),
1595 ))?;
1596 codes.push(code.code().to_vec());
1597 }
1598
1599 let (storage_tries_after_update, account_updates_list) =
1601 self.storage.apply_account_updates_from_trie_with_witness(
1602 trie,
1603 &account_updates,
1604 used_storage_tries,
1605 )?;
1606
1607 self.store_block(block.clone(), account_updates_list, execution_result)?;
1611
1612 for (address, (witness, _storage_trie)) in storage_tries_after_update {
1613 let mut witness = witness.lock().map_err(|_| {
1614 ChainError::WitnessGeneration("Failed to lock storage trie witness".to_string())
1615 })?;
1616 let witness = std::mem::take(&mut *witness);
1617 let witness = witness.into_values().collect::<Vec<_>>();
1618 used_trie_nodes.extend_from_slice(&witness);
1619 touched_account_storage_slots.entry(address).or_default();
1620 }
1621
1622 let (new_state_trie_witness, updated_trie) = TrieLogger::open_trie(
1623 self.storage
1624 .state_trie(block.header.hash())
1625 .map_err(|_| ChainError::ParentStateNotFound)?
1626 .ok_or(ChainError::ParentStateNotFound)?,
1627 );
1628
1629 trie = updated_trie;
1631
1632 for state_trie_witness in current_trie_witness
1633 .lock()
1634 .map_err(|_| {
1635 ChainError::WitnessGeneration("Failed to lock state trie witness".to_string())
1636 })?
1637 .iter()
1638 {
1639 accumulated_state_trie_witness
1640 .insert(*state_trie_witness.0, state_trie_witness.1.clone());
1641 }
1642
1643 current_trie_witness = new_state_trie_witness;
1644 }
1645
1646 used_trie_nodes.extend_from_slice(&Vec::from_iter(
1647 accumulated_state_trie_witness.into_values(),
1648 ));
1649
1650 if used_trie_nodes.is_empty()
1652 && let Some(root) = root_node
1653 {
1654 used_trie_nodes.push((*root).clone());
1655 }
1656
1657 let mut block_headers_bytes = Vec::new();
1659
1660 let first_blockhash_opcode_number = blockhash_opcode_references.keys().min();
1661 let first_needed_block_hash = first_blockhash_opcode_number
1662 .and_then(|n| {
1663 (*n < first_block_header.number.saturating_sub(1))
1664 .then(|| blockhash_opcode_references.get(n))?
1665 .copied()
1666 })
1667 .unwrap_or(first_block_header.parent_hash);
1668
1669 let mut current_header = blocks
1671 .last()
1672 .ok_or_else(|| ChainError::WitnessGeneration("Empty batch".to_string()))?
1673 .header
1674 .clone();
1675
1676 while current_header.hash() != first_needed_block_hash {
1679 let parent_hash = current_header.parent_hash;
1680 let current_number = current_header.number - 1;
1681
1682 current_header = self
1683 .storage
1684 .get_block_header_by_hash(parent_hash)?
1685 .ok_or_else(|| {
1686 ChainError::WitnessGeneration(format!(
1687 "Failed to get block {current_number} header"
1688 ))
1689 })?;
1690
1691 block_headers_bytes.push(current_header.encode_to_vec());
1692 }
1693
1694 let nodes: BTreeMap<H256, Node> = used_trie_nodes
1696 .into_iter()
1697 .map(|node| {
1698 (
1699 node.compute_hash(&NativeCrypto).finalize(&NativeCrypto),
1700 node,
1701 )
1702 })
1703 .collect();
1704 let state_trie_root = if let NodeRef::Node(state_trie_root, _) =
1705 Trie::get_embedded_root(&nodes, initial_state_root)?
1706 {
1707 Some((*state_trie_root).clone())
1708 } else {
1709 None
1710 };
1711
1712 let state_trie = if let Some(state_trie_root) = &state_trie_root {
1714 Trie::new_temp_with_root(state_trie_root.clone().into())
1715 } else {
1716 Trie::new_temp()
1717 };
1718 let mut storage_trie_roots = BTreeMap::new();
1719 for address in touched_account_storage_slots.keys() {
1720 let hashed_address = hash_address(address);
1721 let hashed_address_h256 = H256::from_slice(&hashed_address);
1722 let Some(encoded_account) = state_trie.get(&hashed_address)? else {
1723 continue; };
1725 let storage_root_hash = AccountState::decode(&encoded_account)?.storage_root;
1726 if storage_root_hash == *EMPTY_TRIE_HASH {
1727 continue; }
1729 if !nodes.contains_key(&storage_root_hash) {
1730 continue; }
1732 let node = Trie::get_embedded_root(&nodes, storage_root_hash)?;
1733 let NodeRef::Node(node, _) = node else {
1734 return Err(ChainError::Custom(
1735 "execution witness does not contain non-empty storage trie".to_string(),
1736 ));
1737 };
1738 storage_trie_roots.insert(hashed_address_h256, (*node).clone());
1739 }
1740
1741 Ok(ExecutionWitness {
1742 codes,
1743 block_headers_bytes,
1744 first_block_number: first_block_header.number,
1745 chain_config: self.storage.get_chain_config(),
1746 state_trie_root,
1747 storage_trie_roots,
1748 })
1749 }
1750
1751 pub fn generate_witness_from_account_updates(
1752 &self,
1753 account_updates: Vec<AccountUpdate>,
1754 block: &Block,
1755 parent_header: BlockHeader,
1756 logger: &DatabaseLogger,
1757 ) -> Result<ExecutionWitness, ChainError> {
1758 let trie = self
1760 .storage
1761 .state_trie(parent_header.hash())
1762 .map_err(|_| ChainError::ParentStateNotFound)?
1763 .ok_or(ChainError::ParentStateNotFound)?;
1764 let initial_state_root = trie.hash_no_commit(&NativeCrypto);
1765
1766 let (trie_witness, trie) = TrieLogger::open_trie(trie);
1767
1768 let mut touched_account_storage_slots = BTreeMap::new();
1769 let mut used_trie_nodes = Vec::new();
1771
1772 let root_node = trie.root_node().map_err(|_| {
1774 ChainError::WitnessGeneration("Failed to get root state node".to_string())
1775 })?;
1776
1777 let mut codes = Vec::new();
1778
1779 for account_update in &account_updates {
1780 touched_account_storage_slots.insert(
1781 account_update.address,
1782 account_update
1783 .added_storage
1784 .keys()
1785 .cloned()
1786 .collect::<Vec<H256>>(),
1787 );
1788 }
1789
1790 let blockhash_opcode_references = logger
1792 .block_hashes_accessed
1793 .lock()
1794 .map_err(|_e| ChainError::WitnessGeneration("Failed to get block hashes".to_string()))?
1795 .clone();
1796
1797 if let Some(withdrawals) = block.body.withdrawals.as_ref() {
1799 for withdrawal in withdrawals {
1800 trie.get(&hash_address(&withdrawal.address)).map_err(|_e| {
1801 ChainError::Custom("Failed to access account from trie".to_string())
1802 })?;
1803 }
1804 }
1805
1806 let mut used_storage_tries = HashMap::new();
1807
1808 for (account, acc_keys) in logger
1811 .state_accessed
1812 .lock()
1813 .map_err(|_e| {
1814 ChainError::WitnessGeneration("Failed to execute with witness".to_string())
1815 })?
1816 .iter()
1817 {
1818 trie.get(&hash_address(account)).map_err(|_e| {
1820 ChainError::WitnessGeneration("Failed to access account from trie".to_string())
1821 })?;
1822 if !acc_keys.is_empty()
1824 && let Ok(Some(storage_trie)) =
1825 self.storage.storage_trie(parent_header.hash(), *account)
1826 {
1827 let (storage_trie_witness, storage_trie) = TrieLogger::open_trie(storage_trie);
1828 for storage_key in acc_keys {
1830 let hashed_key = hash_key(storage_key);
1831 storage_trie.get(&hashed_key).map_err(|_e| {
1832 ChainError::WitnessGeneration("Failed to access storage key".to_string())
1833 })?;
1834 }
1835 used_storage_tries.insert(*account, (storage_trie_witness, storage_trie));
1837 }
1838 }
1839
1840 for code_hash in logger
1842 .code_accessed
1843 .lock()
1844 .map_err(|_e| {
1845 ChainError::WitnessGeneration("Failed to gather used bytecodes".to_string())
1846 })?
1847 .iter()
1848 {
1849 let code = self
1850 .storage
1851 .get_account_code(*code_hash)
1852 .map_err(|_e| {
1853 ChainError::WitnessGeneration("Failed to get account code".to_string())
1854 })?
1855 .ok_or(ChainError::WitnessGeneration(
1856 "Failed to get account code".to_string(),
1857 ))?;
1858 codes.push(code.code().to_vec());
1859 }
1860
1861 let (storage_tries_after_update, _account_updates_list) =
1863 self.storage.apply_account_updates_from_trie_with_witness(
1864 trie,
1865 &account_updates,
1866 used_storage_tries,
1867 )?;
1868
1869 for (address, (witness, _storage_trie)) in storage_tries_after_update {
1870 let mut witness = witness.lock().map_err(|_| {
1871 ChainError::WitnessGeneration("Failed to lock storage trie witness".to_string())
1872 })?;
1873 let witness = std::mem::take(&mut *witness);
1874 let witness = witness.into_values().collect::<Vec<_>>();
1875 used_trie_nodes.extend_from_slice(&witness);
1876 touched_account_storage_slots.entry(address).or_default();
1877 }
1878
1879 used_trie_nodes.extend_from_slice(&Vec::from_iter(
1880 trie_witness
1881 .lock()
1882 .map_err(|_| {
1883 ChainError::WitnessGeneration("Failed to lock state trie witness".to_string())
1884 })?
1885 .clone()
1886 .into_values(),
1887 ));
1888
1889 if used_trie_nodes.is_empty()
1891 && let Some(root) = root_node
1892 {
1893 used_trie_nodes.push((*root).clone());
1894 }
1895
1896 let mut block_headers_bytes = Vec::new();
1898
1899 let first_blockhash_opcode_number = blockhash_opcode_references.keys().min();
1900 let first_needed_block_hash = first_blockhash_opcode_number
1901 .and_then(|n| {
1902 (*n < block.header.number.saturating_sub(1))
1903 .then(|| blockhash_opcode_references.get(n))?
1904 .copied()
1905 })
1906 .unwrap_or(block.header.parent_hash);
1907
1908 let mut current_header = block.header.clone();
1909
1910 while current_header.hash() != first_needed_block_hash {
1913 let parent_hash = current_header.parent_hash;
1914 let current_number = current_header.number - 1;
1915
1916 current_header = self
1917 .storage
1918 .get_block_header_by_hash(parent_hash)?
1919 .ok_or_else(|| {
1920 ChainError::WitnessGeneration(format!(
1921 "Failed to get block {current_number} header"
1922 ))
1923 })?;
1924
1925 block_headers_bytes.push(current_header.encode_to_vec());
1926 }
1927
1928 let nodes: BTreeMap<H256, Node> = used_trie_nodes
1930 .into_iter()
1931 .map(|node| {
1932 (
1933 node.compute_hash(&NativeCrypto).finalize(&NativeCrypto),
1934 node,
1935 )
1936 })
1937 .collect();
1938 let state_trie_root = if let NodeRef::Node(state_trie_root, _) =
1939 Trie::get_embedded_root(&nodes, initial_state_root)?
1940 {
1941 Some((*state_trie_root).clone())
1942 } else {
1943 None
1944 };
1945
1946 let state_trie = if let Some(state_trie_root) = &state_trie_root {
1948 Trie::new_temp_with_root(state_trie_root.clone().into())
1949 } else {
1950 Trie::new_temp()
1951 };
1952 let mut storage_trie_roots = BTreeMap::new();
1953 for address in touched_account_storage_slots.keys() {
1954 let hashed_address = hash_address(address);
1955 let hashed_address_h256 = H256::from_slice(&hashed_address);
1956 let Some(encoded_account) = state_trie.get(&hashed_address)? else {
1957 continue; };
1959 let storage_root_hash = AccountState::decode(&encoded_account)?.storage_root;
1960 if storage_root_hash == *EMPTY_TRIE_HASH {
1961 continue; }
1963 if !nodes.contains_key(&storage_root_hash) {
1964 continue; }
1966 let node = Trie::get_embedded_root(&nodes, storage_root_hash)?;
1967 let NodeRef::Node(node, _) = node else {
1968 return Err(ChainError::Custom(
1969 "execution witness does not contain non-empty storage trie".to_string(),
1970 ));
1971 };
1972 storage_trie_roots.insert(hashed_address_h256, (*node).clone());
1973 }
1974
1975 Ok(ExecutionWitness {
1976 codes,
1977 block_headers_bytes,
1978 first_block_number: parent_header.number,
1979 chain_config: self.storage.get_chain_config(),
1980 state_trie_root,
1981 storage_trie_roots,
1982 })
1983 }
1984
1985 #[instrument(
1986 level = "trace",
1987 name = "Block DB update",
1988 skip_all,
1989 fields(namespace = "block_execution")
1990 )]
1991 pub fn store_block(
1992 &self,
1993 block: Block,
1994 account_updates_list: AccountUpdatesList,
1995 execution_result: BlockExecutionResult,
1996 ) -> Result<(), ChainError> {
1997 validate_state_root(&block.header, account_updates_list.state_trie_hash)?;
1999
2000 let update_batch = UpdateBatch {
2001 account_updates: account_updates_list.state_updates,
2002 storage_updates: account_updates_list.storage_updates,
2003 receipts: vec![(block.hash(), execution_result.receipts)],
2004 blocks: vec![block],
2005 code_updates: account_updates_list.code_updates,
2006 batch_mode: false,
2007 };
2008
2009 self.storage
2010 .store_block_updates(update_batch)
2011 .map_err(|e| e.into())
2012 }
2013
2014 pub fn add_block(&self, block: Block) -> Result<(), ChainError> {
2015 let since = Instant::now();
2016 let (res, updates) = self.execute_block(&block)?;
2017 let executed = Instant::now();
2018
2019 let account_updates_list = self
2021 .storage
2022 .apply_account_updates_batch(block.header.parent_hash, &updates)?
2023 .ok_or(ChainError::ParentStateNotFound)?;
2024
2025 let (gas_used, gas_limit, block_number, transactions_count) = (
2026 block.header.gas_used,
2027 block.header.gas_limit,
2028 block.header.number,
2029 block.body.transactions.len(),
2030 );
2031
2032 let merkleized = Instant::now();
2033 let result = self.store_block(block, account_updates_list, res);
2034 let stored = Instant::now();
2035
2036 if self.options.perf_logs_enabled {
2037 Self::print_add_block_logs(
2038 gas_used,
2039 gas_limit,
2040 block_number,
2041 transactions_count,
2042 since,
2043 executed,
2044 merkleized,
2045 stored,
2046 );
2047 }
2048 result
2049 }
2050
2051 pub fn add_block_pipeline(
2052 &self,
2053 block: Block,
2054 bal: Option<&BlockAccessList>,
2055 ) -> Result<(), ChainError> {
2056 let (_, _, result) = self.add_block_pipeline_inner(block, bal, false)?;
2057 result
2058 }
2059
2060 pub fn add_block_pipeline_bal(
2067 &self,
2068 block: Block,
2069 bal: Option<&BlockAccessList>,
2070 ) -> Result<Option<BlockAccessList>, ChainError> {
2071 let (produced_bal, _, result) = self.add_block_pipeline_inner(block, bal, false)?;
2072 result?;
2073 Ok(produced_bal)
2074 }
2075
2076 pub fn add_block_pipeline_with_witness(
2079 &self,
2080 block: Block,
2081 bal: Option<&BlockAccessList>,
2082 ) -> Result<ExecutionWitness, ChainError> {
2083 let (_, witness, result) = self.add_block_pipeline_inner(block, bal, true)?;
2084 result?;
2085 witness.ok_or_else(|| {
2086 ChainError::WitnessGeneration(
2087 "forced witness collection completed without producing a witness".to_string(),
2088 )
2089 })
2090 }
2091
2092 fn add_block_pipeline_inner(
2101 &self,
2102 block: Block,
2103 bal: Option<&BlockAccessList>,
2104 force_witness: bool,
2105 ) -> Result<AddBlockPipelineInnerResult, ChainError> {
2106 let Ok(parent_header) = find_parent_header(&block.header, &self.storage) else {
2108 self.storage.add_pending_block(block)?;
2110 return Err(ChainError::ParentNotFound);
2111 };
2112
2113 let should_store_witness = self.options.precompute_witnesses && self.is_synced();
2114 let collect_witness = should_store_witness || force_witness;
2115
2116 let (mut vm, logger) = if collect_witness {
2117 let vm_db: DynVmDatabase = Box::new(StoreVmDatabase::new(
2121 self.storage.clone(),
2122 parent_header.clone(),
2123 )?);
2124
2125 let logger = Arc::new(DatabaseLogger::new(Arc::new(vm_db)));
2126
2127 let vm = match self.options.r#type.clone() {
2128 BlockchainType::L1 => {
2129 Evm::new_from_db_for_l1(logger.clone(), Arc::new(NativeCrypto))
2130 }
2131 BlockchainType::L2(l2_config) => Evm::new_from_db_for_l2(
2132 logger.clone(),
2133 *l2_config.fee_config.read().map_err(|_| {
2134 EvmError::Custom("Fee config lock was poisoned".to_string())
2135 })?,
2136 Arc::new(NativeCrypto),
2137 ),
2138 };
2139 (vm, Some(logger))
2140 } else {
2141 let vm_db = StoreVmDatabase::new(self.storage.clone(), parent_header.clone())?;
2142 let vm = self.new_evm(vm_db)?;
2143 (vm, None)
2144 };
2145
2146 let (
2147 res,
2148 account_updates_list,
2149 accumulated_updates,
2150 produced_bal,
2151 merkle_queue_length,
2152 instants,
2153 warmer_duration,
2154 ) = { self.execute_block_pipeline(&block, &parent_header, &mut vm, bal, collect_witness)? };
2155
2156 let (gas_used, gas_limit, block_number, transactions_count) = (
2157 block.header.gas_used,
2158 block.header.gas_limit,
2159 block.header.number,
2160 block.body.transactions.len(),
2161 );
2162 let block_hash = block.hash();
2163
2164 let mut witness = None;
2165 if let Some(logger) = logger
2166 && let Some(account_updates) = accumulated_updates
2167 {
2168 let block_hash = block.hash();
2169 let generated_witness = self.generate_witness_from_account_updates(
2170 account_updates,
2171 &block,
2172 parent_header,
2173 &logger,
2174 )?;
2175 match (should_store_witness, force_witness) {
2176 (true, true) => {
2177 witness = Some(generated_witness.clone());
2178 self.storage
2179 .store_witness(block_hash, block_number, generated_witness)?;
2180 }
2181 (true, false) => {
2182 self.storage
2183 .store_witness(block_hash, block_number, generated_witness)?;
2184 }
2185 (false, true) => {
2186 witness = Some(generated_witness);
2187 }
2188 (false, false) => {}
2189 }
2190 };
2191
2192 if let Some(bal) = produced_bal.as_ref().or(bal)
2197 && let Err(err) = self.storage.store_block_access_list(block_hash, bal)
2198 {
2199 warn!("Failed to store block access list for block {block_hash}: {err}");
2200 }
2201
2202 let result = self.store_block(block, account_updates_list, res);
2203
2204 let stored = Instant::now();
2205
2206 let instants = std::array::from_fn(move |i| {
2207 if i < instants.len() {
2208 instants[i]
2209 } else {
2210 stored
2211 }
2212 });
2213
2214 if self.options.perf_logs_enabled {
2215 Self::print_add_block_pipeline_logs(
2216 gas_used,
2217 gas_limit,
2218 block_number,
2219 block_hash,
2220 transactions_count,
2221 merkle_queue_length,
2222 warmer_duration,
2223 instants,
2224 );
2225 }
2226
2227 metrics!(if let Some(bal_ref) = produced_bal.as_ref().or(bal) {
2228 let account_count = bal_ref.accounts().len() as u64;
2229 let slot_count = bal_ref.item_count().saturating_sub(account_count);
2230 let size_bytes = bal_ref.length() as f64;
2231 METRICS_BAL.blocks_total.inc();
2232 METRICS_BAL.size_bytes.set(size_bytes);
2233 METRICS_BAL.size_bytes_histogram.observe(size_bytes);
2234 METRICS_BAL.account_count.set(account_count as i64);
2235 METRICS_BAL.slot_count.set(slot_count as i64);
2236 });
2237
2238 Ok((produced_bal, witness, result))
2239 }
2240
2241 #[allow(clippy::too_many_arguments)]
2242 fn print_add_block_logs(
2243 gas_used: u64,
2244 gas_limit: u64,
2245 block_number: u64,
2246 transactions_count: usize,
2247 since: Instant,
2248 executed: Instant,
2249 merkleized: Instant,
2250 stored: Instant,
2251 ) {
2252 let interval = stored.duration_since(since).as_millis() as f64;
2253 if interval != 0f64 {
2254 let as_gigas = gas_used as f64 / 10_f64.powf(9_f64);
2255 let throughput = as_gigas / interval * 1000_f64;
2256
2257 metrics!(
2258 METRICS_BLOCKS.set_block_number(block_number);
2259 METRICS_BLOCKS.set_latest_gas_used(gas_used as f64);
2260 METRICS_BLOCKS.set_latest_block_gas_limit(gas_limit as f64);
2261 METRICS_BLOCKS.set_latest_gigagas(throughput);
2262 METRICS_BLOCKS.set_execution_ms(executed.duration_since(since).as_secs_f64() * 1000.0);
2263 METRICS_BLOCKS.set_merkle_ms(merkleized.duration_since(executed).as_secs_f64() * 1000.0);
2264 METRICS_BLOCKS.set_store_ms(stored.duration_since(merkleized).as_secs_f64() * 1000.0);
2265 METRICS_BLOCKS.set_transaction_count(transactions_count as i64);
2266 );
2267
2268 let base_log = format!(
2269 "[METRIC] BLOCK EXECUTION THROUGHPUT ({}): {:.3} Ggas/s TIME SPENT: {:.0} ms. Gas Used: {:.3} ({:.0}%), #Txs: {}.",
2270 block_number,
2271 throughput,
2272 interval,
2273 as_gigas,
2274 (gas_used as f64 / gas_limit as f64) * 100.0,
2275 transactions_count
2276 );
2277
2278 fn percentage(init: Instant, end: Instant, total: f64) -> f64 {
2279 (end.duration_since(init).as_millis() as f64 / total * 100.0).round()
2280 }
2281 let extra_log = if as_gigas > 0.0 {
2282 format!(
2283 " exec: {}% merkle: {}% store: {}%",
2284 percentage(since, executed, interval),
2285 percentage(executed, merkleized, interval),
2286 percentage(merkleized, stored, interval)
2287 )
2288 } else {
2289 "".to_string()
2290 };
2291 info!("{}{}", base_log, extra_log);
2292 }
2293 }
2294
2295 #[allow(clippy::too_many_arguments)]
2296 fn print_add_block_pipeline_logs(
2297 gas_used: u64,
2298 gas_limit: u64,
2299 block_number: u64,
2300 block_hash: H256,
2301 transactions_count: usize,
2302 merkle_queue_length: usize,
2303 warmer_duration: Duration,
2304 [
2305 start_instant,
2306 block_validated_instant,
2307 exec_merkle_start,
2308 merkle_start_instant,
2309 exec_end_instant,
2310 merkle_end_instant,
2311 exec_merkle_end_instant,
2312 stored_instant,
2313 ]: [Instant; 8],
2314 ) {
2315 let total_ms = stored_instant.duration_since(start_instant).as_secs_f64() * 1000.0;
2316 if total_ms == 0.0 {
2317 return;
2318 }
2319
2320 let as_mgas = gas_used as f64 / 1e6;
2321 let throughput = (gas_used as f64 / 1e9) / (total_ms / 1000.0);
2322
2323 let validate_ms = block_validated_instant
2325 .duration_since(start_instant)
2326 .as_secs_f64()
2327 * 1000.0;
2328 let exec_ms = exec_end_instant
2329 .duration_since(exec_merkle_start)
2330 .as_secs_f64()
2331 * 1000.0;
2332 let store_ms = stored_instant
2333 .duration_since(exec_merkle_end_instant)
2334 .as_secs_f64()
2335 * 1000.0;
2336 let warmer_ms = warmer_duration.as_secs_f64() * 1000.0;
2337
2338 let _merkle_total_ms = exec_merkle_end_instant
2342 .duration_since(exec_merkle_start)
2343 .as_secs_f64()
2344 * 1000.0;
2345
2346 let merkle_concurrent_ms = (merkle_end_instant
2348 .duration_since(exec_merkle_start)
2349 .as_secs_f64()
2350 * 1000.0)
2351 .min(exec_ms);
2352
2353 let merkle_drain_ms = exec_merkle_end_instant
2355 .saturating_duration_since(exec_end_instant)
2356 .as_secs_f64()
2357 * 1000.0;
2358
2359 let actual_merkle_ms = merkle_concurrent_ms + merkle_drain_ms;
2361 let overlap_pct = if actual_merkle_ms > 0.0 {
2362 (merkle_concurrent_ms / actual_merkle_ms) * 100.0
2363 } else {
2364 0.0
2365 };
2366
2367 let warmer_early_ms = exec_ms - warmer_ms;
2369
2370 let phases = [
2373 ("validate", validate_ms),
2374 ("exec", exec_ms),
2375 ("merkle", merkle_drain_ms),
2376 ("store", store_ms),
2377 ];
2378 let bottleneck = phases
2379 .iter()
2380 .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
2381 .map(|(name, _)| *name)
2382 .unwrap_or("exec");
2383
2384 let pct = |ms: f64| (ms / total_ms * 100.0).round() as u64;
2386
2387 let header = format!(
2389 "[METRIC] BLOCK {} {:#x} | {:.3} Ggas/s | {:.2} ms | {} txs | {:.0} Mgas ({}%)",
2390 block_number,
2391 block_hash,
2392 throughput,
2393 total_ms,
2394 transactions_count,
2395 as_mgas,
2396 (gas_used as f64 / gas_limit as f64 * 100.0).round() as u64
2397 );
2398
2399 let bottleneck_marker = |name: &str| {
2400 if name == bottleneck {
2401 " << BOTTLENECK"
2402 } else {
2403 ""
2404 }
2405 };
2406
2407 let warmer_relation = if warmer_early_ms >= 0.0 {
2408 "before exec"
2409 } else {
2410 "after exec"
2411 };
2412
2413 let merkle_start_delay_ms = merkle_start_instant
2414 .duration_since(exec_merkle_start)
2415 .as_secs_f64()
2416 * 1000.0;
2417
2418 info!("{}", header);
2419 info!(
2420 " |- validate: {:>7.2} ms ({:>2}%){}",
2421 validate_ms,
2422 pct(validate_ms),
2423 bottleneck_marker("validate")
2424 );
2425 info!(
2426 " |- exec: {:>7.2} ms ({:>2}%){}",
2427 exec_ms,
2428 pct(exec_ms),
2429 bottleneck_marker("exec")
2430 );
2431 info!(
2432 " |- merkle: {:>7.2} ms ({:>2}%){} [concurrent: {:.2} ms, drain: {:.2} ms, overlap: {:.0}%, queue: {}, start_delay: {:.2} ms]",
2433 merkle_drain_ms,
2434 pct(merkle_drain_ms),
2435 bottleneck_marker("merkle"),
2436 merkle_concurrent_ms,
2437 merkle_drain_ms,
2438 overlap_pct,
2439 merkle_queue_length,
2440 merkle_start_delay_ms,
2441 );
2442 info!(
2443 " |- store: {:>7.2} ms ({:>2}%){}",
2444 store_ms,
2445 pct(store_ms),
2446 bottleneck_marker("store")
2447 );
2448 info!(
2449 " `- warmer: {:>7.2} ms [finished: {:.2} ms {}]",
2450 warmer_ms,
2451 warmer_early_ms.abs(),
2452 warmer_relation,
2453 );
2454
2455 metrics!(
2457 METRICS_BLOCKS.set_block_number(block_number);
2458 METRICS_BLOCKS.set_latest_gas_used(gas_used as f64);
2459 METRICS_BLOCKS.set_latest_block_gas_limit(gas_limit as f64);
2460 METRICS_BLOCKS.set_latest_gigagas(throughput);
2461 METRICS_BLOCKS.set_transaction_count(transactions_count as i64);
2462 METRICS_BLOCKS.set_validate_ms(validate_ms);
2463 METRICS_BLOCKS.set_execution_ms(exec_ms);
2464 METRICS_BLOCKS.set_merkle_concurrent_ms(merkle_concurrent_ms);
2465 METRICS_BLOCKS.set_merkle_drain_ms(merkle_drain_ms);
2466 METRICS_BLOCKS.set_merkle_ms(_merkle_total_ms);
2467 METRICS_BLOCKS.set_merkle_overlap_pct(overlap_pct);
2468 METRICS_BLOCKS.set_store_ms(store_ms);
2469 METRICS_BLOCKS.set_warmer_ms(warmer_ms);
2470 METRICS_BLOCKS.set_warmer_early_ms(warmer_early_ms);
2471 );
2472 }
2473
2474 pub async fn add_blocks_in_batch(
2486 &self,
2487 blocks: Vec<Block>,
2488 bals: &[Option<BlockAccessList>],
2489 cancellation_token: CancellationToken,
2490 ) -> Result<(), (ChainError, Option<BatchBlockProcessingFailure>)> {
2491 let mut last_valid_hash = H256::default();
2492
2493 debug_assert!(
2497 bals.is_empty() || bals.len() == blocks.len(),
2498 "bals must be empty or aligned with blocks (bals={}, blocks={})",
2499 bals.len(),
2500 blocks.len(),
2501 );
2502
2503 let Some(first_block_header) = blocks.first().map(|e| e.header.clone()) else {
2504 return Err((ChainError::Custom("First block not found".into()), None));
2505 };
2506
2507 let chain_config: ChainConfig = self.storage.get_chain_config();
2508
2509 let mut block_hash_cache: BTreeMap<BlockNumber, BlockHash> =
2512 blocks.iter().map(|b| (b.header.number, b.hash())).collect();
2513
2514 let parent_header = self
2515 .storage
2516 .get_block_header_by_hash(first_block_header.parent_hash)
2517 .map_err(|e| (ChainError::StoreError(e), None))?
2518 .ok_or((ChainError::ParentNotFound, None))?;
2519
2520 block_hash_cache
2524 .entry(parent_header.number)
2525 .or_insert_with(|| parent_header.hash());
2526 let mut hash = parent_header.parent_hash;
2527 let mut number = parent_header.number.saturating_sub(1);
2528 let lookback = first_block_header.number.saturating_sub(256);
2529 while number > lookback {
2530 block_hash_cache.entry(number).or_insert(hash);
2531 match self.storage.get_block_header_by_hash(hash) {
2532 Ok(Some(header)) => {
2533 hash = header.parent_hash;
2534 number = number.saturating_sub(1);
2535 }
2536 Ok(None) => break,
2537 Err(e) => {
2538 warn!("Failed to fetch block header by hash during BLOCKHASH cache walk: {e}");
2539 break;
2540 }
2541 }
2542 }
2543 let vm_db = StoreVmDatabase::new_with_block_hash_cache(
2544 self.storage.clone(),
2545 parent_header,
2546 block_hash_cache,
2547 )
2548 .map_err(|e| (ChainError::EvmError(e), None))?;
2549 let mut vm = self.new_evm(vm_db).map_err(|e| (e.into(), None))?;
2550
2551 let blocks_len = blocks.len();
2552 let mut all_receipts: Vec<(BlockHash, Vec<Receipt>)> = Vec::with_capacity(blocks_len);
2553 let mut total_gas_used = 0;
2554 let mut transactions_count = 0;
2555
2556 let interval = Instant::now();
2557 for (i, block) in blocks.iter().enumerate() {
2558 if cancellation_token.is_cancelled() {
2559 info!("Received shutdown signal, aborting");
2560 return Err((ChainError::Custom(String::from("shutdown signal")), None));
2561 }
2562 let parent_header = if i == 0 {
2564 find_parent_header(&block.header, &self.storage).map_err(|err| {
2565 (
2566 err,
2567 Some(BatchBlockProcessingFailure {
2568 failed_block_hash: block.hash(),
2569 last_valid_hash,
2570 }),
2571 )
2572 })?
2573 } else {
2574 blocks[i - 1].header.clone()
2576 };
2577
2578 let BlockExecutionResult { receipts, .. } = self
2579 .execute_block_from_state(&parent_header, block, &chain_config, &mut vm)
2580 .map_err(|err| {
2581 (
2582 err,
2583 Some(BatchBlockProcessingFailure {
2584 failed_block_hash: block.hash(),
2585 last_valid_hash,
2586 }),
2587 )
2588 })?;
2589 debug!("Executed block with hash {}", block.hash());
2590 last_valid_hash = block.hash();
2591 total_gas_used += block.header.gas_used;
2592 transactions_count += block.body.transactions.len();
2593 all_receipts.push((block.hash(), receipts));
2594
2595 log_batch_progress(blocks_len as u32, i as u32);
2597 tokio::task::yield_now().await;
2598 }
2599
2600 let account_updates = vm
2601 .get_state_transitions()
2602 .map_err(|err| (ChainError::EvmError(err), None))?;
2603
2604 let last_block = blocks
2605 .last()
2606 .ok_or_else(|| (ChainError::Custom("Last block not found".into()), None))?;
2607
2608 let last_block_number = last_block.header.number;
2609 let last_block_gas_limit = last_block.header.gas_limit;
2610
2611 let account_updates_list = self
2613 .storage
2614 .apply_account_updates_batch(first_block_header.parent_hash, &account_updates)
2615 .map_err(|e| (e.into(), None))?
2616 .ok_or((ChainError::ParentStateNotFound, None))?;
2617
2618 let new_state_root = account_updates_list.state_trie_hash;
2619 let state_updates = account_updates_list.state_updates;
2620 let accounts_updates = account_updates_list.storage_updates;
2621 let code_updates = account_updates_list.code_updates;
2622
2623 validate_state_root(&last_block.header, new_state_root).map_err(|e| (e, None))?;
2625
2626 let bals_to_store: Vec<(BlockHash, BlockAccessList)> = blocks
2633 .iter()
2634 .zip(bals.iter())
2635 .filter_map(|(block, bal)| {
2636 let bal = bal.as_ref()?;
2637 bal.matches_commitment(block.header.block_access_list_hash, &NativeCrypto)
2638 .then(|| (block.hash(), bal.clone()))
2639 })
2640 .collect();
2641
2642 let update_batch = UpdateBatch {
2643 account_updates: state_updates,
2644 storage_updates: accounts_updates,
2645 blocks,
2646 receipts: all_receipts,
2647 code_updates,
2648 batch_mode: true,
2649 };
2650
2651 self.storage
2652 .store_block_updates(update_batch)
2653 .map_err(|e| (e.into(), None))?;
2654
2655 for (block_hash, bal) in &bals_to_store {
2656 if let Err(err) = self.storage.store_block_access_list(*block_hash, bal) {
2657 warn!(
2658 "Failed to persist block access list for {block_hash} during batch sync: {err}"
2659 );
2660 }
2661 }
2662
2663 let elapsed_seconds = interval.elapsed().as_secs_f64();
2664 let throughput = if elapsed_seconds > 0.0 && total_gas_used != 0 {
2665 let as_gigas = (total_gas_used as f64) / 1e9;
2666 as_gigas / elapsed_seconds
2667 } else {
2668 0.0
2669 };
2670
2671 metrics!(
2672 METRICS_BLOCKS.set_block_number(last_block_number);
2673 METRICS_BLOCKS.set_latest_block_gas_limit(last_block_gas_limit as f64);
2674 METRICS_BLOCKS.set_latest_gas_used(total_gas_used as f64 / blocks_len as f64);
2676 METRICS_BLOCKS.set_latest_gigagas(throughput);
2677 );
2678
2679 if self.options.perf_logs_enabled {
2680 info!(
2681 "[METRICS] Executed and stored: Range: {}, Last block num: {}, Last block gas limit: {}, Total transactions: {}, Total Gas: {}, Throughput: {} Gigagas/s",
2682 blocks_len,
2683 last_block_number,
2684 last_block_gas_limit,
2685 transactions_count,
2686 total_gas_used,
2687 throughput
2688 );
2689 }
2690
2691 Ok(())
2692 }
2693
2694 #[cfg(feature = "c-kzg")]
2696 pub async fn add_blob_transaction_to_pool(
2697 &self,
2698 transaction: EIP4844Transaction,
2699 blobs_bundle: BlobsBundle,
2700 ) -> Result<H256, MempoolError> {
2701 let fork = self.current_fork().await?;
2702
2703 let transaction = Transaction::EIP4844Transaction(transaction);
2704 let hash = transaction.hash(&NativeCrypto);
2705 if self.mempool.contains_tx(hash)? {
2706 return Ok(hash);
2707 }
2708
2709 let wrapper_len = transaction.encode_canonical_len() + blobs_bundle.length();
2716 if wrapper_len > MAX_BLOB_TX_SIZE {
2717 return Err(MempoolError::TxSizeExceeded {
2718 actual: wrapper_len,
2719 limit: MAX_BLOB_TX_SIZE,
2720 });
2721 }
2722
2723 if let Transaction::EIP4844Transaction(transaction) = &transaction {
2725 blobs_bundle.validate(transaction, fork)?;
2726 }
2727
2728 let sender = transaction.sender(&NativeCrypto)?;
2729
2730 if let Some(tx_to_replace) = self.validate_transaction(&transaction, sender).await? {
2732 self.remove_transaction_from_pool(&tx_to_replace)?;
2733 }
2734
2735 self.mempool.add_blobs_bundle(hash, blobs_bundle)?;
2738 self.mempool
2739 .add_transaction(hash, sender, MempoolTransaction::new(transaction, sender))?;
2740 Ok(hash)
2741 }
2742
2743 pub async fn add_transaction_to_pool(
2745 &self,
2746 transaction: Transaction,
2747 ) -> Result<H256, MempoolError> {
2748 if matches!(transaction, Transaction::EIP4844Transaction(_)) {
2750 return Err(MempoolError::BlobTxNoBlobsBundle);
2751 }
2752 let encoded_len = transaction.encode_canonical_len();
2758 if encoded_len > MAX_TX_SIZE {
2759 return Err(MempoolError::TxSizeExceeded {
2760 actual: encoded_len,
2761 limit: MAX_TX_SIZE,
2762 });
2763 }
2764 let hash = transaction.hash(&NativeCrypto);
2765 if self.mempool.contains_tx(hash)? {
2766 return Ok(hash);
2767 }
2768 let sender = transaction.sender(&NativeCrypto)?;
2769 if let Some(tx_to_replace) = self.validate_transaction(&transaction, sender).await? {
2771 self.remove_transaction_from_pool(&tx_to_replace)?;
2772 }
2773
2774 self.mempool
2776 .add_transaction(hash, sender, MempoolTransaction::new(transaction, sender))?;
2777
2778 Ok(hash)
2779 }
2780
2781 pub fn remove_transaction_from_pool(&self, hash: &H256) -> Result<(), StoreError> {
2783 self.mempool.remove_transaction(hash)
2784 }
2785
2786 pub fn remove_block_transactions_from_pool(&self, block: &Block) -> Result<(), StoreError> {
2788 for tx in &block.body.transactions {
2789 self.mempool.remove_transaction(&tx.hash(&NativeCrypto))?;
2790 }
2791 Ok(())
2792 }
2793
2794 pub fn remove_stale_blob_txs(&self, head_hash: BlockHash) -> Result<(), StoreError> {
2799 let blob_txs = self.mempool.blob_txs()?;
2800 if blob_txs.is_empty() {
2801 return Ok(());
2802 }
2803 let mut nonce_by_sender: HashMap<Address, u64> = HashMap::new();
2805 for (hash, sender, tx_nonce) in blob_txs {
2806 let state_nonce = match nonce_by_sender.entry(sender) {
2807 Entry::Occupied(e) => *e.get(),
2808 Entry::Vacant(e) => {
2809 let nonce = self
2810 .storage
2811 .get_account_info_by_hash(head_hash, sender)?
2812 .map(|info| info.nonce)
2813 .unwrap_or(0);
2814 *e.insert(nonce)
2815 }
2816 };
2817 if tx_nonce < state_nonce {
2818 self.mempool.remove_transaction(&hash)?;
2819 }
2820 }
2821 Ok(())
2822 }
2823
2824 pub async fn validate_transaction(
2857 &self,
2858 tx: &Transaction,
2859 sender: Address,
2860 ) -> Result<Option<H256>, MempoolError> {
2861 let nonce = tx.nonce();
2862
2863 if matches!(tx, &Transaction::PrivilegedL2Transaction(_)) {
2864 return Ok(None);
2865 }
2866
2867 let header_no = self.storage.get_latest_block_number().await?;
2868 let header = self
2869 .storage
2870 .get_block_header(header_no)?
2871 .ok_or(MempoolError::NoBlockHeaderError)?;
2872 let config = self.storage.get_chain_config();
2873
2874 if !matches!(tx, Transaction::EIP4844Transaction(_)) {
2881 let encoded_len = tx.encode_canonical_len();
2882 if encoded_len > MAX_TX_SIZE {
2883 return Err(MempoolError::TxSizeExceeded {
2884 actual: encoded_len,
2885 limit: MAX_TX_SIZE,
2886 });
2887 }
2888 }
2889
2890 let max_initcode_size = if config.is_amsterdam_activated(header.timestamp) {
2893 AMSTERDAM_MAX_INITCODE_SIZE
2894 } else {
2895 MAX_INITCODE_SIZE
2896 };
2897 if config.is_shanghai_activated(header.timestamp)
2898 && tx.is_contract_creation()
2899 && tx.data().len() > max_initcode_size as usize
2900 {
2901 return Err(MempoolError::TxMaxInitCodeSizeError);
2902 }
2903
2904 if config.is_osaka_activated(header.timestamp)
2905 && !config.is_amsterdam_activated(header.timestamp)
2906 && tx.gas_limit() > POST_OSAKA_GAS_LIMIT_CAP
2907 {
2908 return Err(MempoolError::TxMaxGasLimitExceededError(
2910 tx.hash(&NativeCrypto),
2911 tx.gas_limit(),
2912 ));
2913 }
2914
2915 if header.gas_limit < tx.gas_limit() {
2917 return Err(MempoolError::TxGasLimitExceededError);
2918 }
2919
2920 if tx.max_priority_fee().unwrap_or(0) > tx.max_fee_per_gas().unwrap_or(0) {
2922 return Err(MempoolError::TxTipAboveFeeCapError);
2923 }
2924
2925 if let Transaction::EIP7702Transaction(eip7702) = tx {
2930 if !config.is_prague_activated(header.timestamp) {
2932 return Err(MempoolError::Eip7702TxPreFork);
2933 }
2934 if eip7702.authorization_list.is_empty() {
2936 return Err(MempoolError::EmptyAuthorizationList);
2937 }
2938 }
2939
2940 if tx.gas_limit() < mempool::transaction_intrinsic_gas(tx, &header, &config)? {
2942 return Err(MempoolError::TxIntrinsicGasCostAboveLimitError);
2943 }
2944
2945 if let Some(fee) = tx.max_fee_per_blob_gas() {
2947 if fee < MIN_BASE_FEE_PER_BLOB_GAS.into() {
2949 return Err(MempoolError::TxBlobBaseFeeTooLowError);
2950 }
2951 };
2952
2953 let maybe_sender_acc_info = self.storage.get_account_info(header_no, sender).await?;
2954
2955 if let Some(sender_acc_info) = maybe_sender_acc_info {
2956 if nonce < sender_acc_info.nonce || nonce == u64::MAX {
2957 return Err(MempoolError::NonceTooLow);
2958 }
2959
2960 let tx_cost = tx
2961 .cost_without_base_fee()
2962 .ok_or(MempoolError::InvalidTxGasvalues)?;
2963
2964 if tx_cost > sender_acc_info.balance {
2965 return Err(MempoolError::NotEnoughBalance);
2966 }
2967 } else {
2968 return Err(MempoolError::NotEnoughBalance);
2970 }
2971
2972 let tx_to_replace_hash = self.mempool.find_tx_to_replace(sender, nonce, tx)?;
2975
2976 if tx
2977 .chain_id()
2978 .is_some_and(|chain_id| chain_id != config.chain_id)
2979 {
2980 return Err(MempoolError::InvalidChainId(config.chain_id));
2981 }
2982
2983 Ok(tx_to_replace_hash)
2984 }
2985
2986 pub fn set_synced(&self) {
2989 self.is_synced.store(true, Ordering::Relaxed);
2990 }
2991
2992 pub fn set_not_synced(&self) {
2995 self.is_synced.store(false, Ordering::Relaxed);
2996 }
2997
2998 pub fn is_synced(&self) -> bool {
3002 self.is_synced.load(Ordering::Relaxed)
3003 }
3004
3005 pub fn get_p2p_transaction_by_hash(&self, hash: &H256) -> Result<P2PTransaction, StoreError> {
3006 let Some(tx) = self.mempool.get_transaction_by_hash(*hash)? else {
3007 return Err(StoreError::Custom(format!(
3008 "Hash {hash} not found in the mempool",
3009 )));
3010 };
3011 let result = match tx {
3012 Transaction::LegacyTransaction(itx) => P2PTransaction::LegacyTransaction(itx),
3013 Transaction::EIP2930Transaction(itx) => P2PTransaction::EIP2930Transaction(itx),
3014 Transaction::EIP1559Transaction(itx) => P2PTransaction::EIP1559Transaction(itx),
3015 Transaction::EIP4844Transaction(itx) => {
3016 let Some(bundle) = self.mempool.get_blobs_bundle(*hash)? else {
3017 return Err(StoreError::Custom(format!(
3018 "Blob transaction present without its bundle: hash {hash}",
3019 )));
3020 };
3021
3022 P2PTransaction::EIP4844TransactionWithBlobs(WrappedEIP4844Transaction {
3023 tx: itx,
3024 wrapper_version: (bundle.version != 0).then_some(bundle.version),
3025 blobs_bundle: bundle,
3026 })
3027 }
3028 Transaction::EIP7702Transaction(itx) => P2PTransaction::EIP7702Transaction(itx),
3029 Transaction::PrivilegedL2Transaction(_) => {
3033 return Err(StoreError::Custom(
3034 "Privileged Transactions are not supported in P2P".to_string(),
3035 ));
3036 }
3037 Transaction::FeeTokenTransaction(itx) => P2PTransaction::FeeTokenTransaction(itx),
3038 };
3039
3040 Ok(result)
3041 }
3042
3043 pub fn new_evm(&self, vm_db: StoreVmDatabase) -> Result<Evm, EvmError> {
3044 new_evm(&self.options.r#type, vm_db)
3045 }
3046
3047 pub async fn current_fork(&self) -> Result<Fork, StoreError> {
3049 let chain_config = self.storage.get_chain_config();
3050 let latest_block_number = self.storage.get_latest_block_number().await?;
3051 let latest_block = self
3052 .storage
3053 .get_block_header(latest_block_number)?
3054 .ok_or(StoreError::Custom("Latest block not in DB".to_string()))?;
3055 Ok(chain_config.fork(latest_block.timestamp))
3056 }
3057}
3058
3059fn load_trie(
3061 storage: &Store,
3062 parent_state_root: H256,
3063 prefix: Option<H256>,
3064) -> Result<Trie, StoreError> {
3065 Ok(match prefix {
3066 Some(account_hash) => {
3067 let state_trie = storage.open_state_trie(parent_state_root)?;
3068 let storage_root = match state_trie.get(account_hash.as_bytes())? {
3069 Some(rlp) => AccountState::decode(&rlp)?.storage_root,
3070 None => *EMPTY_TRIE_HASH,
3071 };
3072 storage.open_storage_trie(account_hash, parent_state_root, storage_root)?
3073 }
3074 None => storage.open_state_trie(parent_state_root)?,
3075 })
3076}
3077
3078fn collapse_root_node(
3081 storage: &Store,
3082 parent_state_root: H256,
3083 prefix: Option<H256>,
3084 root: BranchNode,
3085) -> Result<Option<Node>, StoreError> {
3086 let children: Vec<(usize, &NodeRef)> = root
3087 .choices
3088 .iter()
3089 .enumerate()
3090 .filter(|(_, choice)| choice.is_valid())
3091 .take(2)
3092 .collect();
3093 if children.len() > 1 {
3094 return Ok(Some(Node::Branch(Box::from(root))));
3095 }
3096 let Some((choice, only_child)) = children.first() else {
3097 return Ok(None);
3098 };
3099 let only_child = Arc::unwrap_or_clone(match only_child {
3100 NodeRef::Node(node, _) => node.clone(),
3101 noderef @ NodeRef::Hash(_) => {
3102 let trie = load_trie(storage, parent_state_root, prefix)?;
3103 let Some(node) = noderef.get_node(trie.db(), Nibbles::from_hex(vec![*choice as u8]))?
3104 else {
3105 return Ok(None);
3106 };
3107 node
3108 }
3109 });
3110 Ok(Some(match only_child {
3111 Node::Branch(_) => {
3112 ExtensionNode::new(Nibbles::from_hex(vec![*choice as u8]), only_child.into()).into()
3113 }
3114 Node::Extension(mut extension_node) => {
3115 extension_node.prefix.prepend(*choice as u8);
3116 extension_node.into()
3117 }
3118 Node::Leaf(mut leaf) => {
3119 leaf.partial.prepend(*choice as u8);
3120 leaf.into()
3121 }
3122 }))
3123}
3124
3125fn collect_and_send(
3127 index: u8,
3128 state_trie: &mut Trie,
3129 pre_collected_state: &mut Vec<TrieNode>,
3130 storage_nodes: &mut Vec<(H256, Vec<TrieNode>)>,
3131 tx: Sender<CollectedStateMsg>,
3132) -> Result<(), StoreError> {
3133 let (subroot, mut state_nodes) = collect_trie(index, std::mem::take(state_trie))?;
3134 if !pre_collected_state.is_empty() {
3135 let mut pre = std::mem::take(pre_collected_state);
3136 pre.extend(state_nodes);
3137 state_nodes = pre;
3138 }
3139 tx.send(CollectedStateMsg {
3140 index,
3141 subroot,
3142 state_nodes,
3143 storage_nodes: std::mem::take(storage_nodes),
3144 })
3145 .map_err(|e| StoreError::Custom(format!("send error: {e}")))?;
3146 Ok(())
3147}
3148
3149fn get_or_open_storage_trie<'a>(
3151 storage_tries: &'a mut FxHashMap<H256, Trie>,
3152 storage: &Store,
3153 parent_state_root: H256,
3154 prefix: H256,
3155 storage_root: H256,
3156) -> Result<&'a mut Trie, StoreError> {
3157 match storage_tries.entry(prefix) {
3158 Entry::Occupied(e) => Ok(e.into_mut()),
3159 Entry::Vacant(e) => {
3160 Ok(e.insert(storage.open_storage_trie(prefix, parent_state_root, storage_root)?))
3161 }
3162 }
3163}
3164
3165fn handle_subtrie(
3166 storage: Store,
3167 rx: cb::Receiver<WorkerRequest>,
3168 parent_state_root: H256,
3169 index: u8,
3170 worker_senders: Vec<cb::Sender<WorkerRequest>>,
3171 shutdown_rx: cb::Receiver<()>,
3172) -> Result<(), StoreError> {
3173 let mut state_trie = storage.open_state_trie(parent_state_root)?;
3174 let mut storage_nodes: Vec<(H256, Vec<TrieNode>)> = vec![];
3175 let mut accounts: FxHashMap<H256, AccountState> = Default::default();
3176 let mut expected_shards: FxHashMap<H256, u16> = Default::default();
3177 let mut storage_state: FxHashMap<H256, PreMerkelizedAccountState> = Default::default();
3178 let mut received_shards: FxHashMap<H256, u16> = Default::default();
3179 let mut pending_storage_accounts: usize = 0;
3180 let mut pending_collect_tx: Option<Sender<CollectedStateMsg>> = None;
3181 let mut pre_collected_state: Vec<TrieNode> = vec![];
3182 let mut storage_tries: FxHashMap<H256, Trie> = Default::default();
3183 let mut pre_collected_storage: FxHashMap<H256, Vec<TrieNode>> = Default::default();
3184
3185 let mut worker_senders: Option<Vec<cb::Sender<WorkerRequest>>> = Some(worker_senders);
3187 let mut dirty = false;
3188 let mut collecting_storages = false;
3191 let mut routing_complete = false;
3192 let mut routing_done_mask: u16 = 0;
3193 let mut storage_to_collect: Vec<(H256, Trie)> = vec![];
3194
3195 loop {
3196 if collecting_storages {
3199 if let Some((prefix, trie)) = storage_to_collect.pop() {
3200 let senders = worker_senders
3201 .as_ref()
3202 .expect("collecting after senders dropped");
3203 let (root, mut nodes) = collect_trie(index, trie)?;
3204 if let Some(mut pre_nodes) = pre_collected_storage.remove(&prefix) {
3205 pre_nodes.extend(nodes);
3206 nodes = pre_nodes;
3207 }
3208 let bucket = prefix.as_fixed_bytes()[0] >> 4;
3209 senders[bucket as usize]
3210 .send(WorkerRequest::StorageShard {
3211 prefix,
3212 index,
3213 subroot: root,
3214 nodes,
3215 })
3216 .map_err(|e| StoreError::Custom(format!("send error: {e}")))?;
3217 } else {
3218 worker_senders = None;
3220 collecting_storages = false;
3221 if pending_storage_accounts == 0
3223 && let Some(tx) = pending_collect_tx.take()
3224 {
3225 collect_and_send(
3226 index,
3227 &mut state_trie,
3228 &mut pre_collected_state,
3229 &mut storage_nodes,
3230 tx,
3231 )?;
3232 break;
3233 }
3234 }
3235 }
3236
3237 let msg = if collecting_storages || dirty {
3240 match rx.try_recv() {
3241 Ok(msg) => msg,
3242 Err(TryRecvError::Disconnected) => break,
3243 Err(TryRecvError::Empty) => {
3244 if matches!(shutdown_rx.try_recv(), Err(TryRecvError::Disconnected)) {
3246 return Err(StoreError::Custom("shard worker shutdown".into()));
3247 }
3248 if dirty {
3249 let mut nodes = state_trie.commit_without_storing(&NativeCrypto);
3253 nodes.retain(|(nib, _)| nib.as_ref().first() == Some(&index));
3254 pre_collected_state.extend(nodes);
3255 if !collecting_storages {
3256 for (prefix, trie) in storage_tries.iter_mut() {
3258 let mut nodes = trie.commit_without_storing(&NativeCrypto);
3259 nodes.retain(|(nib, _)| nib.as_ref().first() == Some(&index));
3260 if !nodes.is_empty() {
3261 pre_collected_storage
3262 .entry(*prefix)
3263 .or_default()
3264 .extend(nodes);
3265 }
3266 }
3267 }
3268 dirty = false;
3269 }
3270 continue;
3271 }
3272 }
3273 } else {
3274 select! {
3275 recv(rx) -> msg => match msg {
3276 Ok(msg) => msg,
3277 Err(_) => break,
3278 },
3279 recv(shutdown_rx) -> _ => {
3280 return Err(StoreError::Custom("shard worker shutdown".into()));
3281 }
3282 }
3283 };
3284
3285 match msg {
3286 WorkerRequest::ProcessAccount {
3287 prefix,
3288 info,
3289 storage: account_storage,
3290 removed,
3291 removed_storage,
3292 } => {
3293 let senders = worker_senders
3294 .as_ref()
3295 .expect("ProcessAccount after collection started");
3296
3297 match accounts.entry(prefix) {
3299 Entry::Occupied(_) => {}
3300 Entry::Vacant(vacant_entry) => {
3301 let account_state = match state_trie.get(prefix.as_bytes())? {
3302 Some(rlp) => {
3303 let state = AccountState::decode(&rlp)?;
3304 state_trie.insert(prefix.as_bytes().to_vec(), rlp)?;
3305 state
3306 }
3307 None => AccountState::default(),
3308 };
3309 vacant_entry.insert(account_state);
3310 }
3311 }
3312
3313 if let Some(info) = info {
3315 let acct = accounts.get_mut(&prefix).expect("just loaded");
3316 acct.nonce = info.nonce;
3317 acct.balance = info.balance;
3318 acct.code_hash = info.code_hash;
3319 let path = prefix.as_bytes();
3320 if *acct != AccountState::default() {
3321 state_trie.insert(path.to_vec(), acct.encode_to_vec())?;
3322 } else {
3323 state_trie.remove(path)?;
3324 }
3325 }
3326
3327 if removed || removed_storage {
3328 pre_collected_storage.remove(&prefix);
3330 storage_tries.insert(prefix, Trie::new_temp());
3331 for (i, tx) in senders.iter().enumerate() {
3332 if i as u8 != index {
3333 tx.send(WorkerRequest::DeleteStorage(prefix))
3334 .map_err(|e| StoreError::Custom(format!("send error: {e}")))?;
3335 }
3336 }
3337 accounts.get_mut(&prefix).expect("just loaded").storage_root = *EMPTY_TRIE_HASH;
3338 if expected_shards.insert(prefix, 0xFFFF).is_none() {
3339 pending_storage_accounts += 1;
3340 }
3341 if removed {
3342 dirty = true;
3343 continue;
3344 }
3345 }
3346
3347 if !account_storage.is_empty() {
3348 let storage_root = accounts
3349 .get(&prefix)
3350 .map(|a| a.storage_root)
3351 .unwrap_or(*EMPTY_TRIE_HASH);
3352
3353 let is_new = !expected_shards.contains_key(&prefix);
3354 for (key, value) in account_storage {
3355 let hashed_key = keccak(key);
3356 let bucket = hashed_key.as_fixed_bytes()[0] >> 4;
3357 *expected_shards.entry(prefix).or_insert(0u16) |= 1 << bucket;
3358 if bucket == index {
3359 let trie = get_or_open_storage_trie(
3361 &mut storage_tries,
3362 &storage,
3363 parent_state_root,
3364 prefix,
3365 storage_root,
3366 )?;
3367 if value.is_zero() {
3368 trie.remove(hashed_key.as_bytes())?;
3369 } else {
3370 trie.insert(hashed_key.as_bytes().to_vec(), value.encode_to_vec())?;
3371 }
3372 } else {
3373 senders[bucket as usize]
3374 .send(WorkerRequest::MerklizeStorage {
3375 prefix,
3376 key: hashed_key,
3377 value,
3378 storage_root,
3379 })
3380 .map_err(|e| StoreError::Custom(format!("send error: {e}")))?;
3381 }
3382 }
3383 if is_new {
3384 pending_storage_accounts += 1;
3385 }
3386 }
3387 dirty = true;
3388 }
3389 WorkerRequest::MerklizeStorage {
3390 prefix,
3391 key,
3392 value,
3393 storage_root,
3394 } => {
3395 let trie = get_or_open_storage_trie(
3396 &mut storage_tries,
3397 &storage,
3398 parent_state_root,
3399 prefix,
3400 storage_root,
3401 )?;
3402 if value.is_zero() {
3403 trie.remove(key.as_bytes())?;
3404 } else {
3405 trie.insert(key.as_bytes().to_vec(), value.encode_to_vec())?;
3406 }
3407 dirty = true;
3408 }
3409 WorkerRequest::DeleteStorage(prefix) => {
3410 pre_collected_storage.remove(&prefix);
3411 storage_tries.insert(prefix, Trie::new_temp());
3412 dirty = true;
3413 }
3414 WorkerRequest::FinishRouting => {
3415 let senders = worker_senders
3417 .as_ref()
3418 .expect("FinishRouting after senders dropped");
3419 for i in 0..16u8 {
3420 senders[i as usize]
3421 .send(WorkerRequest::RoutingDone { from: index })
3422 .map_err(|e| StoreError::Custom(format!("send error: {e}")))?;
3423 }
3424 }
3425 WorkerRequest::RoutingDone { from } => {
3426 routing_done_mask |= 1u16 << from;
3427 if routing_done_mask == 0xFFFF && !collecting_storages && !routing_complete {
3428 collecting_storages = true;
3429 routing_complete = true;
3430 storage_to_collect = storage_tries.drain().collect();
3431 }
3432 }
3433 WorkerRequest::MerklizeAccounts { accounts: batch } => {
3434 for hashed_account in batch {
3436 storage_nodes.push((hashed_account, vec![]));
3437 }
3438 }
3439 WorkerRequest::StorageShard {
3440 prefix,
3441 index: shard_index,
3442 mut subroot,
3443 nodes,
3444 } => {
3445 let state = storage_state.entry(prefix).or_default();
3446 match &mut state.storage_root {
3447 Some(root) => {
3448 root.choices[shard_index as usize] =
3449 std::mem::take(&mut subroot.choices[shard_index as usize]);
3450 }
3451 rootptr => {
3452 *rootptr = Some(subroot);
3453 }
3454 }
3455 state.nodes.extend(nodes);
3456
3457 let received = received_shards.entry(prefix).or_insert(0u16);
3458 *received |= 1 << shard_index;
3459 if *received == expected_shards.get(&prefix).copied().unwrap_or(0) {
3460 let mut state = storage_state.remove(&prefix).expect("shard without state");
3462 let new_storage_root = if let Some(mut root) = state.storage_root {
3463 root.choices.iter_mut().for_each(NodeRef::clear_hash);
3465 let collapsed =
3466 collapse_root_node(&storage, parent_state_root, Some(prefix), *root)?;
3467 if let Some(root) = collapsed {
3468 let mut root = NodeRef::from(root);
3469 let hash =
3470 root.commit(Nibbles::default(), &mut state.nodes, &NativeCrypto);
3471 let _ = DROP_SENDER.send(Box::new(root));
3472 hash.finalize(&NativeCrypto)
3473 } else {
3474 state.nodes.push((Nibbles::default(), vec![RLP_NULL]));
3475 *EMPTY_TRIE_HASH
3476 }
3477 } else {
3478 *EMPTY_TRIE_HASH
3479 };
3480 storage_nodes.push((prefix, state.nodes));
3481
3482 let old_state = accounts.get_mut(&prefix).expect("loaded in ProcessAccount");
3484 old_state.storage_root = new_storage_root;
3485 let path = prefix.as_bytes();
3486 if *old_state != AccountState::default() {
3487 state_trie.insert(path.to_vec(), old_state.encode_to_vec())?;
3488 } else {
3489 state_trie.remove(path)?;
3490 }
3491
3492 dirty = true;
3493 pending_storage_accounts -= 1;
3494 if pending_storage_accounts == 0
3495 && !collecting_storages
3496 && routing_complete
3497 && let Some(tx) = pending_collect_tx.take()
3498 {
3499 collect_and_send(
3500 index,
3501 &mut state_trie,
3502 &mut pre_collected_state,
3503 &mut storage_nodes,
3504 tx,
3505 )?;
3506 break;
3507 }
3508 }
3509 }
3510 WorkerRequest::CollectState { tx } => {
3511 if pending_storage_accounts == 0 && !collecting_storages && routing_complete {
3512 collect_and_send(
3513 index,
3514 &mut state_trie,
3515 &mut pre_collected_state,
3516 &mut storage_nodes,
3517 tx,
3518 )?;
3519 break;
3520 }
3521 pending_collect_tx = Some(tx);
3523 }
3524 }
3525 }
3526 Ok(())
3527}
3528
3529pub fn new_evm(blockchain_type: &BlockchainType, vm_db: StoreVmDatabase) -> Result<Evm, EvmError> {
3530 let evm = match blockchain_type {
3531 BlockchainType::L1 => Evm::new_for_l1(vm_db, Arc::new(NativeCrypto)),
3532 BlockchainType::L2(l2_config) => {
3533 let fee_config = *l2_config
3534 .fee_config
3535 .read()
3536 .map_err(|_| EvmError::Custom("Fee config lock was poisoned".to_string()))?;
3537
3538 Evm::new_for_l2(vm_db, fee_config, Arc::new(NativeCrypto))?
3539 }
3540 };
3541 Ok(evm)
3542}
3543
3544pub fn validate_state_root(
3546 block_header: &BlockHeader,
3547 new_state_root: H256,
3548) -> Result<(), ChainError> {
3549 if new_state_root == block_header.state_root {
3551 Ok(())
3552 } else {
3553 Err(ChainError::InvalidBlock(
3554 InvalidBlockError::StateRootMismatch,
3555 ))
3556 }
3557}
3558
3559pub async fn latest_canonical_block_hash(storage: &Store) -> Result<H256, ChainError> {
3561 let latest_block_number = storage.get_latest_block_number().await?;
3562 if let Some(latest_valid_header) = storage.get_block_header(latest_block_number)? {
3563 let latest_valid_hash = latest_valid_header.hash();
3564 return Ok(latest_valid_hash);
3565 }
3566 Err(ChainError::StoreError(StoreError::Custom(
3567 "Could not find latest valid hash".to_string(),
3568 )))
3569}
3570
3571pub fn find_parent_header(
3574 block_header: &BlockHeader,
3575 storage: &Store,
3576) -> Result<BlockHeader, ChainError> {
3577 match storage.get_block_header_by_hash(block_header.parent_hash)? {
3578 Some(parent_header) => Ok(parent_header),
3579 None => Err(ChainError::ParentNotFound),
3580 }
3581}
3582
3583pub async fn is_canonical(
3584 store: &Store,
3585 block_number: BlockNumber,
3586 block_hash: BlockHash,
3587) -> Result<bool, StoreError> {
3588 match store.get_canonical_block_hash(block_number).await? {
3589 Some(hash) if hash == block_hash => Ok(true),
3590 _ => Ok(false),
3591 }
3592}
3593
3594fn branchify(node: Node) -> Box<BranchNode> {
3595 match node {
3596 Node::Branch(branch_node) => branch_node,
3597 Node::Extension(extension_node) => {
3598 let index = extension_node.prefix.as_ref()[0];
3599 let noderef = if extension_node.prefix.len() == 1 {
3600 extension_node.child
3601 } else {
3602 let prefix = extension_node.prefix.offset(1);
3603 let node = ExtensionNode::new(prefix, extension_node.child);
3604 NodeRef::from(Arc::new(node.into()))
3605 };
3606 let mut choices = BranchNode::EMPTY_CHOICES;
3607 choices[index as usize] = noderef;
3608 Box::new(BranchNode::new(choices))
3609 }
3610 Node::Leaf(leaf_node) => {
3611 let index = leaf_node.partial.as_ref()[0];
3612 let node = LeafNode::new(leaf_node.partial.offset(1), leaf_node.value);
3613 let mut choices = BranchNode::EMPTY_CHOICES;
3614 choices[index as usize] = NodeRef::from(Arc::new(node.into()));
3615 Box::new(BranchNode::new(choices))
3616 }
3617 }
3618}
3619
3620fn collect_trie(index: u8, mut trie: Trie) -> Result<(Box<BranchNode>, Vec<TrieNode>), TrieError> {
3621 let root = branchify(
3622 trie.root_node()?
3623 .map(Arc::unwrap_or_clone)
3624 .unwrap_or_else(|| Node::Branch(Box::default())),
3625 );
3626 trie.root = Node::Branch(root).into();
3627 let (_, mut nodes) = trie.collect_changes_since_last_hash(&NativeCrypto);
3628 nodes.retain(|(nib, _)| nib.as_ref().first() == Some(&index));
3629
3630 let Some(Node::Branch(root)) = trie.root_node()?.map(Arc::unwrap_or_clone) else {
3631 return Err(TrieError::InvalidInput);
3632 };
3633 Ok((root, nodes))
3634}