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