1use alloy_dyn_abi::TypedData;
12use alloy_json_rpc::RpcObject;
13use alloy_primitives::{Address, Bytes, B256};
14use alloy_rpc_types::{
15 simulate::SimBlock,
16 state::{EvmOverrides, StateOverride},
17 BlockId, BlockOverrides, SeismicCallRequest, SeismicRawTxRequest,
18};
19use alloy_rpc_types_eth::{
20 simulate::{SimulatePayload, SimulatedBlock},
21 transaction::TransactionRequest,
22};
23use jsonrpsee::{
24 core::{async_trait, RpcResult},
25 proc_macros::rpc,
26};
27use reth_evm::ConfigureEvmEnv;
28use reth_node_core::node_config::NodeConfig;
29use reth_rpc_eth_api::{
30 helpers::{EthCall, EthTransactions, FullEthApi},
31 RpcBlock,
32};
33use reth_rpc_eth_types::{
34 utils::{recover_raw_transaction, recover_typed_data_request},
35 EthApiError,
36};
37use reth_tracing::tracing::*;
38use reth_transaction_pool::{PoolPooledTx, PoolTransaction, TransactionPool};
39use secp256k1::PublicKey;
40use seismic_enclave::{rpc::EnclaveApiClient, EnclaveClient};
41use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
42
43use crate::{error::SeismicApiError, utils::seismic_override_call_request};
44#[cfg_attr(not(feature = "client"), rpc(server, namespace = "seismic"))]
48#[cfg_attr(feature = "client", rpc(server, client, namespace = "seismic"))]
49pub trait SeismicApi {
50 #[method(name = "getTeePublicKey")]
52 async fn get_tee_public_key(&self) -> RpcResult<PublicKey>;
53}
54
55#[derive(Debug, Default)]
57pub struct SeismicApi {
58 enclave_client: EnclaveClient,
59}
60
61impl SeismicApi {
62 pub fn new<ChainSpec>(config: &NodeConfig<ChainSpec>) -> Self {
64 Self {
65 enclave_client: EnclaveClient::builder()
66 .addr(config.enclave.enclave_server_addr.to_string())
67 .port(config.enclave.enclave_server_port)
68 .timeout(std::time::Duration::from_secs(config.enclave.enclave_timeout))
69 .build(),
70 }
71 }
72
73 pub fn with_enclave_client(mut self, enclave_client: EnclaveClient) -> Self {
75 self.enclave_client = enclave_client;
76 self
77 }
78}
79
80#[async_trait]
81impl SeismicApiServer for SeismicApi {
82 async fn get_tee_public_key(&self) -> RpcResult<PublicKey> {
83 trace!(target: "rpc::seismic", "Serving seismic_getTeePublicKey");
84 self.enclave_client
85 .get_public_key()
86 .await
87 .map_err(|e| SeismicApiError::EnclaveError(e.to_string()).into())
88 }
89}
90
91pub const fn test_address() -> SocketAddr {
93 SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0))
94}
95
96#[cfg_attr(not(feature = "client"), rpc(server, namespace = "eth"))]
98#[cfg_attr(feature = "client", rpc(server, client, namespace = "eth"))]
99pub trait EthApiOverride<B: RpcObject> {
100 #[method(name = "signTypedData_v4")]
103 async fn sign_typed_data_v4(&self, address: Address, data: TypedData) -> RpcResult<String>;
104
105 #[method(name = "simulateV1")]
108 async fn simulate_v1(
109 &self,
110 opts: SimulatePayload<SeismicCallRequest>,
111 block_number: Option<BlockId>,
112 ) -> RpcResult<Vec<SimulatedBlock<B>>>;
113
114 #[method(name = "call")]
116 async fn call(
117 &self,
118 request: alloy_rpc_types::SeismicCallRequest,
119 block_number: Option<BlockId>,
120 state_overrides: Option<StateOverride>,
121 block_overrides: Option<Box<BlockOverrides>>,
122 ) -> RpcResult<Bytes>;
123
124 #[method(name = "sendRawTransaction")]
126 async fn send_raw_transaction(&self, bytes: SeismicRawTxRequest) -> RpcResult<B256>;
127}
128
129#[derive(Debug)]
131pub struct EthApiExt<Eth> {
132 eth_api: Eth,
133}
134
135impl<Eth> EthApiExt<Eth> {
136 pub const fn new(eth_api: Eth) -> Self {
138 Self { eth_api }
139 }
140}
141
142#[async_trait]
143impl<Eth> EthApiOverrideServer<RpcBlock<Eth::NetworkTypes>> for EthApiExt<Eth>
144where
145 Eth: FullEthApi,
146 jsonrpsee_types::error::ErrorObject<'static>: From<Eth::Error>,
147{
148 async fn sign_typed_data_v4(&self, from: Address, data: TypedData) -> RpcResult<String> {
150 trace!(target: "rpc::eth", "Serving eth_signTypedData_v4");
151 let signature = EthTransactions::sign_typed_data(&self.eth_api, &data, from)
152 .map_err(|err| err.into())?;
153 let signature = alloy_primitives::hex::encode(signature);
154 Ok(format!("0x{signature}"))
155 }
156
157 async fn simulate_v1(
159 &self,
160 payload: SimulatePayload<SeismicCallRequest>,
161 block_number: Option<BlockId>,
162 ) -> RpcResult<Vec<SimulatedBlock<RpcBlock<Eth::NetworkTypes>>>> {
163 trace!(target: "rpc::eth", "Serving eth_simulateV1");
164
165 let mut simulated_blocks = Vec::with_capacity(payload.block_state_calls.len());
166
167 for block in payload.block_state_calls {
168 let SimBlock { block_overrides, state_overrides, calls } = block;
169 let mut prepared_calls: Vec<TransactionRequest> = Vec::with_capacity(calls.len());
170
171 for call in calls {
172 let tx_request = match call {
173 alloy_rpc_types::SeismicCallRequest::TransactionRequest(_tx_request) => {
174 return Err(EthApiError::InvalidParams(
175 "Invalid Transaction Request".to_string(),
176 )
177 .into())
178 }
179
180 alloy_rpc_types::SeismicCallRequest::TypedData(typed_request) => {
181 let tx =
182 recover_typed_data_request::<PoolPooledTx<Eth::Pool>>(&typed_request)?
183 .map_transaction(
184 <Eth::Pool as TransactionPool>::Transaction::pooled_into_consensus,
185 );
186
187 TransactionRequest::from_transaction_with_sender(
188 tx.as_signed().clone(),
189 tx.signer(),
190 )
191 }
192
193 alloy_rpc_types::SeismicCallRequest::Bytes(bytes) => {
194 let tx = recover_raw_transaction::<PoolPooledTx<Eth::Pool>>(&bytes)?
195 .map_transaction(
196 <Eth::Pool as TransactionPool>::Transaction::pooled_into_consensus,
197 );
198
199 TransactionRequest::from_transaction_with_sender(
200 tx.as_signed().clone(),
201 tx.signer(),
202 )
203 }
204 };
205 prepared_calls.push(tx_request);
206 }
207
208 let prepared_block =
209 SimBlock { block_overrides, state_overrides, calls: prepared_calls };
210
211 simulated_blocks.push(prepared_block);
212 }
213
214 let result = EthCall::simulate_v1(
215 &self.eth_api,
216 SimulatePayload {
217 block_state_calls: simulated_blocks.clone(),
218 trace_transfers: payload.trace_transfers,
219 validation: payload.validation,
220 return_full_transactions: payload.return_full_transactions,
221 },
222 block_number,
223 )
224 .await;
225 let mut result = result.unwrap();
226
227 for (block, result) in simulated_blocks.iter().zip(result.iter_mut()) {
228 let SimBlock { calls, .. } = block;
229 let SimulatedBlock { calls: call_results, .. } = result;
230
231 for (call_result, call) in call_results.iter_mut().zip(calls.iter()) {
232 call.seismic_elements.map(|seismic_elements| {
233 let encrypted_output = self
234 .eth_api
235 .evm_config()
236 .encrypt(&call_result.return_data, &seismic_elements)
237 .map(|encrypted_output| Bytes::from(encrypted_output))
238 .unwrap();
239 call_result.return_data = encrypted_output;
240 });
241 }
242 }
243
244 Ok(result)
245 }
246
247 async fn call(
249 &self,
250 request: alloy_rpc_types::SeismicCallRequest,
251 block_number: Option<BlockId>,
252 state_overrides: Option<StateOverride>,
253 block_overrides: Option<Box<BlockOverrides>>,
254 ) -> RpcResult<Bytes> {
255 trace!(target: "rpc::eth", ?request, ?block_number, ?state_overrides, ?block_overrides, "Serving overridden eth_call");
256 let tx_request = match request {
257 alloy_rpc_types::SeismicCallRequest::TransactionRequest(tx_request) => {
258 let mut request = tx_request.inner;
259 seismic_override_call_request(&mut request);
260 request
261 }
262
263 alloy_rpc_types::SeismicCallRequest::TypedData(typed_request) => {
264 let tx = recover_typed_data_request::<PoolPooledTx<Eth::Pool>>(&typed_request)?
265 .map_transaction(
266 <Eth::Pool as TransactionPool>::Transaction::pooled_into_consensus,
267 );
268
269 TransactionRequest::from_transaction_with_sender(
270 tx.as_signed().clone(),
271 tx.signer(),
272 )
273 }
274
275 alloy_rpc_types::SeismicCallRequest::Bytes(bytes) => {
276 let tx = recover_raw_transaction::<PoolPooledTx<Eth::Pool>>(&bytes)?
277 .map_transaction(
278 <Eth::Pool as TransactionPool>::Transaction::pooled_into_consensus,
279 );
280
281 TransactionRequest::from_transaction_with_sender(
282 tx.as_signed().clone(),
283 tx.signer(),
284 )
285 }
286 };
287
288 let seismic_elements = tx_request.seismic_elements;
289
290 let result = EthCall::call(
291 &self.eth_api,
292 tx_request,
293 block_number,
294 EvmOverrides::new(state_overrides, block_overrides),
295 )
296 .await?;
297
298 if let Some(seismic_elements) = seismic_elements {
299 let encrypted_output = self
300 .eth_api
301 .evm_config()
302 .encrypt(&result, &seismic_elements)
303 .map(|encrypted_output| Bytes::from(encrypted_output))
304 .unwrap();
305 Ok(encrypted_output)
306 } else {
307 Ok(result)
308 }
309 }
310
311 async fn send_raw_transaction(&self, tx: SeismicRawTxRequest) -> RpcResult<B256> {
313 trace!(target: "rpc::eth", ?tx, "Serving overridden eth_sendRawTransaction");
314 match tx {
315 SeismicRawTxRequest::Bytes(bytes) => {
316 Ok(EthTransactions::send_raw_transaction(&self.eth_api, bytes).await?)
317 }
318 SeismicRawTxRequest::TypedData(typed_data) => {
319 Ok(EthTransactions::send_typed_data_transaction(&self.eth_api, typed_data).await?)
320 }
321 }
322 }
323}
324
325#[cfg(test)]
326mod tests {
327 use crate::utils::test_utils::{build_test_eth_api, launch_http};
328 use alloy_eips::eip712::TypedDataRequest;
329 use alloy_primitives::{b256, hex, PrimitiveSignature};
330 use alloy_rpc_types::Block;
331 use jsonrpsee::core::client::{ClientT, SubscriptionClientT};
332 use reth_enclave::start_mock_enclave_server_random_port;
333 use reth_provider::test_utils::MockEthProvider;
334 use seismic_node::utils::test_utils::get_seismic_tx;
335
336 use super::*;
337
338 async fn test_basic_seismic_calls<C>(client: &C)
339 where
340 C: ClientT + SubscriptionClientT + Sync,
341 {
342 let _pk = SeismicApiClient::get_tee_public_key(client).await.unwrap();
343 }
344
345 async fn test_basic_eth_calls<C>(client: &C)
346 where
347 C: ClientT + SubscriptionClientT + Sync,
348 {
349 let typed_data = get_seismic_tx().eip712_to_type_data();
350 let typed_data_request = TypedDataRequest {
351 data: typed_data.clone(),
352 signature: PrimitiveSignature::new(
353 b256!("1fd474b1f9404c0c5df43b7620119ffbc3a1c3f942c73b6e14e9f55255ed9b1d").into(),
354 b256!("29aca24813279a901ec13b5f7bb53385fa1fc627b946592221417ff74a49600d").into(),
355 false,
356 ),
357 };
358 let tx = Bytes::from(hex!("02f871018303579880850555633d1b82520894eee27662c2b8eba3cd936a23f039f3189633e4c887ad591c62bdaeb180c080a07ea72c68abfb8fca1bd964f0f99132ed9280261bdca3e549546c0205e800f7d0a05b4ef3039e9c9b9babc179a1878fb825b5aaf5aed2fa8744854150157b08d6f3"));
359 let call_request = TransactionRequest::default();
360
361 let _signature = EthApiOverrideClient::<Block>::sign_typed_data_v4(
362 client,
363 Address::ZERO,
364 typed_data.clone(),
365 )
366 .await
367 .unwrap_err();
368 let _result = EthApiOverrideClient::<Block>::call(
369 client,
370 call_request.clone().into(),
371 None,
372 None,
373 None,
374 )
375 .await
376 .unwrap_err();
377 let _result =
378 EthApiOverrideClient::<Block>::call(client, tx.clone().into(), None, None, None)
379 .await
380 .unwrap_err();
381 let _result = EthApiOverrideClient::<Block>::call(
382 client,
383 typed_data_request.clone().into(),
384 None,
385 None,
386 None,
387 )
388 .await
389 .unwrap_err();
390 let _result =
391 EthApiOverrideClient::<Block>::send_raw_transaction(client, tx.clone().into())
392 .await
393 .unwrap();
394 }
395
396 #[tokio::test(flavor = "multi_thread")]
397 async fn test_call_seismic_functions_http() {
398 reth_tracing::init_test_tracing();
399 let enclave_client = start_mock_enclave_server_random_port().await;
400
401 let seismic_api = SeismicApi::default().with_enclave_client(enclave_client);
402
403 let handle = launch_http(seismic_api.into_rpc()).await;
404 let client = handle.http_client().unwrap();
405 test_basic_seismic_calls(&client).await;
406 }
407
408 #[tokio::test(flavor = "multi_thread")]
409 async fn test_call_eth_functions_http() {
410 reth_tracing::init_test_tracing();
411
412 let eth_api = build_test_eth_api(MockEthProvider::default());
413 let eth_api = EthApiExt::new(eth_api);
414 let handle = launch_http(eth_api.into_rpc()).await;
415 test_basic_eth_calls(&handle.http_client().unwrap()).await;
416 }
417}