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}