1use std::sync::Arc;
7
8use alloy_consensus::BlockHeader;
9use alloy_primitives::B256;
10use alloy_rpc_types_eth::BlockNumberOrTag;
11use jsonrpsee::{core::RpcResult, proc_macros::rpc};
12use reth_provider::{BlockNumReader, BlockReaderIdExt, HeaderProvider};
13
14use crate::{
15 stylus_tracer::HostioTraceInfo, ArbBlockInfo, ArbMaintenanceStatus, NumberAndBlockMetadata,
16};
17
18#[rpc(server, namespace = "arb")]
20pub trait ArbApi {
21 #[method(name = "maintenanceStatus")]
23 fn maintenance_status(&self) -> RpcResult<ArbMaintenanceStatus>;
24
25 #[method(name = "checkPublisherHealth")]
28 fn check_publisher_health(&self) -> RpcResult<()>;
29
30 #[method(name = "getBlockInfo")]
32 async fn get_block_info(&self, block_num: u64) -> RpcResult<ArbBlockInfo>;
33
34 #[method(name = "getRawBlockMetadata")]
37 async fn get_raw_block_metadata(
38 &self,
39 from_block: BlockNumberOrTag,
40 to_block: BlockNumberOrTag,
41 ) -> RpcResult<Vec<NumberAndBlockMetadata>>;
42
43 #[method(name = "traceStylusHostio")]
46 async fn trace_stylus_hostio(&self, tx_hash: B256) -> RpcResult<Vec<HostioTraceInfo>>;
47
48 #[method(name = "getValidatedBlock")]
50 fn get_validated_block(&self) -> RpcResult<B256>;
51
52 #[method(name = "getL1Confirmations")]
55 async fn get_l1_confirmations(&self, block_num: u64) -> RpcResult<u64>;
56
57 #[method(name = "findBatchContainingBlock")]
60 async fn find_batch_containing_block(&self, block_num: u64) -> RpcResult<u64>;
61}
62
63pub struct ArbApiHandler<Provider> {
64 provider: Provider,
65 maintenance_status: Arc<parking_lot::RwLock<ArbMaintenanceStatus>>,
66 validated_block: Arc<parking_lot::RwLock<B256>>,
67}
68
69impl<Provider> ArbApiHandler<Provider> {
70 pub fn new(provider: Provider) -> Self {
72 Self {
73 provider,
74 maintenance_status: Arc::new(parking_lot::RwLock::new(ArbMaintenanceStatus::default())),
75 validated_block: Arc::new(parking_lot::RwLock::new(B256::ZERO)),
76 }
77 }
78
79 pub fn maintenance_status_handle(&self) -> Arc<parking_lot::RwLock<ArbMaintenanceStatus>> {
81 self.maintenance_status.clone()
82 }
83
84 pub fn validated_block_handle(&self) -> Arc<parking_lot::RwLock<B256>> {
87 self.validated_block.clone()
88 }
89}
90
91#[async_trait::async_trait]
92impl<Provider> ArbApiServer for ArbApiHandler<Provider>
93where
94 Provider: BlockNumReader + BlockReaderIdExt + HeaderProvider + 'static,
95{
96 fn maintenance_status(&self) -> RpcResult<ArbMaintenanceStatus> {
97 Ok(self.maintenance_status.read().clone())
98 }
99
100 fn check_publisher_health(&self) -> RpcResult<()> {
101 Err(jsonrpsee::types::ErrorObject::owned(
102 jsonrpsee::types::error::INTERNAL_ERROR_CODE,
103 "publishing transactions not supported by this endpoint",
104 None::<()>,
105 ))
106 }
107
108 async fn get_block_info(&self, block_num: u64) -> RpcResult<ArbBlockInfo> {
109 let header = self
110 .provider
111 .sealed_header_by_number_or_tag(BlockNumberOrTag::Number(block_num))
112 .map_err(|e| {
113 jsonrpsee::types::ErrorObject::owned(
114 jsonrpsee::types::error::INTERNAL_ERROR_CODE,
115 e.to_string(),
116 None::<()>,
117 )
118 })?
119 .ok_or_else(|| {
120 jsonrpsee::types::ErrorObject::owned(
121 jsonrpsee::types::error::INVALID_PARAMS_CODE,
122 format!("block {block_num} not found"),
123 None::<()>,
124 )
125 })?;
126
127 let mix = header.mix_hash().unwrap_or_default();
128 let extra = header.extra_data();
129
130 let send_count = u64::from_be_bytes(mix.0[0..8].try_into().unwrap_or_default());
131 let l1_block_number = u64::from_be_bytes(mix.0[8..16].try_into().unwrap_or_default());
132 let arbos_format_version = u64::from_be_bytes(mix.0[16..24].try_into().unwrap_or_default());
133
134 let send_root = if extra.len() >= 32 {
135 B256::from_slice(&extra[..32])
136 } else {
137 B256::ZERO
138 };
139
140 Ok(ArbBlockInfo {
141 l1_block_number,
142 arbos_format_version,
143 send_count,
144 send_root,
145 })
146 }
147
148 async fn trace_stylus_hostio(
149 &self,
150 tx_hash: B256,
151 ) -> RpcResult<Vec<crate::stylus_tracer::HostioTraceInfo>> {
152 Ok(crate::stylus_tracer::take_cached_trace(tx_hash))
153 }
154
155 fn get_validated_block(&self) -> RpcResult<B256> {
156 Ok(*self.validated_block.read())
157 }
158
159 async fn get_l1_confirmations(&self, _block_num: u64) -> RpcResult<u64> {
160 Ok(0)
161 }
162
163 async fn find_batch_containing_block(&self, block_num: u64) -> RpcResult<u64> {
164 Err(jsonrpsee::types::ErrorObject::owned(
165 jsonrpsee::types::error::INTERNAL_ERROR_CODE,
166 format!("no batch index available for block {block_num}"),
167 None::<()>,
168 ))
169 }
170
171 async fn get_raw_block_metadata(
172 &self,
173 from_block: BlockNumberOrTag,
174 to_block: BlockNumberOrTag,
175 ) -> RpcResult<Vec<NumberAndBlockMetadata>> {
176 let internal = |e: alloy_rpc_types_eth::ConversionError| {
177 jsonrpsee::types::ErrorObject::owned(
178 jsonrpsee::types::error::INVALID_PARAMS_CODE,
179 e.to_string(),
180 None::<()>,
181 )
182 };
183 let provider_err = |e: reth_provider::ProviderError| {
184 jsonrpsee::types::ErrorObject::owned(
185 jsonrpsee::types::error::INTERNAL_ERROR_CODE,
186 e.to_string(),
187 None::<()>,
188 )
189 };
190 let best = self.provider.best_block_number().map_err(provider_err)?;
191 let resolve = |bn: BlockNumberOrTag| -> Result<u64, _> {
192 match bn {
193 BlockNumberOrTag::Number(n) => Ok(n),
194 BlockNumberOrTag::Earliest => Ok(0),
195 BlockNumberOrTag::Latest
196 | BlockNumberOrTag::Safe
197 | BlockNumberOrTag::Finalized
198 | BlockNumberOrTag::Pending => Ok(best),
199 }
200 .map_err(internal)
201 };
202 let from = resolve(from_block)?;
203 let to = resolve(to_block)?;
204 if from > to {
205 return Err(jsonrpsee::types::ErrorObject::owned(
206 jsonrpsee::types::error::INVALID_PARAMS_CODE,
207 format!("from_block ({from}) > to_block ({to})"),
208 None::<()>,
209 ));
210 }
211 const MAX_RANGE: u64 = 5_000;
212 if to.saturating_sub(from) + 1 > MAX_RANGE {
213 return Err(jsonrpsee::types::ErrorObject::owned(
214 jsonrpsee::types::error::INVALID_PARAMS_CODE,
215 format!("range exceeds {MAX_RANGE} blocks"),
216 None::<()>,
217 ));
218 }
219 let _ = (from, to, best);
222 Ok(Vec::new())
223 }
224}