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}