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