arb_rpc/block_producer.rs
1//! Block producer trait for the `nitroexecution` RPC handler.
2//!
3//! Defines the interface for producing blocks from L1 incoming messages.
4//! The concrete implementation lives in `arb-node` where it has access
5//! to the full node infrastructure (database, EVM config, state).
6
7use std::sync::Arc;
8
9use alloy_primitives::B256;
10
11/// Result of producing a block.
12#[derive(Debug, Clone)]
13pub struct ProducedBlock {
14 /// Hash of the produced block.
15 pub block_hash: B256,
16 /// Send root from the block's extra_data.
17 pub send_root: B256,
18}
19
20/// Error type for block production.
21#[derive(Debug, thiserror::Error)]
22pub enum BlockProducerError {
23 #[error("state access: {0}")]
24 StateAccess(String),
25 #[error("execution: {0}")]
26 Execution(String),
27 #[error("storage: {0}")]
28 Storage(String),
29 #[error("parse: {0}")]
30 Parse(String),
31 #[error("unexpected: {0}")]
32 Unexpected(String),
33}
34
35/// Input for block production from an L1 incoming message.
36#[derive(Debug, Clone)]
37pub struct BlockProductionInput {
38 /// Message kind (L1MessageType_*).
39 pub kind: u8,
40 /// Message sender (poster address).
41 pub sender: alloy_primitives::Address,
42 /// L1 block number.
43 pub l1_block_number: u64,
44 /// L1 timestamp.
45 pub l1_timestamp: u64,
46 /// L1 request ID (for delayed messages).
47 pub request_id: Option<B256>,
48 /// L1 base fee.
49 pub l1_base_fee: Option<alloy_primitives::U256>,
50 /// L2 message payload (base64-decoded).
51 pub l2_msg: Vec<u8>,
52 /// Delayed messages read count.
53 pub delayed_messages_read: u64,
54 /// Legacy batch gas cost.
55 pub batch_gas_cost: Option<u64>,
56 /// Batch data stats (for newer batch posting reports).
57 pub batch_data_stats: Option<(u64, u64)>,
58}
59
60/// Trait for producing blocks from L1 messages.
61///
62/// Implemented by the node infrastructure where full database and EVM
63/// access is available.
64#[async_trait::async_trait]
65pub trait BlockProducer: Send + Sync + 'static {
66 /// Cache the Init message params for later use during block 1 execution.
67 ///
68 /// The Init message (Kind=11) does NOT produce a block. Its params are
69 /// applied during the first real block's pre-execution so that the
70 /// state root for block 1 includes both Init and execution changes.
71 fn cache_init_message(&self, l2_msg: &[u8]) -> Result<(), BlockProducerError>;
72
73 /// Produce a block from the given L1 incoming message.
74 ///
75 /// The implementation should:
76 /// 1. Parse the L1 message into transactions
77 /// 2. Open the state at the current head
78 /// 3. Execute transactions using the ArbOS pipeline
79 /// 4. Compute the state root
80 /// 5. Persist the block and state changes
81 /// 6. Return the block hash and send root
82 async fn produce_block(
83 &self,
84 msg_idx: u64,
85 input: BlockProductionInput,
86 ) -> Result<ProducedBlock, BlockProducerError>;
87
88 /// Reset the canonical chain head to the given block number.
89 ///
90 /// Used by `nitroexecution_reorg` to roll back state before replaying
91 /// divergent messages. The concrete implementation should truncate
92 /// blocks above `target_block_number` from the canonical chain,
93 /// making that block the new head. Receipts and transactions above
94 /// the target should be removed.
95 ///
96 /// Default implementation returns an "unsupported" error. Node
97 /// implementations that support reorg should override this.
98 async fn reset_to_block(&self, _target_block_number: u64) -> Result<(), BlockProducerError> {
99 Err(BlockProducerError::Unexpected(
100 "reset_to_block not supported by this producer".into(),
101 ))
102 }
103
104 /// Mark finality metadata (safe / finalized / validated block hashes)
105 /// on the canonical chain.
106 ///
107 /// Nitro's consensus layer calls `setFinalityData` periodically to
108 /// propagate finality information derived from L1 confirmations.
109 /// The execution client should store these markers so that RPC
110 /// queries like `eth_getBlockByNumber("finalized")` return the
111 /// correct block.
112 ///
113 /// Default impl is a no-op; node implementations override if they
114 /// support finality tracking.
115 fn set_finality(
116 &self,
117 _safe: Option<B256>,
118 _finalized: Option<B256>,
119 _validated: Option<B256>,
120 ) -> Result<(), BlockProducerError> {
121 Ok(())
122 }
123
124 /// Attach an external validated-block watcher. The producer should
125 /// write the current validated marker into the provided shared
126 /// slot on every `set_finality` call so RPC handlers (which hold
127 /// the same handle) can surface the value.
128 ///
129 /// No-op by default; concrete producers override.
130 fn attach_validated_watcher(&self, _watcher: Arc<parking_lot::RwLock<B256>>) {}
131}