arb_rpc/
nitro_execution.rs

1//! Nitro execution RPC namespace (`nitroexecution_*`).
2//!
3//! Implements the RPC interface that the Nitro consensus layer uses to drive
4//! block production on this execution client. The critical method is
5//! `digestMessage`, which takes an L1 incoming message with metadata and
6//! produces a block, returning the block hash and send root.
7
8use alloy_primitives::{Address, B256, U256};
9use jsonrpsee::{core::RpcResult, proc_macros::rpc};
10use serde::{Deserialize, Serialize};
11
12/// Deserializer for `Option<U256>` accepting hex string ("0x..."), decimal string
13/// ("12345"), or bare JSON number (12345). The canonical `*big.Int` marshals to
14/// a bare JSON number.
15mod opt_u256_dec_or_hex {
16    use alloy_primitives::U256;
17    use serde::{self, Deserialize, Deserializer, Serializer};
18
19    pub fn serialize<S>(value: &Option<U256>, serializer: S) -> Result<S::Ok, S::Error>
20    where
21        S: Serializer,
22    {
23        match value {
24            Some(v) => serde::Serialize::serialize(v, serializer),
25            None => serializer.serialize_none(),
26        }
27    }
28
29    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<U256>, D::Error>
30    where
31        D: Deserializer<'de>,
32    {
33        let v = serde_json::Value::deserialize(deserializer)?;
34        match v {
35            serde_json::Value::Null => Ok(None),
36            serde_json::Value::Number(n) => {
37                if let Some(u) = n.as_u64() {
38                    Ok(Some(U256::from(u)))
39                } else {
40                    let val = U256::from_str_radix(&n.to_string(), 10)
41                        .map_err(serde::de::Error::custom)?;
42                    Ok(Some(val))
43                }
44            }
45            serde_json::Value::String(s) if s.is_empty() => Ok(None),
46            serde_json::Value::String(s) => {
47                let val = if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
48                    U256::from_str_radix(hex, 16).map_err(serde::de::Error::custom)?
49                } else {
50                    U256::from_str_radix(&s, 10).map_err(serde::de::Error::custom)?
51                };
52                Ok(Some(val))
53            }
54            _ => Err(serde::de::Error::custom(
55                "expected number, string, or null for U256",
56            )),
57        }
58    }
59}
60
61// ---------------------------------------------------------------------------
62// RPC data types (JSON-serializable, matching the canonical JSON tags)
63// ---------------------------------------------------------------------------
64
65/// L1 incoming message header.
66/// Fields have explicit JSON tags (camelCase).
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct RpcL1IncomingMessageHeader {
69    pub kind: u8,
70    pub sender: Address,
71    #[serde(rename = "blockNumber")]
72    pub block_number: u64,
73    pub timestamp: u64,
74    #[serde(default, skip_serializing_if = "Option::is_none", rename = "requestId")]
75    pub request_id: Option<B256>,
76    #[serde(
77        default,
78        skip_serializing_if = "Option::is_none",
79        rename = "baseFeeL1",
80        with = "opt_u256_dec_or_hex"
81    )]
82    pub base_fee_l1: Option<U256>,
83}
84
85/// Batch data statistics for L1 cost estimation.
86/// Fields have explicit JSON tags (lowercase).
87#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct RpcBatchDataStats {
89    pub length: u64,
90    pub nonzeros: u64,
91}
92
93/// L1 incoming message containing header and L2 payload.
94/// Fields have explicit JSON tags (camelCase).
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct RpcL1IncomingMessage {
97    pub header: RpcL1IncomingMessageHeader,
98    /// Base64-encoded L2 message bytes.
99    #[serde(default, skip_serializing_if = "Option::is_none", rename = "l2Msg")]
100    pub l2_msg: Option<String>,
101    /// Legacy batch gas cost (for older batch posting reports).
102    #[serde(
103        default,
104        skip_serializing_if = "Option::is_none",
105        rename = "batchGasCost"
106    )]
107    pub batch_gas_cost: Option<u64>,
108    /// Batch data statistics (for newer batch posting reports).
109    #[serde(
110        default,
111        skip_serializing_if = "Option::is_none",
112        rename = "batchDataTokens"
113    )]
114    pub batch_data_tokens: Option<RpcBatchDataStats>,
115}
116
117/// Message with metadata, sent by the consensus layer to the execution client.
118/// Fields have explicit JSON tags (camelCase).
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct RpcMessageWithMetadata {
121    pub message: RpcL1IncomingMessage,
122    #[serde(rename = "delayedMessagesRead")]
123    pub delayed_messages_read: u64,
124}
125
126/// Extended message info including block hash and metadata.
127/// Uses PascalCase JSON serialization (no explicit JSON tags).
128#[derive(Debug, Clone, Serialize, Deserialize)]
129#[serde(rename_all = "PascalCase")]
130pub struct RpcMessageWithMetadataAndBlockInfo {
131    #[serde(rename = "MessageWithMeta")]
132    pub message: RpcMessageWithMetadata,
133    #[serde(default, skip_serializing_if = "Option::is_none")]
134    pub block_hash: Option<B256>,
135    #[serde(default, skip_serializing_if = "Option::is_none")]
136    pub block_metadata: Option<Vec<u8>>,
137}
138
139/// Result of block production: block hash and send root.
140/// Uses PascalCase JSON serialization (no explicit JSON tags).
141#[derive(Debug, Clone, Serialize, Deserialize)]
142#[serde(rename_all = "PascalCase")]
143pub struct RpcMessageResult {
144    pub block_hash: B256,
145    pub send_root: B256,
146}
147
148/// Finality data pushed from consensus.
149/// Uses PascalCase JSON serialization (no explicit JSON tags).
150#[derive(Debug, Clone, Serialize, Deserialize)]
151#[serde(rename_all = "PascalCase")]
152pub struct RpcFinalityData {
153    #[serde(default)]
154    pub msg_idx: u64,
155    #[serde(default)]
156    pub block_hash: B256,
157}
158
159/// Consensus sync data pushed from consensus.
160/// Uses PascalCase JSON serialization (no explicit JSON tags).
161#[derive(Debug, Clone, Serialize, Deserialize)]
162#[serde(rename_all = "PascalCase")]
163pub struct RpcConsensusSyncData {
164    pub synced: bool,
165    pub max_message_count: u64,
166    #[serde(default)]
167    pub sync_progress_map: Option<serde_json::Value>,
168    #[serde(default)]
169    pub updated_at: Option<String>,
170}
171
172/// Maintenance status.
173/// Uses PascalCase JSON serialization (no explicit JSON tags).
174#[derive(Debug, Clone, Default, Serialize, Deserialize)]
175#[serde(rename_all = "PascalCase")]
176pub struct RpcMaintenanceStatus {
177    pub is_running: bool,
178}
179
180// ---------------------------------------------------------------------------
181// RPC trait definition
182// ---------------------------------------------------------------------------
183
184/// Nitro execution RPC namespace.
185///
186/// This is the interface that Nitro's consensus layer calls to drive
187/// block production on the execution client.
188#[rpc(server, namespace = "nitroexecution")]
189pub trait NitroExecutionApi {
190    /// Process a message and produce a block.
191    #[method(name = "digestMessage")]
192    async fn digest_message(
193        &self,
194        msg_idx: u64,
195        message: RpcMessageWithMetadata,
196        message_for_prefetch: Option<RpcMessageWithMetadata>,
197    ) -> RpcResult<RpcMessageResult>;
198
199    /// Handle a chain reorg by rolling back and replaying messages.
200    #[method(name = "reorg")]
201    async fn reorg(
202        &self,
203        msg_idx_of_first_msg_to_add: u64,
204        new_messages: Vec<RpcMessageWithMetadataAndBlockInfo>,
205        old_messages: Vec<RpcMessageWithMetadata>,
206    ) -> RpcResult<Vec<RpcMessageResult>>;
207
208    /// Returns the current head message index.
209    #[method(name = "headMessageIndex")]
210    async fn head_message_index(&self) -> RpcResult<u64>;
211
212    /// Returns the block hash and send root for a given message index.
213    #[method(name = "resultAtMessageIndex")]
214    async fn result_at_message_index(&self, msg_idx: u64) -> RpcResult<RpcMessageResult>;
215
216    /// Updates finality information.
217    #[method(name = "setFinalityData")]
218    fn set_finality_data(
219        &self,
220        safe: Option<RpcFinalityData>,
221        finalized: Option<RpcFinalityData>,
222        validated: Option<RpcFinalityData>,
223    ) -> RpcResult<()>;
224
225    /// Updates consensus sync data.
226    #[method(name = "setConsensusSyncData")]
227    fn set_consensus_sync_data(&self, sync_data: RpcConsensusSyncData) -> RpcResult<()>;
228
229    /// Marks the feed start position.
230    #[method(name = "markFeedStart")]
231    fn mark_feed_start(&self, to: u64) -> RpcResult<()>;
232
233    /// Triggers maintenance operations.
234    #[method(name = "triggerMaintenance")]
235    async fn trigger_maintenance(&self) -> RpcResult<()>;
236
237    /// Checks if maintenance should be triggered.
238    #[method(name = "shouldTriggerMaintenance")]
239    async fn should_trigger_maintenance(&self) -> RpcResult<bool>;
240
241    /// Returns current maintenance status.
242    #[method(name = "maintenanceStatus")]
243    async fn maintenance_status(&self) -> RpcResult<RpcMaintenanceStatus>;
244
245    /// Returns the ArbOS version for a given message index.
246    #[method(name = "arbOSVersionForMessageIndex")]
247    async fn arbos_version_for_message_index(&self, msg_idx: u64) -> RpcResult<u64>;
248}