1use crate::domain::{
9 get_network_relayer, get_network_relayer_by_model, get_relayer_by_id, get_transaction_by_id,
10 Relayer, SignTransactionRequest,
11};
12use crate::jobs::JobProducerTrait;
13use crate::models::{
14 convert_to_internal_rpc_request, AppState, JsonRpcRequest, NetworkRepoModel, NetworkRpcRequest,
15 NetworkTransactionRequest, NotificationRepoModel, RelayerRepoModel, SignerRepoModel,
16 ThinDataAppState, TransactionRepoModel, TransactionResponse,
17};
18use crate::observability::request_id::set_request_id;
19use crate::repositories::{
20 ApiKeyRepositoryTrait, NetworkRepository, PluginRepositoryTrait, RelayerRepository, Repository,
21 TransactionCounterTrait, TransactionRepository,
22};
23use crate::services::plugins::PluginError;
24use actix_web::web;
25use async_trait::async_trait;
26use serde::{Deserialize, Serialize};
27use strum::Display;
28use tracing::{debug, instrument};
29
30#[cfg(test)]
31use mockall::automock;
32
33#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Display)]
34pub enum PluginMethod {
35 #[serde(rename = "sendTransaction")]
36 SendTransaction,
37 #[serde(rename = "getTransaction")]
38 GetTransaction,
39 #[serde(rename = "getRelayerStatus")]
40 GetRelayerStatus,
41 #[serde(rename = "signTransaction")]
42 SignTransaction,
43 #[serde(rename = "getRelayer")]
44 GetRelayer,
45 #[serde(rename = "rpc")]
46 Rpc,
47}
48
49#[derive(Deserialize, Serialize, Clone, Debug)]
50#[serde(rename_all = "camelCase")]
51pub struct Request {
52 pub request_id: String,
53 pub relayer_id: String,
54 pub method: PluginMethod,
55 pub payload: serde_json::Value,
56 pub http_request_id: Option<String>,
57}
58
59#[derive(Deserialize, Serialize, Clone, Debug)]
60#[serde(rename_all = "camelCase")]
61pub struct GetTransactionRequest {
62 pub transaction_id: String,
63}
64
65#[derive(Serialize, Deserialize, Clone, Debug, Default)]
66#[serde(rename_all = "camelCase")]
67pub struct Response {
68 pub request_id: String,
69 pub result: Option<serde_json::Value>,
70 pub error: Option<String>,
71}
72
73#[async_trait]
74#[cfg_attr(test, automock)]
75pub trait RelayerApiTrait<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>: Send + Sync
76where
77 J: JobProducerTrait + 'static,
78 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
79 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
80 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
81 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
82 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
83 TCR: TransactionCounterTrait + Send + Sync + 'static,
84 PR: PluginRepositoryTrait + Send + Sync + 'static,
85 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
86{
87 async fn handle_request(
88 &self,
89 request: Request,
90 state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
91 ) -> Response;
92
93 async fn process_request(
94 &self,
95 request: Request,
96 state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
97 ) -> Result<Response, PluginError>;
98
99 async fn handle_send_transaction(
100 &self,
101 request: Request,
102 state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
103 ) -> Result<Response, PluginError>;
104
105 async fn handle_get_transaction(
106 &self,
107 request: Request,
108 state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
109 ) -> Result<Response, PluginError>;
110
111 async fn handle_get_relayer_status(
112 &self,
113 request: Request,
114 state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
115 ) -> Result<Response, PluginError>;
116
117 async fn handle_sign_transaction(
118 &self,
119 request: Request,
120 state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
121 ) -> Result<Response, PluginError>;
122 async fn handle_get_relayer_info(
123 &self,
124 request: Request,
125 state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
126 ) -> Result<Response, PluginError>;
127 async fn handle_rpc_request(
128 &self,
129 request: Request,
130 state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
131 ) -> Result<Response, PluginError>;
132}
133
134#[derive(Default)]
135pub struct RelayerApi;
136
137impl RelayerApi {
138 #[instrument(name = "Plugin::handle_request", skip_all, fields(method = %request.method, relayer_id = %request.relayer_id, plugin_req_id = %request.http_request_id.as_ref().unwrap_or(&request.request_id)))]
139 pub async fn handle_request<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
140 &self,
141 request: Request,
142 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
143 ) -> Response
144 where
145 J: JobProducerTrait + 'static,
146 TR: TransactionRepository
147 + Repository<TransactionRepoModel, String>
148 + Send
149 + Sync
150 + 'static,
151 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
152 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
153 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
154 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
155 TCR: TransactionCounterTrait + Send + Sync + 'static,
156 PR: PluginRepositoryTrait + Send + Sync + 'static,
157 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
158 {
159 if let Some(http_rid) = request.http_request_id.clone() {
161 set_request_id(http_rid);
162 }
163
164 match self.process_request(request.clone(), state).await {
165 Ok(response) => response,
166 Err(e) => Response {
167 request_id: request.request_id,
168 result: None,
169 error: Some(e.to_string()),
170 },
171 }
172 }
173
174 #[instrument(
175 name = "Plugin::process_request",
176 skip_all,
177 fields(
178 method = %request.method,
179 relayer_id = %request.relayer_id,
180 plugin_req_id = %request.http_request_id.as_ref().unwrap_or(&request.request_id)
181 )
182 )]
183 async fn process_request<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
184 &self,
185 request: Request,
186 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
187 ) -> Result<Response, PluginError>
188 where
189 J: JobProducerTrait + 'static,
190 TR: TransactionRepository
191 + Repository<TransactionRepoModel, String>
192 + Send
193 + Sync
194 + 'static,
195 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
196 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
197 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
198 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
199 TCR: TransactionCounterTrait + Send + Sync + 'static,
200 PR: PluginRepositoryTrait + Send + Sync + 'static,
201 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
202 {
203 match request.method {
204 PluginMethod::SendTransaction => self.handle_send_transaction(request, state).await,
205 PluginMethod::GetTransaction => self.handle_get_transaction(request, state).await,
206 PluginMethod::GetRelayerStatus => self.handle_get_relayer_status(request, state).await,
207 PluginMethod::SignTransaction => self.handle_sign_transaction(request, state).await,
208 PluginMethod::GetRelayer => self.handle_get_relayer_info(request, state).await,
209 PluginMethod::Rpc => self.handle_rpc_request(request, state).await,
210 }
211 }
212
213 #[instrument(
214 name = "Plugin::handle_send_transaction",
215 skip_all,
216 fields(
217 method = %request.method,
218 relayer_id = %request.relayer_id,
219 plugin_req_id = %request.http_request_id.as_ref().unwrap_or(&request.request_id),
220 tx_id = tracing::field::Empty
221 )
222 )]
223 async fn handle_send_transaction<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
224 &self,
225 request: Request,
226 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
227 ) -> Result<Response, PluginError>
228 where
229 J: JobProducerTrait + 'static,
230 TR: TransactionRepository
231 + Repository<TransactionRepoModel, String>
232 + Send
233 + Sync
234 + 'static,
235 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
236 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
237 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
238 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
239 TCR: TransactionCounterTrait + Send + Sync + 'static,
240 PR: PluginRepositoryTrait + Send + Sync + 'static,
241 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
242 {
243 let relayer_repo_model = get_relayer_by_id(request.relayer_id.clone(), state)
244 .await
245 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
246
247 relayer_repo_model
248 .validate_active_state()
249 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
250
251 let network_relayer = get_network_relayer_by_model(relayer_repo_model.clone(), state)
253 .await
254 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
255
256 let tx_request = NetworkTransactionRequest::from_json(
257 &relayer_repo_model.network_type,
258 request.payload.clone(),
259 )
260 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
261
262 tx_request
263 .validate(&relayer_repo_model)
264 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
265
266 let transaction = network_relayer
267 .process_transaction_request(tx_request)
268 .await
269 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
270
271 tracing::Span::current().record("tx_id", transaction.id.as_str());
272 debug!(
273 tx_id = %transaction.id,
274 status = ?transaction.status,
275 "plugin created transaction"
276 );
277
278 let transaction_response: TransactionResponse = transaction.into();
279 let result = serde_json::to_value(transaction_response)
280 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
281
282 Ok(Response {
283 request_id: request.request_id,
284 result: Some(result),
285 error: None,
286 })
287 }
288
289 #[instrument(
290 name = "Plugin::handle_get_transaction",
291 skip_all,
292 fields(
293 method = %request.method,
294 relayer_id = %request.relayer_id,
295 plugin_req_id = %request.http_request_id.as_ref().unwrap_or(&request.request_id),
296 tx_id = tracing::field::Empty
297 )
298 )]
299 async fn handle_get_transaction<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
300 &self,
301 request: Request,
302 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
303 ) -> Result<Response, PluginError>
304 where
305 J: JobProducerTrait + 'static,
306 TR: TransactionRepository
307 + Repository<TransactionRepoModel, String>
308 + Send
309 + Sync
310 + 'static,
311 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
312 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
313 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
314 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
315 TCR: TransactionCounterTrait + Send + Sync + 'static,
316 PR: PluginRepositoryTrait + Send + Sync + 'static,
317 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
318 {
319 get_relayer_by_id(request.relayer_id.clone(), state)
321 .await
322 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
323
324 let get_transaction_request: GetTransactionRequest =
325 serde_json::from_value(request.payload)
326 .map_err(|e| PluginError::InvalidPayload(e.to_string()))?;
327
328 tracing::Span::current().record("tx_id", get_transaction_request.transaction_id.as_str());
329 let transaction = get_transaction_by_id(get_transaction_request.transaction_id, state)
330 .await
331 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
332
333 let transaction_response: TransactionResponse = transaction.into();
334
335 let result = serde_json::to_value(transaction_response)
336 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
337
338 Ok(Response {
339 request_id: request.request_id,
340 result: Some(result),
341 error: None,
342 })
343 }
344
345 #[instrument(
346 name = "Plugin::handle_get_relayer_status",
347 skip_all,
348 fields(
349 method = %request.method,
350 relayer_id = %request.relayer_id,
351 plugin_req_id = %request.http_request_id.as_ref().unwrap_or(&request.request_id)
352 )
353 )]
354 async fn handle_get_relayer_status<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
355 &self,
356 request: Request,
357 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
358 ) -> Result<Response, PluginError>
359 where
360 J: JobProducerTrait + 'static,
361 TR: TransactionRepository
362 + Repository<TransactionRepoModel, String>
363 + Send
364 + Sync
365 + 'static,
366 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
367 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
368 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
369 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
370 TCR: TransactionCounterTrait + Send + Sync + 'static,
371 PR: PluginRepositoryTrait + Send + Sync + 'static,
372 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
373 {
374 let network_relayer = get_network_relayer(request.relayer_id.clone(), state)
375 .await
376 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
377
378 let status = network_relayer
379 .get_status()
380 .await
381 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
382
383 let result =
384 serde_json::to_value(status).map_err(|e| PluginError::RelayerError(e.to_string()))?;
385
386 Ok(Response {
387 request_id: request.request_id,
388 result: Some(result),
389 error: None,
390 })
391 }
392
393 #[instrument(
394 name = "Plugin::handle_sign_transaction",
395 skip_all,
396 fields(
397 method = %request.method,
398 relayer_id = %request.relayer_id,
399 plugin_req_id = %request.http_request_id.as_ref().unwrap_or(&request.request_id)
400 )
401 )]
402 async fn handle_sign_transaction<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
403 &self,
404 request: Request,
405 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
406 ) -> Result<Response, PluginError>
407 where
408 J: JobProducerTrait + 'static,
409 TR: TransactionRepository
410 + Repository<TransactionRepoModel, String>
411 + Send
412 + Sync
413 + 'static,
414 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
415 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
416 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
417 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
418 TCR: TransactionCounterTrait + Send + Sync + 'static,
419 PR: PluginRepositoryTrait + Send + Sync + 'static,
420 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
421 {
422 let sign_request: SignTransactionRequest = serde_json::from_value(request.payload)
423 .map_err(|e| PluginError::InvalidPayload(e.to_string()))?;
424
425 let network_relayer = get_network_relayer(request.relayer_id.clone(), state)
426 .await
427 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
428
429 let response = network_relayer
430 .sign_transaction(&sign_request)
431 .await
432 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
433
434 let result =
435 serde_json::to_value(response).map_err(|e| PluginError::RelayerError(e.to_string()))?;
436
437 Ok(Response {
438 request_id: request.request_id,
439 result: Some(result),
440 error: None,
441 })
442 }
443
444 #[instrument(
445 name = "Plugin::handle_get_relayer_info",
446 skip_all,
447 fields(
448 method = %request.method,
449 relayer_id = %request.relayer_id,
450 plugin_req_id = %request.http_request_id.as_ref().unwrap_or(&request.request_id)
451 )
452 )]
453 async fn handle_get_relayer_info<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
454 &self,
455 request: Request,
456 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
457 ) -> Result<Response, PluginError>
458 where
459 J: JobProducerTrait + 'static,
460 TR: TransactionRepository
461 + Repository<TransactionRepoModel, String>
462 + Send
463 + Sync
464 + 'static,
465 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
466 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
467 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
468 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
469 TCR: TransactionCounterTrait + Send + Sync + 'static,
470 PR: PluginRepositoryTrait + Send + Sync + 'static,
471 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
472 {
473 let relayer = get_relayer_by_id(request.relayer_id.clone(), state)
474 .await
475 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
476 let relayer_response: crate::models::RelayerResponse = relayer.into();
477 let result = serde_json::to_value(relayer_response)
478 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
479 Ok(Response {
480 request_id: request.request_id,
481 result: Some(result),
482 error: None,
483 })
484 }
485
486 #[instrument(
487 name = "Plugin::handle_rpc_request",
488 skip_all,
489 fields(
490 method = %request.method,
491 relayer_id = %request.relayer_id,
492 plugin_req_id = %request.http_request_id.as_ref().unwrap_or(&request.request_id)
493 )
494 )]
495 async fn handle_rpc_request<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
496 &self,
497 request: Request,
498 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
499 ) -> Result<Response, PluginError>
500 where
501 J: JobProducerTrait + 'static,
502 TR: TransactionRepository
503 + Repository<TransactionRepoModel, String>
504 + Send
505 + Sync
506 + 'static,
507 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
508 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
509 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
510 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
511 TCR: TransactionCounterTrait + Send + Sync + 'static,
512 PR: PluginRepositoryTrait + Send + Sync + 'static,
513 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
514 {
515 let relayer_repo_model = get_relayer_by_id(request.relayer_id.clone(), state)
516 .await
517 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
518
519 relayer_repo_model
520 .validate_active_state()
521 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
522
523 let network_relayer = get_network_relayer_by_model(relayer_repo_model.clone(), state)
525 .await
526 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
527
528 let network_rpc_request: JsonRpcRequest<NetworkRpcRequest> =
530 convert_to_internal_rpc_request(request.payload, &relayer_repo_model.network_type)
531 .map_err(|e| PluginError::InvalidPayload(e.to_string()))?;
532
533 let result = network_relayer.rpc(network_rpc_request).await;
534
535 match result {
536 Ok(json_rpc_response) => {
537 let result_value = serde_json::to_value(json_rpc_response)
538 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
539 Ok(Response {
540 request_id: request.request_id,
541 result: Some(result_value),
542 error: None,
543 })
544 }
545 Err(e) => Ok(Response {
546 request_id: request.request_id,
547 result: None,
548 error: Some(e.to_string()),
549 }),
550 }
551 }
552}
553
554#[async_trait]
555impl<J, RR, TR, NR, NFR, SR, TCR, PR, AKR> RelayerApiTrait<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>
556 for RelayerApi
557where
558 J: JobProducerTrait + 'static,
559 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
560 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
561 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
562 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
563 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
564 TCR: TransactionCounterTrait + Send + Sync + 'static,
565 PR: PluginRepositoryTrait + Send + Sync + 'static,
566 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
567{
568 async fn handle_request(
569 &self,
570 request: Request,
571 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
572 ) -> Response {
573 self.handle_request(request, state).await
574 }
575
576 async fn process_request(
577 &self,
578 request: Request,
579 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
580 ) -> Result<Response, PluginError> {
581 self.process_request(request, state).await
582 }
583
584 async fn handle_send_transaction(
585 &self,
586 request: Request,
587 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
588 ) -> Result<Response, PluginError> {
589 self.handle_send_transaction(request, state).await
590 }
591
592 async fn handle_get_transaction(
593 &self,
594 request: Request,
595 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
596 ) -> Result<Response, PluginError> {
597 self.handle_get_transaction(request, state).await
598 }
599
600 async fn handle_get_relayer_status(
601 &self,
602 request: Request,
603 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
604 ) -> Result<Response, PluginError> {
605 self.handle_get_relayer_status(request, state).await
606 }
607
608 async fn handle_sign_transaction(
609 &self,
610 request: Request,
611 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
612 ) -> Result<Response, PluginError> {
613 self.handle_sign_transaction(request, state).await
614 }
615
616 async fn handle_get_relayer_info(
617 &self,
618 request: Request,
619 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
620 ) -> Result<Response, PluginError> {
621 self.handle_get_relayer_info(request, state).await
622 }
623
624 async fn handle_rpc_request(
625 &self,
626 request: Request,
627 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
628 ) -> Result<Response, PluginError> {
629 self.handle_rpc_request(request, state).await
630 }
631}
632
633#[cfg(test)]
634mod tests {
635 use std::env;
636
637 use crate::utils::mocks::mockutils::{
638 create_mock_app_state, create_mock_evm_transaction_request, create_mock_network,
639 create_mock_relayer, create_mock_signer, create_mock_transaction,
640 };
641
642 use super::*;
643
644 fn setup_test_env() {
645 env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D"); env::set_var("REDIS_URL", "redis://localhost:6379");
647 env::set_var("RPC_TIMEOUT_MS", "5000");
648 }
649
650 #[tokio::test]
651 async fn test_handle_request() {
652 setup_test_env();
653 let state = create_mock_app_state(
654 None,
655 Some(vec![create_mock_relayer("test".to_string(), false)]),
656 Some(vec![create_mock_signer()]),
657 Some(vec![create_mock_network()]),
658 None,
659 None,
660 )
661 .await;
662
663 let request = Request {
664 request_id: "test".to_string(),
665 relayer_id: "test".to_string(),
666 method: PluginMethod::SendTransaction,
667 payload: serde_json::json!(create_mock_evm_transaction_request()),
668 http_request_id: None,
669 };
670
671 let relayer_api = RelayerApi;
672 let response = relayer_api
673 .handle_request(request.clone(), &web::ThinData(state))
674 .await;
675
676 assert!(response.error.is_none());
677 assert!(response.result.is_some());
678 }
679
680 #[tokio::test]
681 async fn test_handle_request_error_paused_relayer() {
682 setup_test_env();
683 let paused = true;
684 let state = create_mock_app_state(
685 None,
686 Some(vec![create_mock_relayer("test".to_string(), paused)]),
687 Some(vec![create_mock_signer()]),
688 Some(vec![create_mock_network()]),
689 None,
690 None,
691 )
692 .await;
693
694 let request = Request {
695 request_id: "test".to_string(),
696 relayer_id: "test".to_string(),
697 method: PluginMethod::SendTransaction,
698 payload: serde_json::json!(create_mock_evm_transaction_request()),
699 http_request_id: None,
700 };
701
702 let relayer_api = RelayerApi;
703 let response = relayer_api
704 .handle_request(request.clone(), &web::ThinData(state))
705 .await;
706
707 assert!(response.error.is_some());
708 assert!(response.result.is_none());
709 assert_eq!(response.error.unwrap(), "Relayer error: Relayer is paused");
710 }
711
712 #[tokio::test]
713 async fn test_handle_request_using_trait() {
714 setup_test_env();
715 let state = create_mock_app_state(
716 None,
717 Some(vec![create_mock_relayer("test".to_string(), false)]),
718 Some(vec![create_mock_signer()]),
719 Some(vec![create_mock_network()]),
720 None,
721 None,
722 )
723 .await;
724
725 let request = Request {
726 request_id: "test".to_string(),
727 relayer_id: "test".to_string(),
728 method: PluginMethod::SendTransaction,
729 payload: serde_json::json!(create_mock_evm_transaction_request()),
730 http_request_id: None,
731 };
732
733 let relayer_api = RelayerApi;
734
735 let state = web::ThinData(state);
736
737 let response = RelayerApiTrait::handle_request(&relayer_api, request.clone(), &state).await;
738
739 assert!(response.error.is_none());
740 assert!(response.result.is_some());
741
742 let response =
743 RelayerApiTrait::process_request(&relayer_api, request.clone(), &state).await;
744
745 assert!(response.is_ok());
746
747 let response =
748 RelayerApiTrait::handle_send_transaction(&relayer_api, request.clone(), &state).await;
749
750 assert!(response.is_ok());
751 }
752
753 #[tokio::test]
754 async fn test_handle_get_transaction() {
755 setup_test_env();
756 let state = create_mock_app_state(
757 None,
758 Some(vec![create_mock_relayer("test".to_string(), false)]),
759 Some(vec![create_mock_signer()]),
760 Some(vec![create_mock_network()]),
761 None,
762 Some(vec![create_mock_transaction()]),
763 )
764 .await;
765
766 let request = Request {
767 request_id: "test".to_string(),
768 relayer_id: "test".to_string(),
769 method: PluginMethod::GetTransaction,
770 payload: serde_json::json!(GetTransactionRequest {
771 transaction_id: "test".to_string(),
772 }),
773 http_request_id: None,
774 };
775
776 let relayer_api = RelayerApi;
777 let response = relayer_api
778 .handle_request(request.clone(), &web::ThinData(state))
779 .await;
780
781 assert!(response.error.is_none());
782 assert!(response.result.is_some());
783 }
784
785 #[tokio::test]
786 async fn test_handle_get_transaction_error_relayer_not_found() {
787 setup_test_env();
788 let state = create_mock_app_state(
789 None,
790 None,
791 Some(vec![create_mock_signer()]),
792 Some(vec![create_mock_network()]),
793 None,
794 Some(vec![create_mock_transaction()]),
795 )
796 .await;
797
798 let request = Request {
799 request_id: "test".to_string(),
800 relayer_id: "test".to_string(),
801 method: PluginMethod::GetTransaction,
802 payload: serde_json::json!(GetTransactionRequest {
803 transaction_id: "test".to_string(),
804 }),
805 http_request_id: None,
806 };
807
808 let relayer_api = RelayerApi;
809 let response = relayer_api
810 .handle_request(request.clone(), &web::ThinData(state))
811 .await;
812
813 assert!(response.error.is_some());
814 let error = response.error.unwrap();
815 assert!(error.contains("Relayer with ID test not found"));
816 }
817
818 #[tokio::test]
819 async fn test_handle_get_transaction_error_transaction_not_found() {
820 setup_test_env();
821 let state = create_mock_app_state(
822 None,
823 Some(vec![create_mock_relayer("test".to_string(), false)]),
824 Some(vec![create_mock_signer()]),
825 Some(vec![create_mock_network()]),
826 None,
827 None,
828 )
829 .await;
830
831 let request = Request {
832 request_id: "test".to_string(),
833 relayer_id: "test".to_string(),
834 method: PluginMethod::GetTransaction,
835 payload: serde_json::json!(GetTransactionRequest {
836 transaction_id: "test".to_string(),
837 }),
838 http_request_id: None,
839 };
840
841 let relayer_api = RelayerApi;
842 let response = relayer_api
843 .handle_request(request.clone(), &web::ThinData(state))
844 .await;
845
846 assert!(response.error.is_some());
847 let error = response.error.unwrap();
848 assert!(error.contains("Transaction with ID test not found"));
849 }
850
851 #[tokio::test]
852 async fn test_handle_get_relayer_status_relayer_not_found() {
853 setup_test_env();
854 let state = create_mock_app_state(
855 None,
856 None,
857 Some(vec![create_mock_signer()]),
858 Some(vec![create_mock_network()]),
859 None,
860 None,
861 )
862 .await;
863
864 let request = Request {
865 request_id: "test".to_string(),
866 relayer_id: "test".to_string(),
867 method: PluginMethod::GetRelayerStatus,
868 payload: serde_json::json!({}),
869 http_request_id: None,
870 };
871
872 let relayer_api = RelayerApi;
873 let response = relayer_api
874 .handle_request(request.clone(), &web::ThinData(state))
875 .await;
876
877 assert!(response.error.is_some());
878 let error = response.error.unwrap();
879 assert!(error.contains("Relayer with ID test not found"));
880 }
881
882 #[tokio::test]
883 async fn test_handle_sign_transaction_evm_not_supported() {
884 setup_test_env();
885 let state = create_mock_app_state(
886 None,
887 Some(vec![create_mock_relayer("test".to_string(), false)]),
888 Some(vec![create_mock_signer()]),
889 Some(vec![create_mock_network()]),
890 None,
891 None,
892 )
893 .await;
894
895 let request = Request {
896 request_id: "test".to_string(),
897 relayer_id: "test".to_string(),
898 method: PluginMethod::SignTransaction,
899 payload: serde_json::json!({
900 "unsigned_xdr": "test_xdr"
901 }),
902 http_request_id: None,
903 };
904
905 let relayer_api = RelayerApi;
906 let response = relayer_api
907 .handle_request(request.clone(), &web::ThinData(state))
908 .await;
909
910 assert!(response.error.is_some());
911 let error = response.error.unwrap();
912 assert!(error.contains("sign_transaction not supported for EVM"));
913 }
914
915 #[tokio::test]
916 async fn test_handle_sign_transaction_invalid_payload() {
917 setup_test_env();
918 let state = create_mock_app_state(
919 None,
920 Some(vec![create_mock_relayer("test".to_string(), false)]),
921 Some(vec![create_mock_signer()]),
922 Some(vec![create_mock_network()]),
923 None,
924 None,
925 )
926 .await;
927
928 let request = Request {
929 request_id: "test".to_string(),
930 relayer_id: "test".to_string(),
931 method: PluginMethod::SignTransaction,
932 payload: serde_json::json!({"invalid": "payload"}),
933 http_request_id: None,
934 };
935
936 let relayer_api = RelayerApi;
937 let response = relayer_api
938 .handle_request(request.clone(), &web::ThinData(state))
939 .await;
940
941 assert!(response.error.is_some());
942 let error = response.error.unwrap();
943 assert!(error.contains("Invalid payload"));
944 }
945
946 #[tokio::test]
947 async fn test_handle_sign_transaction_relayer_not_found() {
948 setup_test_env();
949 let state = create_mock_app_state(
950 None,
951 None,
952 Some(vec![create_mock_signer()]),
953 Some(vec![create_mock_network()]),
954 None,
955 None,
956 )
957 .await;
958
959 let request = Request {
960 request_id: "test".to_string(),
961 relayer_id: "test".to_string(),
962 method: PluginMethod::SignTransaction,
963 payload: serde_json::json!({
964 "unsigned_xdr": "test_xdr"
965 }),
966 http_request_id: None,
967 };
968
969 let relayer_api = RelayerApi;
970 let response = relayer_api
971 .handle_request(request.clone(), &web::ThinData(state))
972 .await;
973
974 assert!(response.error.is_some());
975 let error = response.error.unwrap();
976 assert!(error.contains("Relayer with ID test not found"));
977 }
978
979 #[tokio::test]
980 async fn test_handle_get_relayer_info_success() {
981 setup_test_env();
982 let state = create_mock_app_state(
983 None,
984 Some(vec![create_mock_relayer("test".to_string(), false)]),
985 Some(vec![create_mock_signer()]),
986 Some(vec![create_mock_network()]),
987 None,
988 None,
989 )
990 .await;
991
992 let request = Request {
993 request_id: "test".to_string(),
994 relayer_id: "test".to_string(),
995 method: PluginMethod::GetRelayer,
996 payload: serde_json::json!({}),
997 http_request_id: None,
998 };
999
1000 let relayer_api = RelayerApi;
1001 let response = relayer_api
1002 .handle_request(request.clone(), &web::ThinData(state))
1003 .await;
1004
1005 assert!(response.error.is_none());
1006 assert!(response.result.is_some());
1007
1008 let result = response.result.unwrap();
1009 assert!(result.get("id").is_some());
1010 assert!(result.get("name").is_some());
1011 assert!(result.get("network").is_some());
1012 assert!(result.get("address").is_some());
1013 }
1014
1015 #[tokio::test]
1016 async fn test_handle_get_relayer_info_relayer_not_found() {
1017 setup_test_env();
1018 let state = create_mock_app_state(
1019 None,
1020 None,
1021 Some(vec![create_mock_signer()]),
1022 Some(vec![create_mock_network()]),
1023 None,
1024 None,
1025 )
1026 .await;
1027
1028 let request = Request {
1029 request_id: "test".to_string(),
1030 relayer_id: "test".to_string(),
1031 method: PluginMethod::GetRelayer,
1032 payload: serde_json::json!({}),
1033 http_request_id: None,
1034 };
1035
1036 let relayer_api = RelayerApi;
1037 let response = relayer_api
1038 .handle_request(request.clone(), &web::ThinData(state))
1039 .await;
1040
1041 assert!(response.error.is_some());
1042 let error = response.error.unwrap();
1043 assert!(error.contains("Relayer with ID test not found"));
1044 }
1045
1046 #[tokio::test]
1047 async fn test_handle_rpc_request_evm_success() {
1048 setup_test_env();
1049 let state = create_mock_app_state(
1050 None,
1051 Some(vec![create_mock_relayer("test".to_string(), false)]),
1052 Some(vec![create_mock_signer()]),
1053 Some(vec![create_mock_network()]),
1054 None,
1055 None,
1056 )
1057 .await;
1058
1059 let request = Request {
1060 request_id: "test-rpc-1".to_string(),
1061 relayer_id: "test".to_string(),
1062 method: PluginMethod::Rpc,
1063 payload: serde_json::json!({
1064 "jsonrpc": "2.0",
1065 "method": "eth_blockNumber",
1066 "params": [],
1067 "id": 1
1068 }),
1069 http_request_id: None,
1070 };
1071
1072 let relayer_api = RelayerApi;
1073 let response = relayer_api
1074 .handle_request(request.clone(), &web::ThinData(state))
1075 .await;
1076
1077 assert!(response.error.is_none());
1078 assert!(response.result.is_some());
1079 let result = response.result.unwrap();
1080 assert!(result.get("jsonrpc").is_some());
1081 }
1082
1083 #[tokio::test]
1084 async fn test_handle_rpc_request_invalid_payload() {
1085 setup_test_env();
1086 let state = create_mock_app_state(
1087 None,
1088 Some(vec![create_mock_relayer("test".to_string(), false)]),
1089 Some(vec![create_mock_signer()]),
1090 Some(vec![create_mock_network()]),
1091 None,
1092 None,
1093 )
1094 .await;
1095
1096 let request = Request {
1097 request_id: "test-rpc-2".to_string(),
1098 relayer_id: "test".to_string(),
1099 method: PluginMethod::Rpc,
1100 payload: serde_json::json!({
1101 "invalid": "payload"
1102 }),
1103 http_request_id: None,
1104 };
1105
1106 let relayer_api = RelayerApi;
1107 let response = relayer_api
1108 .handle_request(request.clone(), &web::ThinData(state))
1109 .await;
1110
1111 assert!(response.error.is_some());
1112 let error = response.error.unwrap();
1113 assert!(error.contains("Invalid payload") || error.contains("Missing 'method' field"));
1114 }
1115
1116 #[tokio::test]
1117 async fn test_handle_rpc_request_relayer_not_found() {
1118 setup_test_env();
1119 let state = create_mock_app_state(
1120 None,
1121 None,
1122 Some(vec![create_mock_signer()]),
1123 Some(vec![create_mock_network()]),
1124 None,
1125 None,
1126 )
1127 .await;
1128
1129 let request = Request {
1130 request_id: "test-rpc-3".to_string(),
1131 relayer_id: "nonexistent".to_string(),
1132 method: PluginMethod::Rpc,
1133 payload: serde_json::json!({
1134 "jsonrpc": "2.0",
1135 "method": "eth_blockNumber",
1136 "params": [],
1137 "id": 1
1138 }),
1139 http_request_id: None,
1140 };
1141
1142 let relayer_api = RelayerApi;
1143 let response = relayer_api
1144 .handle_request(request.clone(), &web::ThinData(state))
1145 .await;
1146
1147 assert!(response.error.is_some());
1148 let error = response.error.unwrap();
1149 assert!(error.contains("Relayer with ID nonexistent not found"));
1150 }
1151
1152 #[tokio::test]
1153 async fn test_handle_rpc_request_paused_relayer() {
1154 setup_test_env();
1155 let paused = true;
1156 let state = create_mock_app_state(
1157 None,
1158 Some(vec![create_mock_relayer("test".to_string(), paused)]),
1159 Some(vec![create_mock_signer()]),
1160 Some(vec![create_mock_network()]),
1161 None,
1162 None,
1163 )
1164 .await;
1165
1166 let request = Request {
1167 request_id: "test-rpc-4".to_string(),
1168 relayer_id: "test".to_string(),
1169 method: PluginMethod::Rpc,
1170 payload: serde_json::json!({
1171 "jsonrpc": "2.0",
1172 "method": "eth_blockNumber",
1173 "params": [],
1174 "id": 1
1175 }),
1176 http_request_id: None,
1177 };
1178
1179 let relayer_api = RelayerApi;
1180 let response = relayer_api
1181 .handle_request(request.clone(), &web::ThinData(state))
1182 .await;
1183
1184 assert!(response.error.is_some());
1185 let error = response.error.unwrap();
1186 assert!(error.contains("Relayer is paused"));
1187 }
1188
1189 #[tokio::test]
1190 async fn test_handle_rpc_request_with_string_id() {
1191 setup_test_env();
1192 let state = create_mock_app_state(
1193 None,
1194 Some(vec![create_mock_relayer("test".to_string(), false)]),
1195 Some(vec![create_mock_signer()]),
1196 Some(vec![create_mock_network()]),
1197 None,
1198 None,
1199 )
1200 .await;
1201
1202 let request = Request {
1203 request_id: "test-rpc-5".to_string(),
1204 relayer_id: "test".to_string(),
1205 method: PluginMethod::Rpc,
1206 payload: serde_json::json!({
1207 "jsonrpc": "2.0",
1208 "method": "eth_chainId",
1209 "params": [],
1210 "id": "custom-string-id"
1211 }),
1212 http_request_id: None,
1213 };
1214
1215 let relayer_api = RelayerApi;
1216 let response = relayer_api
1217 .handle_request(request.clone(), &web::ThinData(state))
1218 .await;
1219
1220 assert!(response.error.is_none());
1221 assert!(response.result.is_some());
1222 let result = response.result.unwrap();
1223 assert_eq!(result.get("id").unwrap(), "custom-string-id");
1224 }
1225
1226 #[tokio::test]
1227 async fn test_handle_rpc_request_with_null_id() {
1228 setup_test_env();
1229 let state = create_mock_app_state(
1230 None,
1231 Some(vec![create_mock_relayer("test".to_string(), false)]),
1232 Some(vec![create_mock_signer()]),
1233 Some(vec![create_mock_network()]),
1234 None,
1235 None,
1236 )
1237 .await;
1238
1239 let request = Request {
1240 request_id: "test-rpc-6".to_string(),
1241 relayer_id: "test".to_string(),
1242 method: PluginMethod::Rpc,
1243 payload: serde_json::json!({
1244 "jsonrpc": "2.0",
1245 "method": "eth_chainId",
1246 "params": [],
1247 "id": null
1248 }),
1249 http_request_id: None,
1250 };
1251
1252 let relayer_api = RelayerApi;
1253 let response = relayer_api
1254 .handle_request(request.clone(), &web::ThinData(state))
1255 .await;
1256
1257 assert!(response.error.is_none());
1258 assert!(response.result.is_some());
1259 }
1260
1261 #[tokio::test]
1262 async fn test_handle_rpc_request_with_array_params() {
1263 setup_test_env();
1264 let state = create_mock_app_state(
1265 None,
1266 Some(vec![create_mock_relayer("test".to_string(), false)]),
1267 Some(vec![create_mock_signer()]),
1268 Some(vec![create_mock_network()]),
1269 None,
1270 None,
1271 )
1272 .await;
1273
1274 let request = Request {
1275 request_id: "test-rpc-7".to_string(),
1276 relayer_id: "test".to_string(),
1277 method: PluginMethod::Rpc,
1278 payload: serde_json::json!({
1279 "jsonrpc": "2.0",
1280 "method": "eth_getBalance",
1281 "params": ["0x742d35Cc6634C0532925a3b844Bc454e4438f44e", "latest"],
1282 "id": 1
1283 }),
1284 http_request_id: None,
1285 };
1286
1287 let relayer_api = RelayerApi;
1288 let response = relayer_api
1289 .handle_request(request.clone(), &web::ThinData(state))
1290 .await;
1291
1292 assert!(response.error.is_none());
1293 assert!(response.result.is_some());
1294 }
1295
1296 #[tokio::test]
1297 async fn test_handle_rpc_request_with_object_params() {
1298 setup_test_env();
1299 let state = create_mock_app_state(
1300 None,
1301 Some(vec![create_mock_relayer("test".to_string(), false)]),
1302 Some(vec![create_mock_signer()]),
1303 Some(vec![create_mock_network()]),
1304 None,
1305 None,
1306 )
1307 .await;
1308
1309 let request = Request {
1310 request_id: "test-rpc-8".to_string(),
1311 relayer_id: "test".to_string(),
1312 method: PluginMethod::Rpc,
1313 payload: serde_json::json!({
1314 "jsonrpc": "2.0",
1315 "method": "eth_call",
1316 "params": {
1317 "to": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
1318 "data": "0x"
1319 },
1320 "id": 1
1321 }),
1322 http_request_id: None,
1323 };
1324
1325 let relayer_api = RelayerApi;
1326 let response = relayer_api
1327 .handle_request(request.clone(), &web::ThinData(state))
1328 .await;
1329
1330 assert!(response.error.is_none());
1331 assert!(response.result.is_some());
1332 }
1333
1334 #[tokio::test]
1335 async fn test_handle_rpc_request_missing_method() {
1336 setup_test_env();
1337 let state = create_mock_app_state(
1338 None,
1339 Some(vec![create_mock_relayer("test".to_string(), false)]),
1340 Some(vec![create_mock_signer()]),
1341 Some(vec![create_mock_network()]),
1342 None,
1343 None,
1344 )
1345 .await;
1346
1347 let request = Request {
1348 request_id: "test-rpc-9".to_string(),
1349 relayer_id: "test".to_string(),
1350 method: PluginMethod::Rpc,
1351 payload: serde_json::json!({
1352 "jsonrpc": "2.0",
1353 "params": [],
1354 "id": 1
1355 }),
1356 http_request_id: None,
1357 };
1358
1359 let relayer_api = RelayerApi;
1360 let response = relayer_api
1361 .handle_request(request.clone(), &web::ThinData(state))
1362 .await;
1363
1364 assert!(response.error.is_some());
1365 let error = response.error.unwrap();
1366 assert!(error.contains("Missing 'method' field") || error.contains("Invalid payload"));
1367 }
1368
1369 #[tokio::test]
1370 async fn test_handle_rpc_request_empty_method() {
1371 setup_test_env();
1372 let state = create_mock_app_state(
1373 None,
1374 Some(vec![create_mock_relayer("test".to_string(), false)]),
1375 Some(vec![create_mock_signer()]),
1376 Some(vec![create_mock_network()]),
1377 None,
1378 None,
1379 )
1380 .await;
1381
1382 let request = Request {
1383 request_id: "test-rpc-10".to_string(),
1384 relayer_id: "test".to_string(),
1385 method: PluginMethod::Rpc,
1386 payload: serde_json::json!({
1387 "jsonrpc": "2.0",
1388 "method": "",
1389 "params": [],
1390 "id": 1
1391 }),
1392 http_request_id: None,
1393 };
1394
1395 let relayer_api = RelayerApi;
1396 let response = relayer_api
1397 .handle_request(request.clone(), &web::ThinData(state))
1398 .await;
1399
1400 assert!(
1403 response.error.is_some()
1404 || (response.result.is_some()
1405 && response.result.as_ref().unwrap().get("error").is_some())
1406 );
1407 }
1408
1409 #[tokio::test]
1410 async fn test_handle_rpc_request_with_http_request_id() {
1411 setup_test_env();
1412 let state = create_mock_app_state(
1413 None,
1414 Some(vec![create_mock_relayer("test".to_string(), false)]),
1415 Some(vec![create_mock_signer()]),
1416 Some(vec![create_mock_network()]),
1417 None,
1418 None,
1419 )
1420 .await;
1421
1422 let request = Request {
1423 request_id: "test-rpc-11".to_string(),
1424 relayer_id: "test".to_string(),
1425 method: PluginMethod::Rpc,
1426 payload: serde_json::json!({
1427 "jsonrpc": "2.0",
1428 "method": "eth_blockNumber",
1429 "params": [],
1430 "id": 1
1431 }),
1432 http_request_id: Some("http-req-123".to_string()),
1433 };
1434
1435 let relayer_api = RelayerApi;
1436 let response = relayer_api
1437 .handle_request(request.clone(), &web::ThinData(state))
1438 .await;
1439
1440 assert!(response.error.is_none());
1441 assert!(response.result.is_some());
1442 assert_eq!(response.request_id, "test-rpc-11");
1443 }
1444
1445 #[tokio::test]
1446 async fn test_handle_rpc_request_default_jsonrpc_version() {
1447 setup_test_env();
1448 let state = create_mock_app_state(
1449 None,
1450 Some(vec![create_mock_relayer("test".to_string(), false)]),
1451 Some(vec![create_mock_signer()]),
1452 Some(vec![create_mock_network()]),
1453 None,
1454 None,
1455 )
1456 .await;
1457
1458 let request = Request {
1459 request_id: "test-rpc-12".to_string(),
1460 relayer_id: "test".to_string(),
1461 method: PluginMethod::Rpc,
1462 payload: serde_json::json!({
1463 "method": "eth_blockNumber",
1464 "params": [],
1465 "id": 1
1466 }),
1467 http_request_id: None,
1468 };
1469
1470 let relayer_api = RelayerApi;
1471 let response = relayer_api
1472 .handle_request(request.clone(), &web::ThinData(state))
1473 .await;
1474
1475 if response.error.is_none() {
1477 assert!(response.result.is_some());
1478 let result = response.result.unwrap();
1479 assert_eq!(result.get("jsonrpc").unwrap(), "2.0");
1480 } else {
1481 assert!(response.error.is_some());
1483 }
1484 }
1485
1486 #[tokio::test]
1487 async fn test_handle_rpc_request_custom_jsonrpc_version() {
1488 setup_test_env();
1489 let state = create_mock_app_state(
1490 None,
1491 Some(vec![create_mock_relayer("test".to_string(), false)]),
1492 Some(vec![create_mock_signer()]),
1493 Some(vec![create_mock_network()]),
1494 None,
1495 None,
1496 )
1497 .await;
1498
1499 let request = Request {
1500 request_id: "test-rpc-13".to_string(),
1501 relayer_id: "test".to_string(),
1502 method: PluginMethod::Rpc,
1503 payload: serde_json::json!({
1504 "jsonrpc": "1.0",
1505 "method": "eth_blockNumber",
1506 "params": [],
1507 "id": 1
1508 }),
1509 http_request_id: None,
1510 };
1511
1512 let relayer_api = RelayerApi;
1513 let response = relayer_api
1514 .handle_request(request.clone(), &web::ThinData(state))
1515 .await;
1516
1517 assert!(response.error.is_none());
1518 assert!(response.result.is_some());
1519 }
1520
1521 #[tokio::test]
1522 async fn test_handle_rpc_request_result_structure() {
1523 setup_test_env();
1524 let state = create_mock_app_state(
1525 None,
1526 Some(vec![create_mock_relayer("test".to_string(), false)]),
1527 Some(vec![create_mock_signer()]),
1528 Some(vec![create_mock_network()]),
1529 None,
1530 None,
1531 )
1532 .await;
1533
1534 let request = Request {
1535 request_id: "test-rpc-14".to_string(),
1536 relayer_id: "test".to_string(),
1537 method: PluginMethod::Rpc,
1538 payload: serde_json::json!({
1539 "jsonrpc": "2.0",
1540 "method": "eth_blockNumber",
1541 "params": [],
1542 "id": 42
1543 }),
1544 http_request_id: None,
1545 };
1546
1547 let relayer_api = RelayerApi;
1548 let response = relayer_api
1549 .handle_request(request.clone(), &web::ThinData(state))
1550 .await;
1551
1552 assert!(response.error.is_none());
1553 assert!(response.result.is_some());
1554 assert_eq!(response.request_id, "test-rpc-14");
1555
1556 let result = response.result.unwrap();
1557 assert!(result.get("jsonrpc").is_some());
1558 assert!(result.get("id").is_some());
1559 assert!(result.get("result").is_some() || result.get("error").is_some());
1561 }
1562}