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