1use std::sync::{
7 Arc,
8 atomic::{AtomicBool, AtomicU64, Ordering},
9};
10
11use alloy_consensus::{
12 Block, BlockBody, BlockHeader, EMPTY_OMMER_ROOT_HASH, Header, TxReceipt, proofs,
13 transaction::{SignerRecoverable, TxHashRef},
14};
15use alloy_eips::eip2718::Decodable2718;
16use alloy_evm::{
17 EvmFactory,
18 block::{BlockExecutor, BlockExecutorFactory},
19};
20use alloy_primitives::{Address, B64, B256, Bytes, U256};
21use alloy_rpc_types_eth::BlockNumberOrTag;
22use arb_evm::config::{ArbEvmConfig, arbos_version_from_mix_hash, l1_block_number_from_mix_hash};
23use arb_primitives::{ArbPrimitives, signed_tx::ArbTransactionSigned, tx_types::ArbInternalTx};
24use arb_rpc::block_producer::{
25 BlockProducer, BlockProducerError, BlockProductionInput, ProducedBlock,
26};
27use arbos::{
28 arbos_types::parse_init_message,
29 header::{ArbHeaderInfo, derive_arb_header_info},
30 internal_tx,
31 parse_l2::{ParsedTransaction, parse_l2_transactions, parsed_tx_to_signed},
32};
33use parking_lot::Mutex;
34use reth_chain_state::{CanonicalInMemoryState, ExecutedBlock, NewCanonicalChain};
35use reth_chainspec::ChainSpec;
36use reth_evm::ConfigureEvm;
37use reth_metrics::{
38 Metrics,
39 metrics::{self, Counter, Gauge, Histogram},
40};
41use reth_primitives_traits::{NodePrimitives, SealedHeader, logs_bloom};
42use reth_provider::{BlockNumReader, BlockReaderIdExt, HeaderProvider, StateProviderFactory};
43use reth_revm::database::StateProviderDatabase;
44use reth_storage_api::{StateProvider, StateProviderBox};
45use reth_trie_common::{HashedPostState, TrieInputSorted};
46use revm::database::{BundleState, StateBuilder};
47use revm_database::states::bundle_state::BundleRetention;
48use tracing::{debug, info, warn};
49
50use crate::genesis;
51
52pub trait InMemoryStateAccess {
58 type Primitives: NodePrimitives;
59 fn canonical_in_memory_state(&self) -> CanonicalInMemoryState<Self::Primitives>;
60}
61
62impl<N> InMemoryStateAccess for reth_provider::providers::BlockchainProvider<N>
64where
65 N: reth_provider::providers::ProviderNodeTypes,
66{
67 type Primitives = N::Primitives;
68 fn canonical_in_memory_state(&self) -> CanonicalInMemoryState<Self::Primitives> {
69 self.canonical_in_memory_state()
70 }
71}
72
73pub const DEFAULT_FLUSH_INTERVAL: u64 = 128;
74const DEFAULT_MAX_INFLIGHT: usize = 512;
75
76fn max_inflight() -> usize {
77 static MAX: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
78 *MAX.get_or_init(|| {
79 std::env::var("ARB_RETH_MAX_INFLIGHT")
80 .ok()
81 .and_then(|s| s.parse::<usize>().ok())
82 .filter(|n| *n > 0)
83 .unwrap_or(DEFAULT_MAX_INFLIGHT)
84 })
85}
86
87pub struct FlushScheduler {
90 interval: u64,
91 ema_commit_latency_ms: u64,
92}
93
94impl FlushScheduler {
95 pub fn new(interval: u64) -> Self {
96 Self {
97 interval,
98 ema_commit_latency_ms: 0,
99 }
100 }
101
102 pub fn should_flush(&self, since_last: u64) -> bool {
103 since_last >= self.interval
104 }
105
106 pub fn observe(&mut self, commit_latency_ms: u64) {
107 self.ema_commit_latency_ms = (self.ema_commit_latency_ms * 7 + commit_latency_ms * 3) / 10;
108 }
109
110 pub fn current_interval(&self) -> u64 {
111 self.interval
112 }
113}
114
115#[cfg(target_os = "linux")]
116fn read_dirty_pages_mb() -> Option<u64> {
117 let content = std::fs::read_to_string("/proc/meminfo").ok()?;
118 for line in content.lines() {
119 if let Some(rest) = line.strip_prefix("Dirty:") {
120 let kb: u64 = rest.trim().trim_end_matches(" kB").trim().parse().ok()?;
121 return Some(kb / 1024);
122 }
123 }
124 None
125}
126
127#[cfg(not(target_os = "linux"))]
128fn read_dirty_pages_mb() -> Option<u64> {
129 None
130}
131
132#[derive(Metrics)]
134#[metrics(scope = "arb_block_producer")]
135struct ArbBlockProducerMetrics {
136 head_block: Gauge,
138 blocks_produced_total: Counter,
140 gas_processed_total: Counter,
142 transactions_processed_total: Counter,
144 flush_commit_duration_seconds: Histogram,
146 backpressure_stall_seconds: Histogram,
148}
149
150pub struct ArbBlockProducer<Provider> {
152 provider: Provider,
153 chain_spec: Arc<ChainSpec>,
154 evm_config: ArbEvmConfig,
155 in_memory_state: CanonicalInMemoryState<ArbPrimitives>,
156 head_block_num: AtomicU64,
157 blocks_since_flush: AtomicU64,
158 scheduler: Mutex<FlushScheduler>,
159 accumulated_trie_input: Mutex<Arc<TrieInputSorted>>,
160 flushing_trie_input: Mutex<Option<Arc<TrieInputSorted>>>,
161 pending_flush: AtomicBool,
162 produce_lock: tokio::sync::Mutex<()>,
163 cached_init: Mutex<Option<arbos::arbos_types::ParsedInitMessage>>,
164 finality: Mutex<FinalityMarkers>,
166 validated_watcher: Mutex<Option<Arc<parking_lot::RwLock<alloy_primitives::B256>>>>,
170 cached_overlay: Mutex<Option<CachedOverlay>>,
174 cached_prestate: Mutex<Option<CachedPrestate>>,
175 metrics: ArbBlockProducerMetrics,
176}
177
178#[derive(Debug, Default, Clone)]
179struct FinalityMarkers {
180 safe: Option<alloy_primitives::B256>,
181 finalized: Option<alloy_primitives::B256>,
182 validated: Option<alloy_primitives::B256>,
183}
184
185struct CachedOverlay {
186 parent_hash: B256,
187 overlay: Arc<crate::coalesced_state::CoalescedOverlay>,
188}
189
190struct CachedPrestate {
191 parent_hash: B256,
192 contracts: Arc<alloy_primitives::map::HashMap<B256, revm::bytecode::Bytecode>>,
193}
194
195impl<Provider> ArbBlockProducer<Provider>
196where
197 Provider: BlockNumReader,
198{
199 pub fn new(
200 provider: Provider,
201 chain_spec: Arc<ChainSpec>,
202 evm_config: ArbEvmConfig,
203 in_memory_state: CanonicalInMemoryState<ArbPrimitives>,
204 flush_interval: u64,
205 ) -> Self {
206 let head = provider.last_block_number().unwrap_or(0);
207 Self {
208 provider,
209 chain_spec,
210 evm_config,
211 in_memory_state,
212 head_block_num: AtomicU64::new(head),
213 blocks_since_flush: AtomicU64::new(0),
214 scheduler: Mutex::new(FlushScheduler::new(flush_interval)),
215 accumulated_trie_input: Mutex::new(Arc::new(TrieInputSorted::default())),
216 flushing_trie_input: Mutex::new(None),
217 pending_flush: AtomicBool::new(false),
218 produce_lock: tokio::sync::Mutex::new(()),
219 cached_init: Mutex::new(None),
220 finality: Mutex::new(FinalityMarkers::default()),
221 validated_watcher: Mutex::new(None),
222 cached_overlay: Mutex::new(None),
223 cached_prestate: Mutex::new(None),
224 metrics: ArbBlockProducerMetrics::default(),
225 }
226 }
227
228 fn get_or_build_overlay(
229 &self,
230 parent_hash: B256,
231 head_state: &reth_chain_state::BlockState<ArbPrimitives>,
232 ) -> Arc<crate::coalesced_state::CoalescedOverlay> {
233 let mut cache = self.cached_overlay.lock();
234 if let Some(c) = cache.as_ref()
235 && c.parent_hash == parent_hash
236 {
237 return c.overlay.clone();
238 }
239 let overlay = Arc::new(crate::coalesced_state::CoalescedOverlay::from_chain(
240 head_state,
241 ));
242 *cache = Some(CachedOverlay {
243 parent_hash,
244 overlay: overlay.clone(),
245 });
246 overlay
247 }
248
249 fn extend_cached_overlay(&self, new_block_hash: B256, bundle: &BundleState) {
250 let mut cache = self.cached_overlay.lock();
251 let mut overlay = match cache.take() {
252 Some(c) => match Arc::try_unwrap(c.overlay) {
253 Ok(o) => o,
254 Err(arc) => (*arc).clone(),
255 },
256 None => crate::coalesced_state::CoalescedOverlay::default(),
257 };
258 overlay.extend_with_block(bundle);
259 *cache = Some(CachedOverlay {
260 parent_hash: new_block_hash,
261 overlay: Arc::new(overlay),
262 });
263 }
264
265 fn invalidate_cached_overlay(&self) {
266 *self.cached_overlay.lock() = None;
267 }
268
269 fn get_or_build_prestate(
270 &self,
271 parent_hash: B256,
272 head_state: Option<&reth_chain_state::BlockState<ArbPrimitives>>,
273 ) -> Arc<alloy_primitives::map::HashMap<B256, revm::bytecode::Bytecode>> {
274 let mut cache = self.cached_prestate.lock();
275 if let Some(c) = cache.as_ref()
276 && c.parent_hash == parent_hash
277 {
278 return c.contracts.clone();
279 }
280 let mut contracts: alloy_primitives::map::HashMap<B256, revm::bytecode::Bytecode> =
281 Default::default();
282 if let Some(head_state) = head_state {
283 for block_state in head_state.chain() {
284 let exec_output = &block_state.block().execution_output;
285 for (hash, code) in &exec_output.state.contracts {
286 contracts.entry(*hash).or_insert_with(|| code.clone());
287 }
288 }
289 }
290 let arc = Arc::new(contracts);
291 *cache = Some(CachedPrestate {
292 parent_hash,
293 contracts: arc.clone(),
294 });
295 arc
296 }
297
298 fn extend_cached_prestate(&self, new_block_hash: B256, bundle: &BundleState) {
299 let mut cache = self.cached_prestate.lock();
300 let mut contracts = match cache.take() {
301 Some(c) => match Arc::try_unwrap(c.contracts) {
302 Ok(map) => map,
303 Err(arc) => (*arc).clone(),
304 },
305 None => Default::default(),
306 };
307 for (hash, code) in &bundle.contracts {
308 contracts.entry(*hash).or_insert_with(|| code.clone());
309 }
310 *cache = Some(CachedPrestate {
311 parent_hash: new_block_hash,
312 contracts: Arc::new(contracts),
313 });
314 }
315
316 fn invalidate_cached_prestate(&self) {
317 *self.cached_prestate.lock() = None;
318 }
319
320 pub fn finality_markers(
322 &self,
323 ) -> (
324 Option<alloy_primitives::B256>,
325 Option<alloy_primitives::B256>,
326 Option<alloy_primitives::B256>,
327 ) {
328 let f = self.finality.lock();
329 (f.safe, f.finalized, f.validated)
330 }
331}
332
333impl<Provider> ArbBlockProducer<Provider>
334where
335 Provider: BlockNumReader
336 + BlockReaderIdExt
337 + HeaderProvider<Header = Header>
338 + StateProviderFactory
339 + Send
340 + Sync
341 + 'static,
342{
343 fn head_block_number(&self) -> Result<u64, BlockProducerError> {
345 let head = self.head_block_num.load(Ordering::SeqCst);
346 if head > 0 {
347 Ok(head)
348 } else {
349 self.provider
350 .last_block_number()
351 .map_err(|e| BlockProducerError::StateAccess(e.to_string()))
352 }
353 }
354
355 fn parent_header(&self, head_num: u64) -> Result<SealedHeader<Header>, BlockProducerError> {
357 self.provider
358 .sealed_header_by_number_or_tag(BlockNumberOrTag::Number(head_num))
359 .map_err(|e| BlockProducerError::StateAccess(e.to_string()))?
360 .ok_or_else(|| {
361 BlockProducerError::StateAccess(format!("Parent block {head_num} not found"))
362 })
363 }
364
365 fn drain_completed_flush(&self) -> bool {
366 if !self.pending_flush.load(Ordering::SeqCst) {
367 return false;
368 }
369 let Some(result) = crate::launcher::try_flush_result() else {
370 return false;
371 };
372 self.in_memory_state
373 .remove_persisted_blocks(result.last_num_hash);
374 *self.flushing_trie_input.lock() = None;
375 self.pending_flush.store(false, Ordering::SeqCst);
376 self.invalidate_cached_overlay();
377 self.invalidate_cached_prestate();
378 let commit_latency_ms = result.duration.as_millis() as u64;
379 self.metrics
380 .flush_commit_duration_seconds
381 .record(result.duration.as_secs_f64());
382 let flush_interval_current = {
383 let mut sched = self.scheduler.lock();
384 sched.observe(commit_latency_ms);
385 sched.current_interval()
386 };
387 let dirty_pages_mb = read_dirty_pages_mb().unwrap_or(0);
388 let chain_len_unflushed = self
389 .in_memory_state
390 .head_state()
391 .map(|s| s.chain().count())
392 .unwrap_or(0) as u64;
393 info!(
394 target: "block_producer",
395 flushed = result.count,
396 last_block = result.last_num_hash.number,
397 mdbx_commit_latency_ms = commit_latency_ms,
398 dirty_pages_mb,
399 flush_interval_current,
400 chain_len_unflushed,
401 "block flush"
402 );
403 true
404 }
405
406 async fn apply_backpressure(&self) {
407 let chain_len = self
408 .in_memory_state
409 .head_state()
410 .map(|s| s.chain().count())
411 .unwrap_or(0);
412 let limit = max_inflight();
413 if chain_len <= limit {
414 return;
415 }
416 if !self.pending_flush.load(Ordering::SeqCst) {
417 self.start_async_flush();
418 }
419 let start = std::time::Instant::now();
420 let notifier = crate::launcher::flush_notifier();
421 loop {
422 if let Some(n) = notifier.as_ref() {
423 let notified = n.notified();
426 if self.drain_completed_flush() {
427 break;
428 }
429 let waited = tokio::time::timeout(std::time::Duration::from_secs(30), notified)
430 .await
431 .is_ok();
432 if !waited {
433 warn!(
434 target: "block_producer",
435 chain_len,
436 waited_ms = start.elapsed().as_millis() as u64,
437 "Backpressure: flush notification timed out, polling once"
438 );
439 }
440 } else {
441 if self.drain_completed_flush() {
442 break;
443 }
444 tokio::time::sleep(std::time::Duration::from_millis(20)).await;
445 }
446 }
447 self.metrics
448 .backpressure_stall_seconds
449 .record(start.elapsed().as_secs_f64());
450 warn!(
451 target: "block_producer",
452 chain_len,
453 limit,
454 waited_ms = start.elapsed().as_millis() as u64,
455 "Backpressure: drained pending flush"
456 );
457 }
458
459 fn produce_block_with_execution(
460 &self,
461 input: &BlockProductionInput,
462 parsed_txs: Vec<ParsedTransaction>,
463 ) -> Result<ProducedBlock, BlockProducerError> {
464 self.drain_completed_flush();
465
466 let head_num = self.head_block_number()?;
467 let l2_block_number = head_num + 1;
468 let parent_header = self.parent_header(head_num)?;
469
470 let timestamp = input.l1_timestamp.max(parent_header.timestamp());
471 let time_passed = timestamp.saturating_sub(parent_header.timestamp());
472
473 let parent_mix_hash = parent_header.mix_hash().unwrap_or_default();
474 let parent_arbos_version = arbos_version_from_mix_hash(&parent_mix_hash);
475
476 let l1_block_number = input.l1_block_number;
479 let block_l1_block_number = monotonic_l1_block_number(l1_block_number, &parent_mix_hash);
480 let arbos_version = parent_arbos_version; let send_count = {
484 let mut buf = [0u8; 8];
485 buf.copy_from_slice(&parent_mix_hash.0[0..8]);
486 u64::from_be_bytes(buf)
487 };
488 let provisional_mix_hash =
489 compute_mix_hash(send_count, block_l1_block_number, arbos_version);
490
491 let raw_state_provider = self
493 .provider
494 .state_by_block_hash(parent_header.hash())
495 .map_err(|e| BlockProducerError::StateAccess(e.to_string()))?;
496
497 let state_provider: StateProviderBox = match self
498 .in_memory_state
499 .state_by_hash(parent_header.hash())
500 {
501 Some(head_state) => {
502 let overlay = self.get_or_build_overlay(parent_header.hash(), &head_state);
503 if overlay.is_empty() {
504 raw_state_provider
505 } else {
506 crate::coalesced_state::CoalescedStateProvider::new(raw_state_provider, overlay)
507 .boxed()
508 }
509 }
510 _ => raw_state_provider,
511 };
512
513 let l2_base_fee = {
515 let read_slot = |addr: Address, slot: B256| state_provider.storage(addr, slot);
516 arbos::header::read_l2_base_fee(&read_slot)
517 .map_err(|e| BlockProducerError::Storage(e.to_string()))?
518 .or(parent_header.base_fee_per_gas())
519 };
520
521 let provisional_header = Header {
523 parent_hash: parent_header.hash(),
524 ommers_hash: EMPTY_OMMER_ROOT_HASH,
525 beneficiary: input.sender,
526 state_root: B256::ZERO, transactions_root: B256::ZERO,
528 receipts_root: B256::ZERO,
529 withdrawals_root: None,
530 logs_bloom: Default::default(),
531 timestamp,
532 mix_hash: provisional_mix_hash,
533 nonce: B64::from(input.delayed_messages_read.to_be_bytes()),
534 base_fee_per_gas: l2_base_fee,
535 number: l2_block_number,
536 gas_limit: parent_header.gas_limit(),
537 difficulty: U256::from(1),
538 gas_used: 0,
539 extra_data: Default::default(),
540 parent_beacon_block_root: None,
541 blob_gas_used: None,
542 excess_blob_gas: None,
543 requests_hash: None,
544 };
545
546 let evm_env = self
547 .evm_config
548 .evm_env(&provisional_header)
549 .map_err(|_| BlockProducerError::Execution("evm_env construction failed".into()))?;
550
551 let prestate = {
558 let head_state_opt = self.in_memory_state.state_by_hash(parent_header.hash());
559 let contracts =
560 self.get_or_build_prestate(parent_header.hash(), head_state_opt.as_deref());
561 BundleState {
562 contracts: (*contracts).clone(),
563 ..Default::default()
564 }
565 };
566
567 let mut db = StateBuilder::new()
568 .with_database(StateProviderDatabase::new(state_provider.as_ref()))
569 .with_bundle_prestate(prestate)
570 .with_bundle_update()
571 .without_state_clear()
572 .build();
573
574 let chain_id = self.chain_spec.chain().id();
575
576 if let Some(init_msg) = self.cached_init.lock().take() {
583 if !genesis::is_arbos_initialized(&mut db) {
584 let initial_version = std::env::var("ARB_INITIAL_ARBOS_VERSION")
590 .ok()
591 .and_then(|v| v.parse::<u64>().ok())
592 .unwrap_or({
593 if parent_arbos_version > 0 {
594 parent_arbos_version
595 } else {
596 genesis::INITIAL_ARBOS_VERSION
597 }
598 });
599 info!(
600 target: "block_producer",
601 initial_version,
602 "Applying cached ArbOS Init during block {} execution",
603 l2_block_number
604 );
605 genesis::initialize_arbos_state(
606 &mut db,
607 &init_msg,
608 chain_id,
609 initial_version,
610 genesis::DEFAULT_CHAIN_OWNER,
611 genesis::ArbOSInit::default(),
612 )
613 .map_err(|e| BlockProducerError::Execution(e.to_string()))?;
614 } else {
615 use arbos::{arbos_state::ArbosState, burn::SystemBurner};
616 info!(
617 target: "block_producer",
618 initial_l1_base_fee = %init_msg.initial_l1_base_fee,
619 "ArbOS already initialized; overriding L1 price_per_unit from Init message"
620 );
621 let state_ptr: *mut _ = &mut db;
627 let mut arb_state =
628 ArbosState::open(unsafe { &mut *state_ptr }, SystemBurner::new(None, false))
629 .map_err(|e| BlockProducerError::Execution(e.to_string()))?;
630 let _ = arb_state
631 .l1_pricing_state
632 .set_price_per_unit(unsafe { &mut *state_ptr }, init_msg.initial_l1_base_fee);
633 if let Ok(target) = std::env::var("ARB_INITIAL_ARBOS_VERSION")
634 && let Ok(target_version) = target.parse::<u64>()
635 {
636 let current = arb_state.arbos_version();
637 if target_version > current {
638 match arb_state.upgrade_arbos_version(
639 unsafe { &mut *state_ptr },
640 target_version,
641 true,
642 ) {
643 Err(e) => {
644 info!(target: "block_producer", err = ?e, target_version, "ArbOS upgrade via env var failed");
645 }
646 _ => {
647 info!(
648 target: "block_producer",
649 from = current,
650 to = target_version,
651 "ArbOS upgraded via ARB_INITIAL_ARBOS_VERSION"
652 );
653 }
654 }
655 }
656 }
657 }
658 }
659
660 let parent_extra = parent_header.extra_data().to_vec();
661 let mut exec_extra = parent_extra.clone();
662 exec_extra.resize(32, 0);
663 exec_extra.extend_from_slice(&input.delayed_messages_read.to_be_bytes());
664
665 let exec_ctx = alloy_evm::eth::EthBlockExecutionCtx {
666 tx_count_hint: Some(parsed_txs.len() + 2), parent_hash: parent_header.hash(),
668 parent_beacon_block_root: None,
669 ommers: &[],
670 withdrawals: None,
671 extra_data: exec_extra.into(),
672 };
673
674 let multi_gas_sink = arb_evm::multi_gas::MultiGasSink::default();
679 let evm = self
680 .evm_config
681 .block_executor_factory()
682 .evm_factory()
683 .create_evm_with_inspector(
684 &mut db,
685 evm_env.clone(),
686 arb_evm::multi_gas::MultiGasInspector::with_sink(multi_gas_sink.clone()),
687 );
688 let mut executor = self
689 .evm_config
690 .block_executor_factory()
691 .create_arb_executor(evm, exec_ctx, chain_id);
692 executor.set_multi_gas_sink(multi_gas_sink);
693 executor.arb_ctx.l2_block_number = l2_block_number;
694 executor.arb_ctx.l1_block_number = block_l1_block_number;
695
696 let l2_hash_entries = {
698 let mut entries = Vec::new();
699 let parent_num = l2_block_number.saturating_sub(1);
700 entries.push((parent_num, parent_header.hash()));
701 let cache_cold = parent_num > 1
702 && self
703 .evm_config
704 .executor_factory
705 .arb_evm_factory()
706 .chain_caches()
707 .l2_block_hashes
708 .lock()
709 .get(&parent_num.saturating_sub(1))
710 .is_none();
711 if cache_cold {
712 let mut hash = parent_header.parent_hash();
713 for i in 2..=256u64 {
714 let Some(n) = l2_block_number.checked_sub(i) else {
715 break;
716 };
717 entries.push((n, hash));
718 match self
719 .provider
720 .sealed_header_by_number_or_tag(BlockNumberOrTag::Number(n))
721 {
722 Ok(Some(h)) => hash = h.parent_hash(),
723 _ => break,
724 }
725 }
726 }
727 entries
728 };
729
730 executor
732 .apply_pre_execution_changes()
733 .map_err(|e| BlockProducerError::Execution(format!("pre-exec: {e}")))?;
734
735 for (l2_num, hash) in l2_hash_entries {
736 executor
737 .precompile_ctx
738 .block
739 .cache_l2_block_hash(l2_num, hash);
740 }
741
742 let mut all_txs: Vec<ArbTransactionSigned> = Vec::new();
743
744 let l1_base_fee = input.l1_base_fee.unwrap_or(U256::ZERO);
746 let start_block_data = internal_tx::encode_start_block(
747 l1_base_fee,
748 l1_block_number,
749 l2_block_number,
750 time_passed,
751 );
752
753 let start_block_tx = create_internal_tx(chain_id, &start_block_data);
754 execute_and_commit_tx(&mut executor, &start_block_tx, "StartBlock")?;
755 all_txs.push(start_block_tx);
756
757 let pre_recovered: Vec<Option<ArbTransactionSigned>> = {
759 use rayon::prelude::*;
760 parsed_txs
761 .par_iter()
762 .map(|parsed| match parsed {
763 ParsedTransaction::InternalStartBlock { .. }
764 | ParsedTransaction::BatchPostingReport { .. } => None,
765 other => {
766 let signed = parsed_tx_to_signed(other, chain_id)?;
767 let _ = signed.recover_signer();
768 Some(signed)
769 }
770 })
771 .collect()
772 };
773
774 for (idx, parsed) in parsed_txs.iter().enumerate() {
776 match parsed {
777 ParsedTransaction::InternalStartBlock { .. } => {
778 continue;
780 }
781 ParsedTransaction::BatchPostingReport {
782 batch_timestamp,
783 batch_poster,
784 batch_number,
785 l1_base_fee_estimate,
786 extra_gas,
787 ..
788 } => {
789 let report_data =
792 if parent_arbos_version >= arb_chainspec::arbos_version::ARBOS_VERSION_50 {
793 let (length, non_zeros) = input.batch_data_stats.unwrap_or((0, 0));
795 internal_tx::encode_batch_posting_report_v2(
796 *batch_timestamp,
797 *batch_poster,
798 *batch_number,
799 length,
800 non_zeros,
801 *extra_gas,
802 *l1_base_fee_estimate,
803 )
804 } else {
805 let legacy_gas = input.batch_gas_cost.unwrap_or(0);
807 let batch_data_gas = legacy_gas.saturating_add(*extra_gas);
808 internal_tx::encode_batch_posting_report(
809 *batch_timestamp,
810 *batch_poster,
811 *batch_number,
812 batch_data_gas,
813 *l1_base_fee_estimate,
814 )
815 };
816 let report_tx = create_internal_tx(chain_id, &report_data);
817 execute_and_commit_tx(&mut executor, &report_tx, "BatchPostingReport")?;
818 all_txs.push(report_tx);
819 continue;
820 }
821 _ => {}
822 }
823
824 let signed_tx = match pre_recovered.get(idx).and_then(|s| s.clone()) {
825 Some(tx) => tx,
826 None => {
827 debug!(target: "block_producer", ?parsed, "Skipping unparseable transaction");
828 continue;
829 }
830 };
831
832 let recovered = match signed_tx.clone().try_into_recovered() {
833 Ok(r) => r,
834 Err(e) => {
835 warn!(target: "block_producer", error = %e, "Failed to recover tx sender, skipping");
836 continue;
837 }
838 };
839 let tx_hash = *signed_tx.tx_hash();
840 let (exec_outcome, hostio_records) = arb_rpc::stylus_tracer::with_trace_buffer(|| {
841 executor.execute_transaction_without_commit(recovered)
842 });
843 match exec_outcome {
844 Ok(result) => {
845 match executor.commit_transaction(result) {
846 Ok(_gas_used) => {
847 all_txs.push(signed_tx);
848 if !hostio_records.is_empty() {
849 arb_rpc::stylus_tracer::cache_trace(tx_hash, hostio_records);
850 }
851
852 loop {
857 let scheduled = executor.drain_scheduled_txs();
858 debug!(
859 target: "block_producer",
860 count = scheduled.len(),
861 "Drained scheduled txs"
862 );
863 if scheduled.is_empty() {
864 break;
865 }
866 for encoded in scheduled {
867 let retry_tx: Option<ArbTransactionSigned> =
868 ArbTransactionSigned::decode_2718(&mut &encoded[..]).ok();
869 if let Some(retry_tx) = retry_tx {
870 let retry_signed = retry_tx.clone();
871 let retry_hash = *retry_signed.tx_hash();
872 match retry_tx.try_into_recovered() {
873 Ok(recovered_retry) => {
874 let (retry_outcome, retry_records) =
875 arb_rpc::stylus_tracer::with_trace_buffer(
876 || {
877 executor
878 .execute_transaction_without_commit(
879 recovered_retry,
880 )
881 },
882 );
883 match retry_outcome {
884 Ok(retry_result) => {
885 match executor
886 .commit_transaction(retry_result)
887 {
888 Ok(_) => {
889 all_txs.push(retry_signed);
890 if !retry_records.is_empty() {
891 arb_rpc::stylus_tracer::cache_trace(
892 retry_hash,
893 retry_records,
894 );
895 }
896 }
897 Err(e) => {
898 warn!(
899 target: "block_producer",
900 error = %e,
901 "Failed to commit auto-redeem tx"
902 );
903 }
904 }
905 }
906 Err(e) => {
907 warn!(
908 target: "block_producer",
909 error = %e,
910 "Auto-redeem tx execution failed"
911 );
912 }
913 }
914 }
915 Err(e) => {
916 warn!(
917 target: "block_producer",
918 error = %e,
919 "Failed to recover auto-redeem tx sender"
920 );
921 }
922 }
923 }
924 }
925 }
926 }
927 Err(e) => {
928 warn!(target: "block_producer", error = %e, "Failed to commit transaction");
929 }
930 }
931 }
932 Err(ref e) if e.to_string().contains("block gas limit reached") => {
933 break;
934 }
935 Err(e) => {
936 warn!(target: "block_producer", error = %e, "Transaction execution failed, skipping");
937 }
938 }
939 }
940
941 let zombie_accounts = executor.zombie_accounts().clone();
942 let finalise_deleted = executor.finalise_deleted().clone();
943
944 let (_, exec_result) = executor
945 .finish()
946 .map_err(|e| BlockProducerError::Execution(format!("finish: {e}")))?;
947
948 let receipts: Vec<arb_primitives::ArbReceipt> = exec_result.receipts;
949
950 db.merge_transitions(BundleRetention::Reverts);
951 let mut bundle = db.take_bundle();
952
953 augment_bundle_from_cache(&mut bundle, &db.cache, &*state_provider)?;
954
955 let keccak_empty_hash = alloy_primitives::B256::from(alloy_primitives::keccak256([]));
957 for addr in &finalise_deleted {
958 if zombie_accounts.contains(addr) {
959 continue;
960 }
961 if bundle.state.contains_key(addr) {
962 let existed_before = state_provider.basic_account(addr).ok().flatten().is_some();
963 if existed_before {
964 let still_empty = bundle
968 .state
969 .get(addr)
970 .and_then(|a| a.info.as_ref())
971 .is_none_or(|info| {
972 info.nonce == 0
973 && info.balance.is_zero()
974 && info.code_hash == keccak_empty_hash
975 });
976 if still_empty && let Some(bundle_acct) = bundle.state.get_mut(addr) {
977 bundle_acct.info = None;
978 }
979 } else {
980 let still_empty = bundle
981 .state
982 .get(addr)
983 .and_then(|a| a.info.as_ref())
984 .is_none_or(|info| {
985 info.nonce == 0
986 && info.balance.is_zero()
987 && info.code_hash == keccak_empty_hash
988 });
989 if still_empty {
990 bundle.state.remove(addr);
991 }
992 }
993 continue;
994 }
995 if let Ok(Some(acct)) = state_provider.basic_account(addr) {
996 let was_originally_empty = acct.balance.is_zero()
997 && acct.nonce == 0
998 && acct.bytecode_hash.is_none_or(|h| h == keccak_empty_hash);
999 if was_originally_empty {
1000 continue;
1001 }
1002 bundle.state.insert(
1003 *addr,
1004 revm_database::BundleAccount {
1005 info: None, original_info: None,
1007 storage: Default::default(),
1008 status: revm_database::AccountStatus::Changed,
1009 },
1010 );
1011 }
1012 }
1013
1014 filter_unchanged_storage(&mut bundle);
1015 delete_empty_accounts(&mut bundle, &zombie_accounts, &*state_provider);
1016
1017 let hashed_state =
1018 HashedPostState::from_bundle_state::<reth_trie_common::KeccakKeyHasher>(bundle.state());
1019
1020 let (state_root, trie_updates) = {
1021 let acc_arc = self.accumulated_trie_input.lock().clone();
1022 let flushing_arc = self.flushing_trie_input.lock().clone();
1023
1024 let block_state_sorted = hashed_state.clone().into_sorted();
1025 let prefix_sets = block_state_sorted.construct_prefix_sets().freeze();
1026
1027 let mut new_acc_state = (*acc_arc.state).clone();
1028 new_acc_state.extend_ref_and_sort(&block_state_sorted);
1029 let new_acc_state_arc = Arc::new(new_acc_state);
1030
1031 let (overlay_state_arc, overlay_nodes_arc) = if let Some(f) = &flushing_arc {
1032 let mut s = (*f.state).clone();
1033 s.extend_ref_and_sort(&new_acc_state_arc);
1034 let mut n = (*f.nodes).clone();
1035 n.extend_ref_and_sort(&acc_arc.nodes);
1036 (Arc::new(s), Arc::new(n))
1037 } else {
1038 (Arc::clone(&new_acc_state_arc), Arc::clone(&acc_arc.nodes))
1039 };
1040
1041 let overlay = Arc::new(TrieInputSorted::new(
1042 overlay_nodes_arc,
1043 overlay_state_arc,
1044 Default::default(),
1045 ));
1046
1047 let (root, updates) =
1048 crate::launcher::compute_parallel_state_root(overlay, prefix_sets)
1049 .map_err(|e| BlockProducerError::Execution(format!("state root: {e}")))?;
1050
1051 let mut new_acc_nodes = (*acc_arc.nodes).clone();
1052 new_acc_nodes.extend_ref_and_sort(&updates.clone_into_sorted());
1053 *self.accumulated_trie_input.lock() = Arc::new(TrieInputSorted::new(
1054 Arc::new(new_acc_nodes),
1055 new_acc_state_arc,
1056 Default::default(),
1057 ));
1058
1059 (root, updates)
1060 };
1061
1062 let arb_info =
1064 derive_header_info_from_state(state_provider.as_ref(), &bundle, input.sender)?;
1065
1066 let final_mix_hash = arb_info
1067 .as_ref()
1068 .map(|info| info.compute_mix_hash())
1069 .unwrap_or(provisional_mix_hash);
1070
1071 let extra_data: Bytes = arb_info
1072 .as_ref()
1073 .map(|info| {
1074 let mut data = info.send_root.to_vec();
1075 data.resize(32, 0);
1076 data.into()
1077 })
1078 .unwrap_or_else(|| {
1079 let mut data = parent_extra.clone();
1080 data.resize(32, 0);
1081 data.into()
1082 });
1083
1084 let send_root = arb_info
1085 .as_ref()
1086 .map(|info| info.send_root)
1087 .unwrap_or_else(|| {
1088 if parent_extra.len() >= 32 {
1089 B256::from_slice(&parent_extra[..32])
1090 } else {
1091 B256::ZERO
1092 }
1093 });
1094
1095 let gas_used = exec_result.gas_used;
1097 let logs_bloom_val = logs_bloom(receipts.iter().flat_map(|r| r.logs()));
1098
1099 let transactions_root =
1100 proofs::calculate_transaction_root::<ArbTransactionSigned>(&all_txs);
1101 let receipts_root = proofs::calculate_receipt_root(
1102 &receipts
1103 .iter()
1104 .map(|r| r.with_bloom_ref())
1105 .collect::<Vec<_>>(),
1106 );
1107
1108 let header = Header {
1109 parent_hash: parent_header.hash(),
1110 ommers_hash: EMPTY_OMMER_ROOT_HASH,
1111 beneficiary: input.sender,
1112 state_root,
1113 transactions_root,
1114 receipts_root,
1115 withdrawals_root: None,
1116 logs_bloom: logs_bloom_val,
1117 timestamp,
1118 mix_hash: final_mix_hash,
1119 nonce: B64::from(input.delayed_messages_read.to_be_bytes()),
1120 base_fee_per_gas: l2_base_fee,
1121 number: l2_block_number,
1122 gas_limit: parent_header.gas_limit(),
1123 difficulty: U256::from(1),
1124 gas_used,
1125 extra_data,
1126 parent_beacon_block_root: None,
1127 blob_gas_used: None,
1128 excess_blob_gas: None,
1129 requests_hash: None,
1130 };
1131
1132 let block = Block::<ArbTransactionSigned> {
1133 header,
1134 body: BlockBody {
1135 transactions: all_txs,
1136 ommers: Default::default(),
1137 withdrawals: None,
1138 },
1139 };
1140
1141 let sealed = reth_primitives_traits::SealedBlock::seal_slow(block);
1142 let block_hash = sealed.hash();
1143
1144 self.extend_cached_overlay(block_hash, &bundle);
1145 self.extend_cached_prestate(block_hash, &bundle);
1146
1147 {
1149 use alloy_evm::block::BlockExecutionResult;
1150 use reth_chain_state::ComputedTrieData;
1151 use reth_execution_types::BlockExecutionOutput;
1152 use reth_primitives_traits::RecoveredBlock;
1153
1154 let recovered = Arc::new(RecoveredBlock::new_sealed(sealed.clone(), vec![]));
1155 let exec_output = Arc::new(BlockExecutionOutput {
1156 state: bundle,
1157 result: BlockExecutionResult {
1158 receipts,
1159 requests: Default::default(),
1160 gas_used,
1161 blob_gas_used: 0,
1162 },
1163 });
1164 let computed = ComputedTrieData {
1165 hashed_state: Arc::new(hashed_state.into_sorted()),
1166 trie_updates: Arc::new(trie_updates.into_sorted()),
1167 anchored_trie_input: None,
1168 };
1169 let executed = ExecutedBlock::new(recovered, exec_output, computed);
1170
1171 self.in_memory_state
1172 .update_chain(NewCanonicalChain::Commit {
1173 new: vec![executed],
1174 });
1175
1176 let sealed_header = SealedHeader::new(sealed.header().clone(), sealed.hash());
1177 self.in_memory_state.set_canonical_head(sealed_header);
1178 }
1179
1180 self.head_block_num.store(l2_block_number, Ordering::SeqCst);
1181
1182 let num_txs = sealed.body().transactions.len();
1183 {
1185 self.metrics.head_block.set(l2_block_number as f64);
1186 self.metrics.blocks_produced_total.increment(1);
1187 self.metrics.gas_processed_total.increment(gas_used);
1188 self.metrics
1189 .transactions_processed_total
1190 .increment(num_txs as u64);
1191 }
1192
1193 let since_flush = self.blocks_since_flush.fetch_add(1, Ordering::SeqCst) + 1;
1194 let should_flush = self.scheduler.lock().should_flush(since_flush);
1195 if should_flush && !self.pending_flush.load(Ordering::SeqCst) {
1196 self.start_async_flush();
1197 }
1198
1199 info!(
1200 target: "block_producer",
1201 block_num = l2_block_number,
1202 ?block_hash,
1203 ?send_root,
1204 ?state_root,
1205 num_txs,
1206 gas_used,
1207 "Produced block"
1208 );
1209
1210 Ok(ProducedBlock {
1211 block_hash,
1212 send_root,
1213 })
1214 }
1215
1216 fn start_async_flush(&self) {
1218 let mut blocks: Vec<ExecutedBlock<ArbPrimitives>> = Vec::new();
1219 if let Some(head_state) = self.in_memory_state.head_state() {
1220 for block_state in head_state.chain() {
1221 blocks.push(block_state.block().clone());
1222 }
1223 }
1224 blocks.reverse();
1225
1226 let Some(last) = blocks.last() else {
1227 return;
1228 };
1229 let last_num_hash = alloy_eips::BlockNumHash::new(
1230 last.recovered_block().number(),
1231 last.recovered_block().hash(),
1232 );
1233
1234 let current = std::mem::take(&mut *self.accumulated_trie_input.lock());
1236 *self.flushing_trie_input.lock() = Some(current);
1237
1238 self.blocks_since_flush.store(0, Ordering::SeqCst);
1239 self.pending_flush.store(true, Ordering::SeqCst);
1240
1241 let count = blocks.len();
1242 crate::launcher::start_flush(crate::launcher::FlushRequest {
1243 blocks,
1244 last_num_hash,
1245 });
1246
1247 debug!(
1248 target: "block_producer",
1249 count,
1250 last_block = last_num_hash.number,
1251 "Started async flush"
1252 );
1253 }
1254}
1255
1256#[async_trait::async_trait]
1257impl<Provider> BlockProducer for ArbBlockProducer<Provider>
1258where
1259 Provider: BlockNumReader
1260 + BlockReaderIdExt
1261 + HeaderProvider<Header = Header>
1262 + StateProviderFactory
1263 + Send
1264 + Sync
1265 + 'static,
1266{
1267 fn cache_init_message(&self, l2_msg: &[u8]) -> Result<(), BlockProducerError> {
1268 let init_msg = parse_init_message(l2_msg)
1269 .map_err(|e| BlockProducerError::Parse(format!("init message: {e}")))?;
1270
1271 info!(
1272 target: "block_producer",
1273 chain_id = %init_msg.chain_id,
1274 initial_l1_base_fee = %init_msg.initial_l1_base_fee,
1275 "Cached Init message params"
1276 );
1277
1278 *self.cached_init.lock() = Some(init_msg);
1279 Ok(())
1280 }
1281
1282 async fn produce_block(
1283 &self,
1284 msg_idx: u64,
1285 input: BlockProductionInput,
1286 ) -> Result<ProducedBlock, BlockProducerError> {
1287 let _lock = self.produce_lock.lock().await;
1288
1289 let head_num = self.head_block_number()?;
1291 let expected_block = head_num + 1;
1292 let actual_block = msg_idx;
1293
1294 if expected_block != actual_block {
1295 return Err(BlockProducerError::Unexpected(format!(
1296 "Expected block {expected_block} but got msg_idx {msg_idx} (block {actual_block})"
1297 )));
1298 }
1299
1300 let chain_id = self.chain_spec.chain().id();
1302
1303 let parsed_txs = parse_l2_transactions(
1304 input.kind,
1305 input.sender,
1306 &input.l2_msg,
1307 input.request_id,
1308 input.l1_base_fee,
1309 chain_id,
1310 )
1311 .unwrap_or_else(|e| {
1312 warn!(target: "block_producer", error=%e, "Error parsing L2 message, treating as empty");
1313 vec![]
1314 });
1315
1316 debug!(
1317 target: "block_producer",
1318 msg_idx,
1319 kind = input.kind,
1320 num_txs = parsed_txs.len(),
1321 "Parsed L1 message"
1322 );
1323
1324 self.apply_backpressure().await;
1325 self.produce_block_with_execution(&input, parsed_txs)
1326 }
1327
1328 async fn reset_to_block(&self, target_block_number: u64) -> Result<(), BlockProducerError> {
1329 let _lock = self.produce_lock.lock().await;
1330 let current = self.head_block_num.load(Ordering::SeqCst);
1331 if target_block_number > current {
1332 return Err(BlockProducerError::Unexpected(format!(
1333 "reset target {target_block_number} > current head {current}"
1334 )));
1335 }
1336 if target_block_number == current {
1337 return Ok(());
1338 }
1339
1340 let header = self
1341 .provider
1342 .sealed_header_by_number_or_tag(BlockNumberOrTag::Number(target_block_number))
1343 .map_err(|e| BlockProducerError::StateAccess(e.to_string()))?
1344 .ok_or_else(|| {
1345 BlockProducerError::Unexpected(format!(
1346 "reset target block {target_block_number} not found"
1347 ))
1348 })?;
1349
1350 if self.pending_flush.load(Ordering::SeqCst)
1352 && let Some(result) = crate::launcher::try_flush_result()
1353 {
1354 self.in_memory_state
1355 .remove_persisted_blocks(result.last_num_hash);
1356 *self.flushing_trie_input.lock() = None;
1357 self.pending_flush.store(false, Ordering::SeqCst);
1358 }
1359
1360 let mut old_blocks: Vec<reth_chain_state::ExecutedBlock<ArbPrimitives>> = Vec::new();
1365 for bn in (target_block_number + 1)..=current {
1366 if let Some(state) = self.in_memory_state.state_by_number(bn) {
1367 old_blocks.push(state.block());
1368 }
1369 }
1370
1371 if !old_blocks.is_empty() {
1373 self.in_memory_state
1374 .update_chain(reth_chain_state::NewCanonicalChain::Reorg {
1375 new: Vec::new(),
1376 old: old_blocks,
1377 });
1378 }
1379
1380 self.invalidate_cached_overlay();
1381 self.invalidate_cached_prestate();
1382
1383 self.in_memory_state.set_canonical_head(header.clone());
1386
1387 self.head_block_num
1390 .store(target_block_number, Ordering::SeqCst);
1391
1392 if let Some(rx) = crate::launcher::start_unwind(target_block_number) {
1395 match rx.recv() {
1396 Ok(Ok(())) => {}
1397 Ok(Err(e)) => {
1398 return Err(BlockProducerError::Storage(format!(
1399 "unwind above {target_block_number}: {e}"
1400 )));
1401 }
1402 Err(e) => {
1403 return Err(BlockProducerError::Storage(format!(
1404 "unwind channel closed: {e}"
1405 )));
1406 }
1407 }
1408 }
1409
1410 *self.accumulated_trie_input.lock() = Arc::new(TrieInputSorted::default());
1412
1413 info!(
1414 target: "block_producer",
1415 target = target_block_number,
1416 hash = %header.hash(),
1417 old_count = current - target_block_number,
1418 "reset head"
1419 );
1420 Ok(())
1421 }
1422
1423 fn set_finality(
1424 &self,
1425 safe: Option<alloy_primitives::B256>,
1426 finalized: Option<alloy_primitives::B256>,
1427 validated: Option<alloy_primitives::B256>,
1428 ) -> Result<(), BlockProducerError> {
1429 let mut f = self.finality.lock();
1430 if safe.is_some() {
1431 f.safe = safe;
1432 }
1433 if finalized.is_some() {
1434 f.finalized = finalized;
1435 }
1436 if validated.is_some() {
1437 f.validated = validated;
1438 }
1439 drop(f);
1440
1441 if let Some(h) = safe
1445 && let Ok(Some(sealed)) = self.provider.sealed_header_by_hash(h)
1446 {
1447 self.in_memory_state.set_safe(sealed);
1448 }
1449 if let Some(h) = finalized
1450 && let Ok(Some(sealed)) = self.provider.sealed_header_by_hash(h)
1451 {
1452 self.in_memory_state.set_finalized(sealed);
1453 }
1454 if let Some(h) = validated
1458 && let Some(w) = self.validated_watcher.lock().as_ref()
1459 {
1460 *w.write() = h;
1461 }
1462 Ok(())
1463 }
1464
1465 fn attach_validated_watcher(&self, watcher: Arc<parking_lot::RwLock<alloy_primitives::B256>>) {
1466 *self.validated_watcher.lock() = Some(watcher);
1467 }
1468}
1469
1470fn create_internal_tx(chain_id: u64, data: &[u8]) -> ArbTransactionSigned {
1476 use arb_primitives::signed_tx::ArbTypedTransaction;
1477 let tx = ArbTypedTransaction::Internal(ArbInternalTx {
1478 chain_id: U256::from(chain_id),
1479 data: Bytes::copy_from_slice(data),
1480 });
1481 let sig = alloy_primitives::Signature::new(U256::ZERO, U256::ZERO, false);
1482 ArbTransactionSigned::new_unhashed(tx, sig)
1483}
1484
1485fn execute_and_commit_tx<E>(
1487 executor: &mut E,
1488 tx: &ArbTransactionSigned,
1489 label: &str,
1490) -> Result<(), BlockProducerError>
1491where
1492 E: BlockExecutor<Transaction = ArbTransactionSigned>,
1493{
1494 let recovered = tx
1495 .clone()
1496 .try_into_recovered()
1497 .map_err(|e| BlockProducerError::Execution(format!("{label} recovery: {e}")))?;
1498
1499 let result = executor
1500 .execute_transaction_without_commit(recovered)
1501 .map_err(|e| BlockProducerError::Execution(format!("{label} execution: {e}")))?;
1502
1503 executor
1504 .commit_transaction(result)
1505 .map_err(|e| BlockProducerError::Execution(format!("{label} commit: {e}")))?;
1506
1507 Ok(())
1508}
1509
1510fn compute_mix_hash(send_count: u64, l1_block_number: u64, arbos_version: u64) -> B256 {
1511 arbos::header::compute_arbos_mixhash(send_count, l1_block_number, arbos_version, false)
1512}
1513
1514fn monotonic_l1_block_number(reported: u64, parent_mix_hash: &B256) -> u64 {
1517 reported.max(l1_block_number_from_mix_hash(parent_mix_hash))
1518}
1519
1520fn delete_empty_accounts(
1522 bundle: &mut BundleState,
1523 zombie_accounts: &rustc_hash::FxHashSet<Address>,
1524 state_provider: &dyn StateProvider,
1525) {
1526 let keccak_empty = alloy_primitives::B256::from(alloy_primitives::keccak256([]));
1527 let mut to_remove = Vec::new();
1528 for (addr, account) in bundle.state.iter_mut() {
1529 if let Some(ref info) = account.info {
1530 let is_empty =
1531 info.nonce == 0 && info.balance.is_zero() && info.code_hash == keccak_empty;
1532 if is_empty && !zombie_accounts.contains(addr) {
1533 let existed_before = state_provider.basic_account(addr).ok().flatten().is_some();
1534 if existed_before {
1535 debug!(
1536 target: "block_producer",
1537 addr = ?addr,
1538 "EIP-161: deleting empty account from state"
1539 );
1540 account.info = None;
1541 } else {
1542 to_remove.push(*addr);
1543 }
1544 }
1545 }
1546 }
1547 for addr in to_remove {
1548 bundle.state.remove(&addr);
1549 }
1550}
1551
1552fn filter_unchanged_storage(bundle: &mut BundleState) {
1554 for (_addr, account) in bundle.state.iter_mut() {
1555 account
1556 .storage
1557 .retain(|_key, slot| slot.present_value != slot.previous_or_original_value);
1558 }
1559}
1560
1561fn derive_header_info_from_state(
1563 state_provider: &dyn StateProvider,
1564 bundle_state: &BundleState,
1565 coinbase: Address,
1566) -> Result<Option<ArbHeaderInfo>, BlockProducerError> {
1567 let read_slot = |addr: Address, slot: B256| {
1568 if let Some(account) = bundle_state.state.get(&addr) {
1569 let slot_u256 = U256::from_be_bytes(slot.0);
1570 if let Some(storage_slot) = account.storage.get(&slot_u256) {
1571 return Ok(Some(storage_slot.present_value));
1572 }
1573 }
1574 state_provider.storage(addr, slot)
1575 };
1576
1577 derive_arb_header_info(&read_slot, coinbase)
1578 .map_err(|e| BlockProducerError::Storage(e.to_string()))
1579}
1580
1581fn augment_bundle_from_cache(
1583 bundle: &mut BundleState,
1584 cache: &revm_database::CacheState,
1585 state_provider: &dyn StateProvider,
1586) -> Result<(), BlockProducerError> {
1587 use revm_database::states::plain_account::StorageSlot;
1588
1589 for (addr, cache_acct) in &cache.accounts {
1590 let current_info = cache_acct.account.as_ref().map(|a| a.info.clone());
1591 let current_storage = cache_acct
1592 .account
1593 .as_ref()
1594 .map(|a| &a.storage)
1595 .cloned()
1596 .unwrap_or_default();
1597
1598 if let Some(bundle_acct) = bundle.state.get_mut(addr) {
1599 bundle_acct.info = current_info;
1601
1602 for (key, value) in ¤t_storage {
1603 if let Some(slot) = bundle_acct.storage.get_mut(key) {
1604 slot.present_value = *value;
1605 } else {
1606 let original_value = state_provider
1608 .storage(*addr, B256::from(*key))
1609 .map_err(|e| BlockProducerError::Storage(e.to_string()))?
1610 .unwrap_or(U256::ZERO);
1611 if *value != original_value {
1612 bundle_acct.storage.insert(
1613 *key,
1614 StorageSlot {
1615 previous_or_original_value: original_value,
1616 present_value: *value,
1617 },
1618 );
1619 }
1620 }
1621 }
1622 } else {
1623 let original = state_provider
1625 .basic_account(addr)
1626 .map_err(|e| BlockProducerError::Storage(e.to_string()))?;
1627
1628 let info_changed = match (&original, ¤t_info) {
1629 (None, None) => false,
1630 (Some(_), None) | (None, Some(_)) => true,
1631 (Some(orig), Some(curr)) => {
1632 orig.balance != curr.balance
1633 || orig.nonce != curr.nonce
1634 || orig
1635 .bytecode_hash
1636 .unwrap_or(alloy_primitives::KECCAK256_EMPTY)
1637 != curr.code_hash
1638 }
1639 };
1640
1641 let mut storage_changes: alloy_primitives::map::HashMap<U256, StorageSlot> =
1642 alloy_primitives::map::HashMap::default();
1643 for (key, value) in ¤t_storage {
1644 let original_value = state_provider
1645 .storage(*addr, B256::from(*key))
1646 .map_err(|e| BlockProducerError::Storage(e.to_string()))?
1647 .unwrap_or(U256::ZERO);
1648 if original_value != *value {
1649 storage_changes.insert(
1650 *key,
1651 StorageSlot {
1652 previous_or_original_value: original_value,
1653 present_value: *value,
1654 },
1655 );
1656 }
1657 }
1658
1659 if info_changed || !storage_changes.is_empty() {
1660 let original_info = original.as_ref().map(|a| revm::state::AccountInfo {
1661 balance: a.balance,
1662 nonce: a.nonce,
1663 code_hash: a.bytecode_hash.unwrap_or(alloy_primitives::KECCAK256_EMPTY),
1664 code: None,
1665 account_id: None,
1666 });
1667
1668 let status = if original.is_some() {
1669 revm_database::AccountStatus::Changed
1670 } else {
1671 revm_database::AccountStatus::InMemoryChange
1672 };
1673
1674 bundle.state.insert(
1675 *addr,
1676 revm_database::BundleAccount {
1677 info: current_info,
1678 original_info,
1679 storage: storage_changes,
1680 status,
1681 },
1682 );
1683 }
1684 }
1685 }
1686 Ok(())
1687}
1688
1689#[cfg(test)]
1690mod tests {
1691 use arbos::header::compute_arbos_mixhash;
1692
1693 use super::*;
1694
1695 #[test]
1696 fn l1_block_number_clamps_to_parent() {
1697 let parent = compute_arbos_mixhash(0, 10_538_022, 51, false);
1698 assert_eq!(monotonic_l1_block_number(10_537_967, &parent), 10_538_022);
1700 assert_eq!(monotonic_l1_block_number(10_538_099, &parent), 10_538_099);
1702 assert_eq!(monotonic_l1_block_number(10_538_022, &parent), 10_538_022);
1704 }
1705}