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}