openzeppelin_relayer/domain/transaction/mod.rs
1//! This module defines the core transaction handling logic for different blockchain networks,
2//! including Ethereum (EVM), Solana, and Stellar. It provides a unified interface for preparing,
3//! submitting, handling, canceling, replacing, signing, and validating transactions across these
4//! networks. The module also includes a factory for creating network-specific transaction handlers
5//! based on relayer and repository information.
6//!
7//! The main components of this module are:
8//! - `Transaction` trait: Defines the operations for handling transactions.
9//! - `NetworkTransaction` enum: Represents a transaction for different network types.
10//! - `RelayerTransactionFactory`: A factory for creating network transactions.
11//!
12//! The module leverages async traits to handle asynchronous operations and uses the `eyre` crate
13//! for error handling.
14use crate::{
15 jobs::JobProducer,
16 models::{
17 EvmNetwork, NetworkTransactionRequest, NetworkType, RelayerRepoModel, SignerRepoModel,
18 SolanaNetwork, StellarNetwork, TransactionError, TransactionRepoModel,
19 },
20 repositories::{
21 NetworkRepository, NetworkRepositoryStorage, RelayerRepositoryStorage,
22 TransactionCounterRepositoryStorage, TransactionRepositoryStorage,
23 },
24 services::{
25 gas::{
26 cache::GasPriceCache, evm_gas_price::EvmGasPriceService,
27 price_params_handler::PriceParamsHandler,
28 },
29 get_network_provider, EvmSignerFactory, StellarSignerFactory,
30 },
31};
32use async_trait::async_trait;
33use eyre::Result;
34#[cfg(test)]
35use mockall::automock;
36use std::sync::Arc;
37
38pub mod evm;
39pub mod solana;
40pub mod stellar;
41
42mod util;
43pub use util::*;
44
45// Explicit re-exports to avoid ambiguous glob re-exports
46pub use evm::{DefaultEvmTransaction, EvmRelayerTransaction};
47pub use solana::{DefaultSolanaTransaction, SolanaRelayerTransaction};
48pub use stellar::{DefaultStellarTransaction, StellarRelayerTransaction};
49
50/// A trait that defines the operations for handling transactions across different networks.
51#[cfg_attr(test, automock)]
52#[async_trait]
53#[allow(dead_code)]
54pub trait Transaction {
55 /// Prepares a transaction for submission.
56 ///
57 /// # Arguments
58 ///
59 /// * `tx` - A `TransactionRepoModel` representing the transaction to be prepared.
60 ///
61 /// # Returns
62 ///
63 /// A `Result` containing the prepared `TransactionRepoModel` or a `TransactionError`.
64 async fn prepare_transaction(
65 &self,
66 tx: TransactionRepoModel,
67 ) -> Result<TransactionRepoModel, TransactionError>;
68
69 /// Submits a transaction to the network.
70 ///
71 /// # Arguments
72 ///
73 /// * `tx` - A `TransactionRepoModel` representing the transaction to be submitted.
74 ///
75 /// # Returns
76 ///
77 /// A `Result` containing the submitted `TransactionRepoModel` or a `TransactionError`.
78 async fn submit_transaction(
79 &self,
80 tx: TransactionRepoModel,
81 ) -> Result<TransactionRepoModel, TransactionError>;
82
83 /// Resubmits a transaction with updated parameters.
84 ///
85 /// # Arguments
86 ///
87 /// * `tx` - A `TransactionRepoModel` representing the transaction to be resubmitted.
88 ///
89 /// # Returns
90 ///
91 /// A `Result` containing the resubmitted `TransactionRepoModel` or a `TransactionError`.
92 async fn resubmit_transaction(
93 &self,
94 tx: TransactionRepoModel,
95 ) -> Result<TransactionRepoModel, TransactionError>;
96
97 /// Handles the status of a transaction.
98 ///
99 /// # Arguments
100 ///
101 /// * `tx` - A `TransactionRepoModel` representing the transaction whose status is to be
102 /// handled.
103 ///
104 /// # Returns
105 ///
106 /// A `Result` containing the updated `TransactionRepoModel` or a `TransactionError`.
107 async fn handle_transaction_status(
108 &self,
109 tx: TransactionRepoModel,
110 ) -> Result<TransactionRepoModel, TransactionError>;
111
112 /// Cancels a transaction.
113 ///
114 /// # Arguments
115 ///
116 /// * `tx` - A `TransactionRepoModel` representing the transaction to be canceled.
117 ///
118 /// # Returns
119 ///
120 /// A `Result` containing the canceled `TransactionRepoModel` or a `TransactionError`.
121 async fn cancel_transaction(
122 &self,
123 tx: TransactionRepoModel,
124 ) -> Result<TransactionRepoModel, TransactionError>;
125
126 /// Replaces a transaction with a new one.
127 ///
128 /// # Arguments
129 ///
130 /// * `old_tx` - A `TransactionRepoModel` representing the transaction to be replaced.
131 /// * `new_tx_request` - A `NetworkTransactionRequest` representing the new transaction data.
132 ///
133 /// # Returns
134 ///
135 /// A `Result` containing the new `TransactionRepoModel` or a `TransactionError`.
136 async fn replace_transaction(
137 &self,
138 old_tx: TransactionRepoModel,
139 new_tx_request: NetworkTransactionRequest,
140 ) -> Result<TransactionRepoModel, TransactionError>;
141
142 /// Signs a transaction.
143 ///
144 /// # Arguments
145 ///
146 /// * `tx` - A `TransactionRepoModel` representing the transaction to be signed.
147 ///
148 /// # Returns
149 ///
150 /// A `Result` containing the signed `TransactionRepoModel` or a `TransactionError`.
151 async fn sign_transaction(
152 &self,
153 tx: TransactionRepoModel,
154 ) -> Result<TransactionRepoModel, TransactionError>;
155
156 /// Validates a transaction.
157 ///
158 /// # Arguments
159 ///
160 /// * `tx` - A `TransactionRepoModel` representing the transaction to be validated.
161 ///
162 /// # Returns
163 ///
164 /// A `Result` containing a boolean indicating the validity of the transaction or a
165 /// `TransactionError`.
166 async fn validate_transaction(
167 &self,
168 tx: TransactionRepoModel,
169 ) -> Result<bool, TransactionError>;
170}
171
172/// An enum representing a transaction for different network types.
173pub enum NetworkTransaction {
174 Evm(Box<DefaultEvmTransaction>),
175 Solana(DefaultSolanaTransaction),
176 Stellar(DefaultStellarTransaction),
177}
178
179#[async_trait]
180impl Transaction for NetworkTransaction {
181 /// Prepares a transaction for submission based on the network type.
182 ///
183 /// # Arguments
184 ///
185 /// * `tx` - A `TransactionRepoModel` representing the transaction to be prepared.
186 ///
187 /// # Returns
188 ///
189 /// A `Result` containing the prepared `TransactionRepoModel` or a `TransactionError`.
190 async fn prepare_transaction(
191 &self,
192 tx: TransactionRepoModel,
193 ) -> Result<TransactionRepoModel, TransactionError> {
194 match self {
195 NetworkTransaction::Evm(relayer) => relayer.prepare_transaction(tx).await,
196 NetworkTransaction::Solana(relayer) => relayer.prepare_transaction(tx).await,
197 NetworkTransaction::Stellar(relayer) => relayer.prepare_transaction(tx).await,
198 }
199 }
200
201 /// Submits a transaction to the network based on the network type.
202 ///
203 /// # Arguments
204 ///
205 /// * `tx` - A `TransactionRepoModel` representing the transaction to be submitted.
206 ///
207 /// # Returns
208 ///
209 /// A `Result` containing the submitted `TransactionRepoModel` or a `TransactionError`.
210 async fn submit_transaction(
211 &self,
212 tx: TransactionRepoModel,
213 ) -> Result<TransactionRepoModel, TransactionError> {
214 match self {
215 NetworkTransaction::Evm(relayer) => relayer.submit_transaction(tx).await,
216 NetworkTransaction::Solana(relayer) => relayer.submit_transaction(tx).await,
217 NetworkTransaction::Stellar(relayer) => relayer.submit_transaction(tx).await,
218 }
219 }
220 /// Resubmits a transaction with updated parameters based on the network type.
221 ///
222 /// # Arguments
223 ///
224 /// * `tx` - A `TransactionRepoModel` representing the transaction to be resubmitted.
225 ///
226 /// # Returns
227 ///
228 /// A `Result` containing the resubmitted `TransactionRepoModel` or a `TransactionError`.
229 async fn resubmit_transaction(
230 &self,
231 tx: TransactionRepoModel,
232 ) -> Result<TransactionRepoModel, TransactionError> {
233 match self {
234 NetworkTransaction::Evm(relayer) => relayer.resubmit_transaction(tx).await,
235 NetworkTransaction::Solana(relayer) => relayer.resubmit_transaction(tx).await,
236 NetworkTransaction::Stellar(relayer) => relayer.resubmit_transaction(tx).await,
237 }
238 }
239
240 /// Handles the status of a transaction based on the network type.
241 ///
242 /// # Arguments
243 ///
244 /// * `tx` - A `TransactionRepoModel` representing the transaction whose status is to be
245 /// handled.
246 ///
247 /// # Returns
248 ///
249 /// A `Result` containing the updated `TransactionRepoModel` or a `TransactionError`.
250 async fn handle_transaction_status(
251 &self,
252 tx: TransactionRepoModel,
253 ) -> Result<TransactionRepoModel, TransactionError> {
254 match self {
255 NetworkTransaction::Evm(relayer) => relayer.handle_transaction_status(tx).await,
256 NetworkTransaction::Solana(relayer) => relayer.handle_transaction_status(tx).await,
257 NetworkTransaction::Stellar(relayer) => relayer.handle_transaction_status(tx).await,
258 }
259 }
260
261 /// Cancels a transaction based on the network type.
262 ///
263 /// # Arguments
264 ///
265 /// * `tx` - A `TransactionRepoModel` representing the transaction to be canceled.
266 ///
267 /// # Returns
268 ///
269 /// A `Result` containing the canceled `TransactionRepoModel` or a `TransactionError`.
270 async fn cancel_transaction(
271 &self,
272 tx: TransactionRepoModel,
273 ) -> Result<TransactionRepoModel, TransactionError> {
274 match self {
275 NetworkTransaction::Evm(relayer) => relayer.cancel_transaction(tx).await,
276 NetworkTransaction::Solana(_) => solana_not_supported_transaction(),
277 NetworkTransaction::Stellar(relayer) => relayer.cancel_transaction(tx).await,
278 }
279 }
280
281 /// Replaces a transaction with a new one based on the network type.
282 ///
283 /// # Arguments
284 ///
285 /// * `old_tx` - A `TransactionRepoModel` representing the transaction to be replaced.
286 /// * `new_tx_request` - A `NetworkTransactionRequest` representing the new transaction data.
287 ///
288 /// # Returns
289 ///
290 /// A `Result` containing the new `TransactionRepoModel` or a `TransactionError`.
291 async fn replace_transaction(
292 &self,
293 old_tx: TransactionRepoModel,
294 new_tx_request: NetworkTransactionRequest,
295 ) -> Result<TransactionRepoModel, TransactionError> {
296 match self {
297 NetworkTransaction::Evm(relayer) => {
298 relayer.replace_transaction(old_tx, new_tx_request).await
299 }
300 NetworkTransaction::Solana(_) => solana_not_supported_transaction(),
301 NetworkTransaction::Stellar(relayer) => {
302 relayer.replace_transaction(old_tx, new_tx_request).await
303 }
304 }
305 }
306
307 /// Signs a transaction based on the network type.
308 ///
309 /// # Arguments
310 ///
311 /// * `tx` - A `TransactionRepoModel` representing the transaction to be signed.
312 ///
313 /// # Returns
314 ///
315 /// A `Result` containing the signed `TransactionRepoModel` or a `TransactionError`.
316 async fn sign_transaction(
317 &self,
318 tx: TransactionRepoModel,
319 ) -> Result<TransactionRepoModel, TransactionError> {
320 match self {
321 NetworkTransaction::Evm(relayer) => relayer.sign_transaction(tx).await,
322 NetworkTransaction::Solana(relayer) => relayer.sign_transaction(tx).await,
323 NetworkTransaction::Stellar(relayer) => relayer.sign_transaction(tx).await,
324 }
325 }
326
327 /// Validates a transaction based on the network type.
328 ///
329 /// # Arguments
330 ///
331 /// * `tx` - A `TransactionRepoModel` representing the transaction to be validated.
332 ///
333 /// # Returns
334 ///
335 /// A `Result` containing a boolean indicating the validity of the transaction or a
336 /// `TransactionError`.
337 async fn validate_transaction(
338 &self,
339 tx: TransactionRepoModel,
340 ) -> Result<bool, TransactionError> {
341 match self {
342 NetworkTransaction::Evm(relayer) => relayer.validate_transaction(tx).await,
343 NetworkTransaction::Solana(relayer) => relayer.validate_transaction(tx).await,
344 NetworkTransaction::Stellar(relayer) => relayer.validate_transaction(tx).await,
345 }
346 }
347}
348
349/// A trait for creating network transactions.
350#[allow(dead_code)]
351pub trait RelayerTransactionFactoryTrait {
352 /// Creates a network transaction based on the relayer and repository information.
353 ///
354 /// # Arguments
355 ///
356 /// * `relayer` - A `RelayerRepoModel` representing the relayer.
357 /// * `relayer_repository` - An `Arc` to the `RelayerRepositoryStorage`.
358 /// * `transaction_repository` - An `Arc` to the `TransactionRepositoryStorage`.
359 /// * `job_producer` - An `Arc` to the `JobProducer`.
360 ///
361 /// # Returns
362 ///
363 /// A `Result` containing the created `NetworkTransaction` or a `TransactionError`.
364 fn create_transaction(
365 relayer: RelayerRepoModel,
366 relayer_repository: Arc<RelayerRepositoryStorage>,
367 transaction_repository: Arc<TransactionRepositoryStorage>,
368 job_producer: Arc<JobProducer>,
369 ) -> Result<NetworkTransaction, TransactionError>;
370}
371/// A factory for creating relayer transactions.
372pub struct RelayerTransactionFactory;
373
374#[allow(dead_code)]
375impl RelayerTransactionFactory {
376 /// Creates a network transaction based on the relayer, signer, and repository information.
377 ///
378 /// # Arguments
379 ///
380 /// * `relayer` - A `RelayerRepoModel` representing the relayer.
381 /// * `signer` - A `SignerRepoModel` representing the signer.
382 /// * `relayer_repository` - An `Arc` to the `RelayerRepositoryStorage`.
383 /// * `transaction_repository` - An `Arc` to the `InMemoryTransactionRepository`.
384 /// * `transaction_counter_store` - An `Arc` to the `InMemoryTransactionCounter`.
385 /// * `job_producer` - An `Arc` to the `JobProducer`.
386 ///
387 /// # Returns
388 ///
389 /// A `Result` containing the created `NetworkTransaction` or a `TransactionError`.
390 pub async fn create_transaction(
391 relayer: RelayerRepoModel,
392 signer: SignerRepoModel,
393 relayer_repository: Arc<RelayerRepositoryStorage>,
394 network_repository: Arc<NetworkRepositoryStorage>,
395 transaction_repository: Arc<TransactionRepositoryStorage>,
396 transaction_counter_store: Arc<TransactionCounterRepositoryStorage>,
397 job_producer: Arc<JobProducer>,
398 ) -> Result<NetworkTransaction, TransactionError> {
399 match relayer.network_type {
400 NetworkType::Evm => {
401 let network_repo = network_repository
402 .get_by_name(NetworkType::Evm, &relayer.network)
403 .await
404 .ok()
405 .flatten()
406 .ok_or_else(|| {
407 TransactionError::NetworkConfiguration(format!(
408 "Network {} not found",
409 relayer.network
410 ))
411 })?;
412
413 let network = EvmNetwork::try_from(network_repo)
414 .map_err(|e| TransactionError::NetworkConfiguration(e.to_string()))?;
415
416 let evm_provider = get_network_provider(&network, relayer.custom_rpc_urls.clone())?;
417 let signer_service = EvmSignerFactory::create_evm_signer(signer.into()).await?;
418 let price_params_handler =
419 PriceParamsHandler::for_network(&network, evm_provider.clone());
420
421 let evm_gas_cache = GasPriceCache::global();
422
423 // Use the global cache if gas price caching is enabled
424 let cache = if let Some(cfg) = &network.gas_price_cache {
425 evm_gas_cache.configure_network(network.chain_id, cfg.clone());
426 Some(evm_gas_cache.clone())
427 } else {
428 if evm_gas_cache.has_configuration_for_network(network.chain_id) {
429 evm_gas_cache.remove_network(network.chain_id);
430 }
431 None
432 };
433
434 let gas_price_service =
435 EvmGasPriceService::new(evm_provider.clone(), network.clone(), cache);
436
437 let price_calculator =
438 evm::PriceCalculator::new(gas_price_service, price_params_handler);
439
440 Ok(NetworkTransaction::Evm(Box::new(
441 DefaultEvmTransaction::new(
442 relayer,
443 evm_provider,
444 relayer_repository,
445 network_repository,
446 transaction_repository,
447 transaction_counter_store,
448 job_producer,
449 price_calculator,
450 signer_service,
451 )?,
452 )))
453 }
454 NetworkType::Solana => {
455 let network_repo = network_repository
456 .get_by_name(NetworkType::Solana, &relayer.network)
457 .await
458 .ok()
459 .flatten()
460 .ok_or_else(|| {
461 TransactionError::NetworkConfiguration(format!(
462 "Network {} not found",
463 relayer.network
464 ))
465 })?;
466
467 let network = SolanaNetwork::try_from(network_repo)
468 .map_err(|e| TransactionError::NetworkConfiguration(e.to_string()))?;
469
470 let solana_provider = Arc::new(get_network_provider(
471 &network,
472 relayer.custom_rpc_urls.clone(),
473 )?);
474
475 Ok(NetworkTransaction::Solana(SolanaRelayerTransaction::new(
476 relayer,
477 relayer_repository,
478 solana_provider,
479 transaction_repository,
480 job_producer,
481 )?))
482 }
483 NetworkType::Stellar => {
484 let signer_service =
485 Arc::new(StellarSignerFactory::create_stellar_signer(&signer.into())?);
486
487 let network_repo = network_repository
488 .get_by_name(NetworkType::Stellar, &relayer.network)
489 .await
490 .ok()
491 .flatten()
492 .ok_or_else(|| {
493 TransactionError::NetworkConfiguration(format!(
494 "Network {} not found",
495 relayer.network
496 ))
497 })?;
498
499 let network = StellarNetwork::try_from(network_repo)
500 .map_err(|e| TransactionError::NetworkConfiguration(e.to_string()))?;
501
502 let stellar_provider =
503 get_network_provider(&network, relayer.custom_rpc_urls.clone())
504 .map_err(|e| TransactionError::NetworkConfiguration(e.to_string()))?;
505
506 Ok(NetworkTransaction::Stellar(DefaultStellarTransaction::new(
507 relayer,
508 relayer_repository,
509 transaction_repository,
510 job_producer,
511 signer_service,
512 stellar_provider,
513 transaction_counter_store,
514 )?))
515 }
516 }
517 }
518}