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