openzeppelin_relayer/services/plugins/
relayer_api.rs

1//! This module is responsible for handling the requests to the relayer API.
2//!
3//! It manages an internal API that mirrors the HTTP external API of the relayer.
4//!
5//! Supported methods:
6//! - `sendTransaction` - sends a transaction to the relayer.
7//!
8use crate::domain::{get_network_relayer, get_relayer_by_id, Relayer};
9use crate::models::{NetworkTransactionRequest, TransactionResponse};
10use crate::{jobs::JobProducerTrait, models::AppState};
11use actix_web::web;
12use async_trait::async_trait;
13use serde::{Deserialize, Serialize};
14
15use super::PluginError;
16
17#[cfg(test)]
18use mockall::automock;
19
20#[derive(Debug, Serialize, Deserialize, Clone)]
21pub enum PluginMethod {
22    #[serde(rename = "sendTransaction")]
23    SendTransaction,
24}
25
26#[derive(Deserialize, Serialize, Clone, Debug)]
27#[serde(rename_all = "camelCase")]
28pub struct Request {
29    pub request_id: String,
30    pub relayer_id: String,
31    pub method: PluginMethod,
32    pub payload: serde_json::Value,
33}
34
35#[derive(Serialize, Deserialize, Clone, Debug, Default)]
36#[serde(rename_all = "camelCase")]
37pub struct Response {
38    pub request_id: String,
39    pub result: Option<serde_json::Value>,
40    pub error: Option<String>,
41}
42
43#[cfg_attr(test, automock)]
44#[async_trait]
45pub trait RelayerApiTrait {
46    async fn handle_request<J: JobProducerTrait + 'static>(
47        &self,
48        request: Request,
49        state: &web::ThinData<AppState<J>>,
50    ) -> Response;
51    async fn process_request<J: JobProducerTrait + 'static>(
52        &self,
53        request: Request,
54        state: &web::ThinData<AppState<J>>,
55    ) -> Result<Response, PluginError>;
56    async fn handle_send_transaction<J: JobProducerTrait + 'static>(
57        &self,
58        request: Request,
59        state: &web::ThinData<AppState<J>>,
60    ) -> Result<Response, PluginError>;
61}
62
63#[derive(Default)]
64pub struct RelayerApi;
65
66impl RelayerApi {
67    pub async fn handle_request<J: JobProducerTrait + 'static>(
68        &self,
69        request: Request,
70        state: &web::ThinData<AppState<J>>,
71    ) -> Response {
72        match self.process_request(request.clone(), state).await {
73            Ok(response) => response,
74            Err(e) => Response {
75                request_id: request.request_id,
76                result: None,
77                error: Some(e.to_string()),
78            },
79        }
80    }
81
82    async fn process_request<J: JobProducerTrait + 'static>(
83        &self,
84        request: Request,
85        state: &web::ThinData<AppState<J>>,
86    ) -> Result<Response, PluginError> {
87        match request.method {
88            PluginMethod::SendTransaction => self.handle_send_transaction(request, state).await,
89        }
90    }
91
92    async fn handle_send_transaction<J: JobProducerTrait + 'static>(
93        &self,
94        request: Request,
95        state: &web::ThinData<AppState<J>>,
96    ) -> Result<Response, PluginError> {
97        let relayer_repo_model = get_relayer_by_id(request.relayer_id.clone(), state)
98            .await
99            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
100
101        relayer_repo_model
102            .validate_active_state()
103            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
104
105        let network_relayer = get_network_relayer(request.relayer_id.clone(), state)
106            .await
107            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
108
109        let tx_request = NetworkTransactionRequest::from_json(
110            &relayer_repo_model.network_type,
111            request.payload.clone(),
112        )
113        .map_err(|e| PluginError::RelayerError(e.to_string()))?;
114
115        tx_request
116            .validate(&relayer_repo_model)
117            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
118
119        let transaction = network_relayer
120            .process_transaction_request(tx_request)
121            .await
122            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
123
124        let transaction_response: TransactionResponse = transaction.into();
125        let result = serde_json::to_value(transaction_response)
126            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
127
128        Ok(Response {
129            request_id: request.request_id,
130            result: Some(result),
131            error: None,
132        })
133    }
134}
135
136#[async_trait]
137impl RelayerApiTrait for RelayerApi {
138    async fn handle_request<J: JobProducerTrait + 'static>(
139        &self,
140        request: Request,
141        state: &web::ThinData<AppState<J>>,
142    ) -> Response {
143        self.handle_request(request, state).await
144    }
145
146    async fn process_request<J: JobProducerTrait + 'static>(
147        &self,
148        request: Request,
149        state: &web::ThinData<AppState<J>>,
150    ) -> Result<Response, PluginError> {
151        self.process_request(request, state).await
152    }
153
154    async fn handle_send_transaction<J: JobProducerTrait + 'static>(
155        &self,
156        request: Request,
157        state: &web::ThinData<AppState<J>>,
158    ) -> Result<Response, PluginError> {
159        self.handle_send_transaction(request, state).await
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use std::env;
166
167    use crate::utils::mocks::mockutils::{
168        create_mock_app_state, create_mock_evm_transaction_request, create_mock_network,
169        create_mock_relayer, create_mock_signer,
170    };
171
172    use super::*;
173
174    fn setup_test_env() {
175        env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D"); // noboost
176        env::set_var("REDIS_URL", "redis://localhost:6379");
177        env::set_var("RPC_TIMEOUT_MS", "5000");
178    }
179
180    #[tokio::test]
181    async fn test_handle_request() {
182        setup_test_env();
183        let state = create_mock_app_state(
184            Some(vec![create_mock_relayer("test".to_string(), false)]),
185            Some(vec![create_mock_signer()]),
186            Some(vec![create_mock_network()]),
187            None,
188        )
189        .await;
190
191        let request = Request {
192            request_id: "test".to_string(),
193            relayer_id: "test".to_string(),
194            method: PluginMethod::SendTransaction,
195            payload: serde_json::json!(create_mock_evm_transaction_request()),
196        };
197
198        let relayer_api = RelayerApi;
199        let response = relayer_api
200            .handle_request(request.clone(), &web::ThinData(state))
201            .await;
202
203        assert!(response.error.is_none());
204        assert!(response.result.is_some());
205    }
206
207    #[tokio::test]
208    async fn test_handle_request_error_paused_relayer() {
209        setup_test_env();
210        let paused = true;
211        let state = create_mock_app_state(
212            Some(vec![create_mock_relayer("test".to_string(), paused)]),
213            Some(vec![create_mock_signer()]),
214            Some(vec![create_mock_network()]),
215            None,
216        )
217        .await;
218
219        let request = Request {
220            request_id: "test".to_string(),
221            relayer_id: "test".to_string(),
222            method: PluginMethod::SendTransaction,
223            payload: serde_json::json!(create_mock_evm_transaction_request()),
224        };
225
226        let relayer_api = RelayerApi;
227        let response = relayer_api
228            .handle_request(request.clone(), &web::ThinData(state))
229            .await;
230
231        assert!(response.error.is_some());
232        assert!(response.result.is_none());
233        assert_eq!(response.error.unwrap(), "Relayer error: Relayer is paused");
234    }
235
236    #[tokio::test]
237    async fn test_handle_request_using_trait() {
238        setup_test_env();
239        let state = create_mock_app_state(
240            Some(vec![create_mock_relayer("test".to_string(), false)]),
241            Some(vec![create_mock_signer()]),
242            Some(vec![create_mock_network()]),
243            None,
244        )
245        .await;
246
247        let request = Request {
248            request_id: "test".to_string(),
249            relayer_id: "test".to_string(),
250            method: PluginMethod::SendTransaction,
251            payload: serde_json::json!(create_mock_evm_transaction_request()),
252        };
253
254        let relayer_api = RelayerApi;
255
256        let state = web::ThinData(state);
257
258        let response = RelayerApiTrait::handle_request(&relayer_api, request.clone(), &state).await;
259
260        assert!(response.error.is_none());
261        assert!(response.result.is_some());
262
263        let response =
264            RelayerApiTrait::process_request(&relayer_api, request.clone(), &state).await;
265
266        assert!(response.is_ok());
267
268        let response =
269            RelayerApiTrait::handle_send_transaction(&relayer_api, request.clone(), &state).await;
270
271        assert!(response.is_ok());
272    }
273}