openzeppelin_relayer/services/plugins/
relayer_api.rs1use 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"); 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}