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}