1use crate::domain::map_provider_error;
2use crate::domain::relayer::evm::create_error_response;
3use crate::{
26 constants::{STELLAR_SMALLEST_UNIT_NAME, STELLAR_STATUS_CHECK_INITIAL_DELAY_SECONDS},
27 domain::{
28 create_success_response, transaction::stellar::fetch_next_sequence_from_chain,
29 BalanceResponse, SignDataRequest, SignDataResponse, SignTransactionExternalResponse,
30 SignTransactionExternalResponseStellar, SignTransactionRequest, SignTypedDataRequest,
31 },
32 jobs::{JobProducerTrait, RelayerHealthCheck, TransactionRequest, TransactionStatusCheck},
33 models::{
34 produce_relayer_disabled_payload, DeletePendingTransactionsResponse, DisabledReason,
35 HealthCheckFailure, JsonRpcRequest, JsonRpcResponse, NetworkRepoModel, NetworkRpcRequest,
36 NetworkRpcResult, NetworkTransactionRequest, NetworkType, RelayerRepoModel, RelayerStatus,
37 RepositoryError, RpcErrorCodes, StellarNetwork, StellarRpcRequest, TransactionRepoModel,
38 TransactionStatus,
39 },
40 repositories::{NetworkRepository, RelayerRepository, Repository, TransactionRepository},
41 services::{
42 provider::{StellarProvider, StellarProviderTrait},
43 signer::{StellarSignTrait, StellarSigner},
44 TransactionCounterService, TransactionCounterServiceTrait,
45 },
46 utils::calculate_scheduled_timestamp,
47};
48use async_trait::async_trait;
49use eyre::Result;
50use std::sync::Arc;
51use tracing::{debug, info, warn};
52
53use crate::domain::relayer::{Relayer, RelayerError};
54
55pub struct StellarRelayerDependencies<RR, NR, TR, J, TCS>
57where
58 RR: Repository<RelayerRepoModel, String> + RelayerRepository + Send + Sync + 'static,
59 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
60 TR: Repository<TransactionRepoModel, String> + TransactionRepository + Send + Sync + 'static,
61 J: JobProducerTrait + Send + Sync + 'static,
62 TCS: TransactionCounterServiceTrait + Send + Sync + 'static,
63{
64 pub relayer_repository: Arc<RR>,
65 pub network_repository: Arc<NR>,
66 pub transaction_repository: Arc<TR>,
67 pub transaction_counter_service: Arc<TCS>,
68 pub job_producer: Arc<J>,
69}
70
71impl<RR, NR, TR, J, TCS> StellarRelayerDependencies<RR, NR, TR, J, TCS>
72where
73 RR: Repository<RelayerRepoModel, String> + RelayerRepository + Send + Sync + 'static,
74 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
75 TR: Repository<TransactionRepoModel, String> + TransactionRepository + Send + Sync + 'static,
76 J: JobProducerTrait + Send + Sync,
77 TCS: TransactionCounterServiceTrait + Send + Sync + 'static,
78{
79 pub fn new(
93 relayer_repository: Arc<RR>,
94 network_repository: Arc<NR>,
95 transaction_repository: Arc<TR>,
96 transaction_counter_service: Arc<TCS>,
97 job_producer: Arc<J>,
98 ) -> Self {
99 Self {
100 relayer_repository,
101 network_repository,
102 transaction_repository,
103 transaction_counter_service,
104 job_producer,
105 }
106 }
107}
108
109#[allow(dead_code)]
110pub struct StellarRelayer<P, RR, NR, TR, J, TCS, S>
111where
112 P: StellarProviderTrait + Send + Sync,
113 RR: Repository<RelayerRepoModel, String> + RelayerRepository + Send + Sync + 'static,
114 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
115 TR: Repository<TransactionRepoModel, String> + TransactionRepository + Send + Sync + 'static,
116 J: JobProducerTrait + Send + Sync + 'static,
117 TCS: TransactionCounterServiceTrait + Send + Sync + 'static,
118 S: StellarSignTrait + Send + Sync + 'static,
119{
120 relayer: RelayerRepoModel,
121 signer: S,
122 network: StellarNetwork,
123 provider: P,
124 relayer_repository: Arc<RR>,
125 network_repository: Arc<NR>,
126 transaction_repository: Arc<TR>,
127 transaction_counter_service: Arc<TCS>,
128 job_producer: Arc<J>,
129}
130
131pub type DefaultStellarRelayer<J, TR, NR, RR, TCR> =
132 StellarRelayer<StellarProvider, RR, NR, TR, J, TransactionCounterService<TCR>, StellarSigner>;
133
134impl<P, RR, NR, TR, J, TCS, S> StellarRelayer<P, RR, NR, TR, J, TCS, S>
135where
136 P: StellarProviderTrait + Send + Sync,
137 RR: Repository<RelayerRepoModel, String> + RelayerRepository + Send + Sync + 'static,
138 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
139 TR: Repository<TransactionRepoModel, String> + TransactionRepository + Send + Sync + 'static,
140 J: JobProducerTrait + Send + Sync + 'static,
141 TCS: TransactionCounterServiceTrait + Send + Sync + 'static,
142 S: StellarSignTrait + Send + Sync + 'static,
143{
144 #[allow(clippy::too_many_arguments)]
162 pub async fn new(
163 relayer: RelayerRepoModel,
164 signer: S,
165 provider: P,
166 dependencies: StellarRelayerDependencies<RR, NR, TR, J, TCS>,
167 ) -> Result<Self, RelayerError> {
168 let network_repo = dependencies
169 .network_repository
170 .get_by_name(NetworkType::Stellar, &relayer.network)
171 .await
172 .ok()
173 .flatten()
174 .ok_or_else(|| {
175 RelayerError::NetworkConfiguration(format!("Network {} not found", relayer.network))
176 })?;
177
178 let network = StellarNetwork::try_from(network_repo)?;
179
180 Ok(Self {
181 relayer,
182 signer,
183 network,
184 provider,
185 relayer_repository: dependencies.relayer_repository,
186 network_repository: dependencies.network_repository,
187 transaction_repository: dependencies.transaction_repository,
188 transaction_counter_service: dependencies.transaction_counter_service,
189 job_producer: dependencies.job_producer,
190 })
191 }
192
193 async fn sync_sequence(&self) -> Result<(), RelayerError> {
194 info!(
195 "Syncing sequence for relayer: {} ({})",
196 self.relayer.id, self.relayer.address
197 );
198
199 let next = fetch_next_sequence_from_chain(&self.provider, &self.relayer.address)
200 .await
201 .map_err(RelayerError::ProviderError)?;
202
203 info!(
204 "Setting next sequence {} for relayer {}",
205 next, self.relayer.id
206 );
207 self.transaction_counter_service
208 .set(next)
209 .await
210 .map_err(RelayerError::from)?;
211 Ok(())
212 }
213}
214
215#[async_trait]
216impl<P, RR, NR, TR, J, TCS, S> Relayer for StellarRelayer<P, RR, NR, TR, J, TCS, S>
217where
218 P: StellarProviderTrait + Send + Sync,
219 RR: Repository<RelayerRepoModel, String> + RelayerRepository + Send + Sync + 'static,
220 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
221 TR: Repository<TransactionRepoModel, String> + TransactionRepository + Send + Sync + 'static,
222 J: JobProducerTrait + Send + Sync + 'static,
223 TCS: TransactionCounterServiceTrait + Send + Sync + 'static,
224 S: StellarSignTrait + Send + Sync + 'static,
225{
226 async fn process_transaction_request(
227 &self,
228 network_transaction: NetworkTransactionRequest,
229 ) -> Result<TransactionRepoModel, RelayerError> {
230 let network_model = self
231 .network_repository
232 .get_by_name(NetworkType::Stellar, &self.relayer.network)
233 .await?
234 .ok_or_else(|| {
235 RelayerError::NetworkConfiguration(format!(
236 "Network {} not found",
237 self.relayer.network
238 ))
239 })?;
240 let transaction =
241 TransactionRepoModel::try_from((&network_transaction, &self.relayer, &network_model))?;
242
243 self.transaction_repository
244 .create(transaction.clone())
245 .await
246 .map_err(|e| RepositoryError::TransactionFailure(e.to_string()))?;
247
248 self.job_producer
249 .produce_transaction_request_job(
250 TransactionRequest::new(transaction.id.clone(), transaction.relayer_id.clone()),
251 None,
252 )
253 .await?;
254
255 self.job_producer
256 .produce_check_transaction_status_job(
257 TransactionStatusCheck::new(
258 transaction.id.clone(),
259 transaction.relayer_id.clone(),
260 crate::models::NetworkType::Stellar,
261 ),
262 Some(calculate_scheduled_timestamp(
263 STELLAR_STATUS_CHECK_INITIAL_DELAY_SECONDS,
264 )),
265 )
266 .await?;
267
268 Ok(transaction)
269 }
270
271 async fn get_balance(&self) -> Result<BalanceResponse, RelayerError> {
272 let account_entry = self
273 .provider
274 .get_account(&self.relayer.address)
275 .await
276 .map_err(|e| {
277 RelayerError::ProviderError(format!("Failed to fetch account for balance: {e}"))
278 })?;
279
280 Ok(BalanceResponse {
281 balance: account_entry.balance as u128,
282 unit: STELLAR_SMALLEST_UNIT_NAME.to_string(),
283 })
284 }
285
286 async fn get_status(&self) -> Result<RelayerStatus, RelayerError> {
287 let relayer_model = &self.relayer;
288
289 let account_entry = self
290 .provider
291 .get_account(&relayer_model.address)
292 .await
293 .map_err(|e| {
294 RelayerError::ProviderError(format!("Failed to get account details: {e}"))
295 })?;
296
297 let sequence_number_str = account_entry.seq_num.0.to_string();
298
299 let balance_response = self.get_balance().await?;
300
301 let pending_statuses = [TransactionStatus::Pending, TransactionStatus::Submitted];
302 let pending_transactions = self
303 .transaction_repository
304 .find_by_status(&relayer_model.id, &pending_statuses[..])
305 .await
306 .map_err(RelayerError::from)?;
307 let pending_transactions_count = pending_transactions.len() as u64;
308
309 let confirmed_statuses = [TransactionStatus::Confirmed];
310 let confirmed_transactions = self
311 .transaction_repository
312 .find_by_status(&relayer_model.id, &confirmed_statuses[..])
313 .await
314 .map_err(RelayerError::from)?;
315
316 let last_confirmed_transaction_timestamp = confirmed_transactions
317 .iter()
318 .filter_map(|tx| tx.confirmed_at.as_ref())
319 .max()
320 .cloned();
321
322 Ok(RelayerStatus::Stellar {
323 balance: balance_response.balance.to_string(),
324 pending_transactions_count,
325 last_confirmed_transaction_timestamp,
326 system_disabled: relayer_model.system_disabled,
327 paused: relayer_model.paused,
328 sequence_number: sequence_number_str,
329 })
330 }
331
332 async fn delete_pending_transactions(
333 &self,
334 ) -> Result<DeletePendingTransactionsResponse, RelayerError> {
335 println!("Stellar delete_pending_transactions...");
336 Ok(DeletePendingTransactionsResponse {
337 queued_for_cancellation_transaction_ids: vec![],
338 failed_to_queue_transaction_ids: vec![],
339 total_processed: 0,
340 })
341 }
342
343 async fn sign_data(&self, _request: SignDataRequest) -> Result<SignDataResponse, RelayerError> {
344 Err(RelayerError::NotSupported(
345 "Signing data not supported for Stellar".to_string(),
346 ))
347 }
348
349 async fn sign_typed_data(
350 &self,
351 _request: SignTypedDataRequest,
352 ) -> Result<SignDataResponse, RelayerError> {
353 Err(RelayerError::NotSupported(
354 "Signing typed data not supported for Stellar".to_string(),
355 ))
356 }
357
358 async fn rpc(
359 &self,
360 request: JsonRpcRequest<NetworkRpcRequest>,
361 ) -> Result<JsonRpcResponse<NetworkRpcResult>, RelayerError> {
362 let JsonRpcRequest { id, params, .. } = request;
363 let stellar_request = match params {
364 NetworkRpcRequest::Stellar(stellar_req) => stellar_req,
365 _ => {
366 return Ok(create_error_response(
367 id.clone(),
368 RpcErrorCodes::INVALID_PARAMS,
369 "Invalid params",
370 "Expected Stellar network request",
371 ))
372 }
373 };
374
375 let (method, params_json) = match stellar_request {
377 StellarRpcRequest::RawRpcRequest { method, params } => (method, params),
378 };
379
380 match self
381 .provider
382 .raw_request_dyn(&method, params_json, id.clone())
383 .await
384 {
385 Ok(result_value) => Ok(create_success_response(id.clone(), result_value)),
386 Err(provider_error) => {
387 let (error_code, error_message) = map_provider_error(&provider_error);
388 Ok(create_error_response(
389 id.clone(),
390 error_code,
391 error_message,
392 &provider_error.to_string(),
393 ))
394 }
395 }
396 }
397
398 async fn validate_min_balance(&self) -> Result<(), RelayerError> {
399 Ok(())
400 }
401
402 async fn check_health(&self) -> Result<(), Vec<HealthCheckFailure>> {
403 debug!(
404 "running health checks for Stellar relayer {}",
405 self.relayer.id
406 );
407
408 match self.sync_sequence().await {
409 Ok(_) => {
410 debug!(
411 "all health checks passed for Stellar relayer {}",
412 self.relayer.id
413 );
414 Ok(())
415 }
416 Err(e) => {
417 let reason = HealthCheckFailure::SequenceSyncFailed(e.to_string());
418 warn!("health checks failed: {:?}", reason);
419 Err(vec![reason])
420 }
421 }
422 }
423
424 async fn initialize_relayer(&self) -> Result<(), RelayerError> {
425 debug!("initializing Stellar relayer {}", self.relayer.id);
426
427 match self.check_health().await {
428 Ok(_) => {
429 if self.relayer.system_disabled {
431 self.relayer_repository
433 .enable_relayer(self.relayer.id.clone())
434 .await?;
435 }
436
437 info!(
438 "Stellar relayer initialized successfully: {}",
439 self.relayer.id
440 );
441 Ok(())
442 }
443 Err(failures) => {
444 let reason = DisabledReason::from_health_failures(failures).unwrap_or_else(|| {
446 DisabledReason::SequenceSyncFailed("Unknown error".to_string())
447 });
448
449 warn!(reason = %reason, "disabling relayer");
450 let updated_relayer = self
451 .relayer_repository
452 .disable_relayer(self.relayer.id.clone(), reason.clone())
453 .await?;
454
455 if let Some(notification_id) = &self.relayer.notification_id {
457 self.job_producer
458 .produce_send_notification_job(
459 produce_relayer_disabled_payload(
460 notification_id,
461 &updated_relayer,
462 &reason.safe_description(),
463 ),
464 None,
465 )
466 .await?;
467 }
468
469 self.job_producer
471 .produce_relayer_health_check_job(
472 RelayerHealthCheck::new(self.relayer.id.clone()),
473 Some(calculate_scheduled_timestamp(10)),
474 )
475 .await?;
476
477 Ok(())
478 }
479 }
480 }
481
482 async fn sign_transaction(
483 &self,
484 request: &SignTransactionRequest,
485 ) -> Result<SignTransactionExternalResponse, RelayerError> {
486 let stellar_req = match request {
487 SignTransactionRequest::Stellar(req) => req,
488 _ => {
489 return Err(RelayerError::NotSupported(
490 "Invalid request type for Stellar relayer".to_string(),
491 ))
492 }
493 };
494
495 let response = self
497 .signer
498 .sign_xdr_transaction(&stellar_req.unsigned_xdr, &self.network.passphrase)
499 .await
500 .map_err(RelayerError::SignerError)?;
501
502 let signature_bytes = &response.signature.signature.0;
504 let signature_string =
505 base64::Engine::encode(&base64::engine::general_purpose::STANDARD, signature_bytes);
506
507 Ok(SignTransactionExternalResponse::Stellar(
508 SignTransactionExternalResponseStellar {
509 signed_xdr: response.signed_xdr,
510 signature: signature_string,
511 },
512 ))
513 }
514}
515
516#[cfg(test)]
517mod tests {
518 use super::*;
519 use crate::{
520 config::{NetworkConfigCommon, StellarNetworkConfig},
521 constants::STELLAR_SMALLEST_UNIT_NAME,
522 domain::{SignTransactionRequestStellar, SignXdrTransactionResponseStellar},
523 jobs::MockJobProducerTrait,
524 models::{
525 NetworkConfigData, NetworkRepoModel, NetworkType, RelayerNetworkPolicy,
526 RelayerRepoModel, RelayerStellarPolicy, SignerError,
527 },
528 repositories::{
529 InMemoryNetworkRepository, MockRelayerRepository, MockTransactionRepository,
530 },
531 services::{
532 provider::{MockStellarProviderTrait, ProviderError},
533 signer::MockStellarSignTrait,
534 MockTransactionCounterServiceTrait,
535 },
536 };
537 use mockall::predicate::*;
538 use soroban_rs::xdr::{
539 AccountEntry, AccountEntryExt, AccountId, DecoratedSignature, PublicKey, SequenceNumber,
540 Signature, SignatureHint, String32, Thresholds, Uint256, VecM,
541 };
542 use std::future::ready;
543 use std::sync::Arc;
544
545 struct TestCtx {
547 relayer_model: RelayerRepoModel,
548 network_repository: Arc<InMemoryNetworkRepository>,
549 }
550
551 impl Default for TestCtx {
552 fn default() -> Self {
553 let network_repository = Arc::new(InMemoryNetworkRepository::new());
554
555 let relayer_model = RelayerRepoModel {
556 id: "test-relayer-id".to_string(),
557 name: "Test Relayer".to_string(),
558 network: "testnet".to_string(),
559 paused: false,
560 network_type: NetworkType::Stellar,
561 signer_id: "signer-id".to_string(),
562 policies: RelayerNetworkPolicy::Stellar(RelayerStellarPolicy::default()),
563 address: "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF".to_string(),
564 notification_id: Some("notification-id".to_string()),
565 system_disabled: false,
566 custom_rpc_urls: None,
567 ..Default::default()
568 };
569
570 TestCtx {
571 relayer_model,
572 network_repository,
573 }
574 }
575 }
576
577 impl TestCtx {
578 async fn setup_network(&self) {
579 let test_network = NetworkRepoModel {
580 id: "stellar:testnet".to_string(),
581 name: "testnet".to_string(),
582 network_type: NetworkType::Stellar,
583 config: NetworkConfigData::Stellar(StellarNetworkConfig {
584 common: NetworkConfigCommon {
585 network: "testnet".to_string(),
586 from: None,
587 rpc_urls: Some(vec!["https://horizon-testnet.stellar.org".to_string()]),
588 explorer_urls: None,
589 average_blocktime_ms: Some(5000),
590 is_testnet: Some(true),
591 tags: None,
592 },
593 passphrase: Some("Test SDF Network ; September 2015".to_string()),
594 }),
595 };
596
597 self.network_repository.create(test_network).await.unwrap();
598 }
599 }
600
601 #[tokio::test]
602 async fn test_sync_sequence_success() {
603 let ctx = TestCtx::default();
604 ctx.setup_network().await;
605 let relayer_model = ctx.relayer_model.clone();
606 let mut provider = MockStellarProviderTrait::new();
607 provider
608 .expect_get_account()
609 .with(eq(relayer_model.address.clone()))
610 .returning(|_| {
611 Box::pin(async {
612 Ok(AccountEntry {
613 account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32]))),
614 balance: 0,
615 ext: AccountEntryExt::V0,
616 flags: 0,
617 home_domain: String32::default(),
618 inflation_dest: None,
619 seq_num: SequenceNumber(5),
620 num_sub_entries: 0,
621 signers: VecM::default(),
622 thresholds: Thresholds([0, 0, 0, 0]),
623 })
624 })
625 });
626 let mut counter = MockTransactionCounterServiceTrait::new();
627 counter
628 .expect_set()
629 .with(eq(6u64))
630 .returning(|_| Box::pin(async { Ok(()) }));
631 let relayer_repo = MockRelayerRepository::new();
632 let tx_repo = MockTransactionRepository::new();
633 let job_producer = MockJobProducerTrait::new();
634 let signer = MockStellarSignTrait::new();
635
636 let relayer = StellarRelayer::new(
637 relayer_model.clone(),
638 signer,
639 provider,
640 StellarRelayerDependencies::new(
641 Arc::new(relayer_repo),
642 ctx.network_repository.clone(),
643 Arc::new(tx_repo),
644 Arc::new(counter),
645 Arc::new(job_producer),
646 ),
647 )
648 .await
649 .unwrap();
650
651 let result = relayer.sync_sequence().await;
652 assert!(result.is_ok());
653 }
654
655 #[tokio::test]
656 async fn test_sync_sequence_provider_error() {
657 let ctx = TestCtx::default();
658 ctx.setup_network().await;
659 let relayer_model = ctx.relayer_model.clone();
660 let mut provider = MockStellarProviderTrait::new();
661 provider
662 .expect_get_account()
663 .with(eq(relayer_model.address.clone()))
664 .returning(|_| Box::pin(async { Err(ProviderError::Other("fail".to_string())) }));
665 let counter = MockTransactionCounterServiceTrait::new();
666 let relayer_repo = MockRelayerRepository::new();
667 let tx_repo = MockTransactionRepository::new();
668 let job_producer = MockJobProducerTrait::new();
669 let signer = MockStellarSignTrait::new();
670
671 let relayer = StellarRelayer::new(
672 relayer_model.clone(),
673 signer,
674 provider,
675 StellarRelayerDependencies::new(
676 Arc::new(relayer_repo),
677 ctx.network_repository.clone(),
678 Arc::new(tx_repo),
679 Arc::new(counter),
680 Arc::new(job_producer),
681 ),
682 )
683 .await
684 .unwrap();
685
686 let result = relayer.sync_sequence().await;
687 assert!(matches!(result, Err(RelayerError::ProviderError(_))));
688 }
689
690 #[tokio::test]
691 async fn test_get_status_success_stellar() {
692 let ctx = TestCtx::default();
693 ctx.setup_network().await;
694 let relayer_model = ctx.relayer_model.clone();
695 let mut provider_mock = MockStellarProviderTrait::new();
696 let mut tx_repo_mock = MockTransactionRepository::new();
697 let relayer_repo_mock = MockRelayerRepository::new();
698 let job_producer_mock = MockJobProducerTrait::new();
699 let counter_mock = MockTransactionCounterServiceTrait::new();
700
701 provider_mock.expect_get_account().times(2).returning(|_| {
702 Box::pin(ready(Ok(AccountEntry {
703 account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32]))),
704 balance: 10000000,
705 seq_num: SequenceNumber(12345),
706 ext: AccountEntryExt::V0,
707 flags: 0,
708 home_domain: String32::default(),
709 inflation_dest: None,
710 num_sub_entries: 0,
711 signers: VecM::default(),
712 thresholds: Thresholds([0, 0, 0, 0]),
713 })))
714 });
715
716 tx_repo_mock
717 .expect_find_by_status()
718 .withf(|relayer_id, statuses| {
719 relayer_id == "test-relayer-id"
720 && statuses == [TransactionStatus::Pending, TransactionStatus::Submitted]
721 })
722 .returning(|_, _| Ok(vec![]) as Result<Vec<TransactionRepoModel>, RepositoryError>)
723 .once();
724
725 let confirmed_tx = TransactionRepoModel {
726 id: "tx1_stellar".to_string(),
727 relayer_id: relayer_model.id.clone(),
728 status: TransactionStatus::Confirmed,
729 confirmed_at: Some("2023-02-01T12:00:00Z".to_string()),
730 ..TransactionRepoModel::default()
731 };
732 tx_repo_mock
733 .expect_find_by_status()
734 .withf(|relayer_id, statuses| {
735 relayer_id == "test-relayer-id" && statuses == [TransactionStatus::Confirmed]
736 })
737 .returning(move |_, _| {
738 Ok(vec![confirmed_tx.clone()]) as Result<Vec<TransactionRepoModel>, RepositoryError>
739 })
740 .once();
741 let signer = MockStellarSignTrait::new();
742
743 let stellar_relayer = StellarRelayer::new(
744 relayer_model.clone(),
745 signer,
746 provider_mock,
747 StellarRelayerDependencies::new(
748 Arc::new(relayer_repo_mock),
749 ctx.network_repository.clone(),
750 Arc::new(tx_repo_mock),
751 Arc::new(counter_mock),
752 Arc::new(job_producer_mock),
753 ),
754 )
755 .await
756 .unwrap();
757
758 let status = stellar_relayer.get_status().await.unwrap();
759
760 match status {
761 RelayerStatus::Stellar {
762 balance,
763 pending_transactions_count,
764 last_confirmed_transaction_timestamp,
765 system_disabled,
766 paused,
767 sequence_number,
768 } => {
769 assert_eq!(balance, "10000000");
770 assert_eq!(pending_transactions_count, 0);
771 assert_eq!(
772 last_confirmed_transaction_timestamp,
773 Some("2023-02-01T12:00:00Z".to_string())
774 );
775 assert_eq!(system_disabled, relayer_model.system_disabled);
776 assert_eq!(paused, relayer_model.paused);
777 assert_eq!(sequence_number, "12345");
778 }
779 _ => panic!("Expected Stellar RelayerStatus"),
780 }
781 }
782
783 #[tokio::test]
784 async fn test_get_status_stellar_provider_error() {
785 let ctx = TestCtx::default();
786 ctx.setup_network().await;
787 let relayer_model = ctx.relayer_model.clone();
788 let mut provider_mock = MockStellarProviderTrait::new();
789 let tx_repo_mock = MockTransactionRepository::new();
790 let relayer_repo_mock = MockRelayerRepository::new();
791 let job_producer_mock = MockJobProducerTrait::new();
792 let counter_mock = MockTransactionCounterServiceTrait::new();
793
794 provider_mock
795 .expect_get_account()
796 .with(eq(relayer_model.address.clone()))
797 .returning(|_| {
798 Box::pin(async { Err(ProviderError::Other("Stellar provider down".to_string())) })
799 });
800 let signer = MockStellarSignTrait::new();
801
802 let stellar_relayer = StellarRelayer::new(
803 relayer_model.clone(),
804 signer,
805 provider_mock,
806 StellarRelayerDependencies::new(
807 Arc::new(relayer_repo_mock),
808 ctx.network_repository.clone(),
809 Arc::new(tx_repo_mock),
810 Arc::new(counter_mock),
811 Arc::new(job_producer_mock),
812 ),
813 )
814 .await
815 .unwrap();
816
817 let result = stellar_relayer.get_status().await;
818 assert!(result.is_err());
819 match result.err().unwrap() {
820 RelayerError::ProviderError(msg) => {
821 assert!(msg.contains("Failed to get account details"))
822 }
823 _ => panic!("Expected ProviderError for get_account failure"),
824 }
825 }
826
827 #[tokio::test]
828 async fn test_get_balance_success() {
829 let ctx = TestCtx::default();
830 ctx.setup_network().await;
831 let relayer_model = ctx.relayer_model.clone();
832 let mut provider = MockStellarProviderTrait::new();
833 let expected_balance = 100_000_000i64; provider
836 .expect_get_account()
837 .with(eq(relayer_model.address.clone()))
838 .returning(move |_| {
839 Box::pin(async move {
840 Ok(AccountEntry {
841 account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32]))),
842 balance: expected_balance,
843 ext: AccountEntryExt::V0,
844 flags: 0,
845 home_domain: String32::default(),
846 inflation_dest: None,
847 seq_num: SequenceNumber(5),
848 num_sub_entries: 0,
849 signers: VecM::default(),
850 thresholds: Thresholds([0, 0, 0, 0]),
851 })
852 })
853 });
854
855 let relayer_repo = Arc::new(MockRelayerRepository::new());
856 let tx_repo = Arc::new(MockTransactionRepository::new());
857 let job_producer = Arc::new(MockJobProducerTrait::new());
858 let counter = Arc::new(MockTransactionCounterServiceTrait::new());
859 let signer = MockStellarSignTrait::new();
860
861 let relayer = StellarRelayer::new(
862 relayer_model,
863 signer,
864 provider,
865 StellarRelayerDependencies::new(
866 relayer_repo,
867 ctx.network_repository.clone(),
868 tx_repo,
869 counter,
870 job_producer,
871 ),
872 )
873 .await
874 .unwrap();
875
876 let result = relayer.get_balance().await;
877 assert!(result.is_ok());
878 let balance_response = result.unwrap();
879 assert_eq!(balance_response.balance, expected_balance as u128);
880 assert_eq!(balance_response.unit, STELLAR_SMALLEST_UNIT_NAME);
881 }
882
883 #[tokio::test]
884 async fn test_get_balance_provider_error() {
885 let ctx = TestCtx::default();
886 ctx.setup_network().await;
887 let relayer_model = ctx.relayer_model.clone();
888 let mut provider = MockStellarProviderTrait::new();
889
890 provider
891 .expect_get_account()
892 .with(eq(relayer_model.address.clone()))
893 .returning(|_| {
894 Box::pin(async { Err(ProviderError::Other("provider failed".to_string())) })
895 });
896
897 let relayer_repo = Arc::new(MockRelayerRepository::new());
898 let tx_repo = Arc::new(MockTransactionRepository::new());
899 let job_producer = Arc::new(MockJobProducerTrait::new());
900 let counter = Arc::new(MockTransactionCounterServiceTrait::new());
901 let signer = MockStellarSignTrait::new();
902
903 let relayer = StellarRelayer::new(
904 relayer_model,
905 signer,
906 provider,
907 StellarRelayerDependencies::new(
908 relayer_repo,
909 ctx.network_repository.clone(),
910 tx_repo,
911 counter,
912 job_producer,
913 ),
914 )
915 .await
916 .unwrap();
917
918 let result = relayer.get_balance().await;
919 assert!(result.is_err());
920 match result.err().unwrap() {
921 RelayerError::ProviderError(msg) => {
922 assert!(msg.contains("Failed to fetch account for balance"));
923 }
924 _ => panic!("Unexpected error type"),
925 }
926 }
927
928 #[tokio::test]
929 async fn test_sign_transaction_success() {
930 let ctx = TestCtx::default();
931 ctx.setup_network().await;
932 let relayer_model = ctx.relayer_model.clone();
933 let provider = MockStellarProviderTrait::new();
934 let mut signer = MockStellarSignTrait::new();
935
936 let unsigned_xdr = "AAAAAgAAAAD///8AAAAAAAAAAQAAAAAAAAACAAAAAQAAAAAAAAAB";
937 let expected_signed_xdr =
938 "AAAAAgAAAAD///8AAAAAAAABAAAAAAAAAAIAAAABAAAAAAAAAAEAAAABAAAAA...";
939 let expected_signature = DecoratedSignature {
940 hint: SignatureHint([1, 2, 3, 4]),
941 signature: Signature([5u8; 64].try_into().unwrap()),
942 };
943 let expected_signature_for_closure = expected_signature.clone();
944
945 signer
946 .expect_sign_xdr_transaction()
947 .with(eq(unsigned_xdr), eq("Test SDF Network ; September 2015"))
948 .returning(move |_, _| {
949 Ok(SignXdrTransactionResponseStellar {
950 signed_xdr: expected_signed_xdr.to_string(),
951 signature: expected_signature_for_closure.clone(),
952 })
953 });
954
955 let relayer_repo = Arc::new(MockRelayerRepository::new());
956 let tx_repo = Arc::new(MockTransactionRepository::new());
957 let job_producer = Arc::new(MockJobProducerTrait::new());
958 let counter = Arc::new(MockTransactionCounterServiceTrait::new());
959
960 let relayer = StellarRelayer::new(
961 relayer_model,
962 signer,
963 provider,
964 StellarRelayerDependencies::new(
965 relayer_repo,
966 ctx.network_repository.clone(),
967 tx_repo,
968 counter,
969 job_producer,
970 ),
971 )
972 .await
973 .unwrap();
974
975 let request = SignTransactionRequest::Stellar(SignTransactionRequestStellar {
976 unsigned_xdr: unsigned_xdr.to_string(),
977 });
978 let result = relayer.sign_transaction(&request).await;
979 assert!(result.is_ok());
980
981 match result.unwrap() {
982 SignTransactionExternalResponse::Stellar(response) => {
983 assert_eq!(response.signed_xdr, expected_signed_xdr);
984 let expected_signature_base64 = base64::Engine::encode(
986 &base64::engine::general_purpose::STANDARD,
987 &expected_signature.signature.0,
988 );
989 assert_eq!(response.signature, expected_signature_base64);
990 }
991 _ => panic!("Expected Stellar response"),
992 }
993 }
994
995 #[tokio::test]
996 async fn test_sign_transaction_signer_error() {
997 let ctx = TestCtx::default();
998 ctx.setup_network().await;
999 let relayer_model = ctx.relayer_model.clone();
1000 let provider = MockStellarProviderTrait::new();
1001 let mut signer = MockStellarSignTrait::new();
1002
1003 let unsigned_xdr = "INVALID_XDR";
1004
1005 signer
1006 .expect_sign_xdr_transaction()
1007 .with(eq(unsigned_xdr), eq("Test SDF Network ; September 2015"))
1008 .returning(|_, _| Err(SignerError::SigningError("Invalid XDR format".to_string())));
1009
1010 let relayer_repo = Arc::new(MockRelayerRepository::new());
1011 let tx_repo = Arc::new(MockTransactionRepository::new());
1012 let job_producer = Arc::new(MockJobProducerTrait::new());
1013 let counter = Arc::new(MockTransactionCounterServiceTrait::new());
1014
1015 let relayer = StellarRelayer::new(
1016 relayer_model,
1017 signer,
1018 provider,
1019 StellarRelayerDependencies::new(
1020 relayer_repo,
1021 ctx.network_repository.clone(),
1022 tx_repo,
1023 counter,
1024 job_producer,
1025 ),
1026 )
1027 .await
1028 .unwrap();
1029
1030 let request = SignTransactionRequest::Stellar(SignTransactionRequestStellar {
1031 unsigned_xdr: unsigned_xdr.to_string(),
1032 });
1033 let result = relayer.sign_transaction(&request).await;
1034 assert!(result.is_err());
1035
1036 match result.err().unwrap() {
1037 RelayerError::SignerError(err) => match err {
1038 SignerError::SigningError(msg) => {
1039 assert_eq!(msg, "Invalid XDR format");
1040 }
1041 _ => panic!("Expected SigningError"),
1042 },
1043 _ => panic!("Expected RelayerError::SignerError"),
1044 }
1045 }
1046
1047 #[tokio::test]
1048 async fn test_sign_transaction_with_different_network_passphrase() {
1049 let ctx = TestCtx::default();
1050 let custom_network = NetworkRepoModel {
1052 id: "stellar:mainnet".to_string(),
1053 name: "mainnet".to_string(),
1054 network_type: NetworkType::Stellar,
1055 config: NetworkConfigData::Stellar(StellarNetworkConfig {
1056 common: NetworkConfigCommon {
1057 network: "mainnet".to_string(),
1058 from: None,
1059 rpc_urls: Some(vec!["https://horizon.stellar.org".to_string()]),
1060 explorer_urls: None,
1061 average_blocktime_ms: Some(5000),
1062 is_testnet: Some(false),
1063 tags: None,
1064 },
1065 passphrase: Some("Public Global Stellar Network ; September 2015".to_string()),
1066 }),
1067 };
1068 ctx.network_repository.create(custom_network).await.unwrap();
1069
1070 let mut relayer_model = ctx.relayer_model.clone();
1071 relayer_model.network = "mainnet".to_string();
1072
1073 let provider = MockStellarProviderTrait::new();
1074 let mut signer = MockStellarSignTrait::new();
1075
1076 let unsigned_xdr = "AAAAAgAAAAD///8AAAAAAAAAAQAAAAAAAAACAAAAAQAAAAAAAAAB";
1077 let expected_signature = DecoratedSignature {
1078 hint: SignatureHint([10, 20, 30, 40]),
1079 signature: Signature([15u8; 64].try_into().unwrap()),
1080 };
1081 let expected_signature_for_closure = expected_signature.clone();
1082
1083 signer
1084 .expect_sign_xdr_transaction()
1085 .with(
1086 eq(unsigned_xdr),
1087 eq("Public Global Stellar Network ; September 2015"),
1088 )
1089 .returning(move |_, _| {
1090 Ok(SignXdrTransactionResponseStellar {
1091 signed_xdr: "mainnet_signed_xdr".to_string(),
1092 signature: expected_signature_for_closure.clone(),
1093 })
1094 });
1095
1096 let relayer_repo = Arc::new(MockRelayerRepository::new());
1097 let tx_repo = Arc::new(MockTransactionRepository::new());
1098 let job_producer = Arc::new(MockJobProducerTrait::new());
1099 let counter = Arc::new(MockTransactionCounterServiceTrait::new());
1100
1101 let relayer = StellarRelayer::new(
1102 relayer_model,
1103 signer,
1104 provider,
1105 StellarRelayerDependencies::new(
1106 relayer_repo,
1107 ctx.network_repository.clone(),
1108 tx_repo,
1109 counter,
1110 job_producer,
1111 ),
1112 )
1113 .await
1114 .unwrap();
1115
1116 let request = SignTransactionRequest::Stellar(SignTransactionRequestStellar {
1117 unsigned_xdr: unsigned_xdr.to_string(),
1118 });
1119 let result = relayer.sign_transaction(&request).await;
1120 assert!(result.is_ok());
1121
1122 match result.unwrap() {
1123 SignTransactionExternalResponse::Stellar(response) => {
1124 assert_eq!(response.signed_xdr, "mainnet_signed_xdr");
1125 let expected_signature_string = base64::Engine::encode(
1127 &base64::engine::general_purpose::STANDARD,
1128 &expected_signature.signature.0,
1129 );
1130 assert_eq!(response.signature, expected_signature_string);
1131 }
1132 _ => panic!("Expected Stellar response"),
1133 }
1134 }
1135
1136 #[tokio::test]
1137 async fn test_initialize_relayer_disables_when_validation_fails() {
1138 let ctx = TestCtx::default();
1139 ctx.setup_network().await;
1140 let mut relayer_model = ctx.relayer_model.clone();
1141 relayer_model.system_disabled = false; relayer_model.notification_id = Some("test-notification-id".to_string());
1143
1144 let mut provider = MockStellarProviderTrait::new();
1145 let mut relayer_repo = MockRelayerRepository::new();
1146 let mut job_producer = MockJobProducerTrait::new();
1147
1148 provider
1150 .expect_get_account()
1151 .returning(|_| Box::pin(ready(Err(ProviderError::Other("RPC error".to_string())))));
1152
1153 let mut disabled_relayer = relayer_model.clone();
1155 disabled_relayer.system_disabled = true;
1156 relayer_repo
1157 .expect_disable_relayer()
1158 .withf(|id, reason| {
1159 id == "test-relayer-id"
1160 && matches!(reason, crate::models::DisabledReason::SequenceSyncFailed(_))
1161 })
1162 .returning(move |_, _| Ok(disabled_relayer.clone()));
1163
1164 job_producer
1166 .expect_produce_send_notification_job()
1167 .returning(|_, _| Box::pin(async { Ok(()) }));
1168
1169 job_producer
1171 .expect_produce_relayer_health_check_job()
1172 .returning(|_, _| Box::pin(async { Ok(()) }));
1173
1174 let tx_repo = MockTransactionRepository::new();
1175 let counter = MockTransactionCounterServiceTrait::new();
1176 let signer = MockStellarSignTrait::new();
1177
1178 let relayer = StellarRelayer::new(
1179 relayer_model.clone(),
1180 signer,
1181 provider,
1182 StellarRelayerDependencies::new(
1183 Arc::new(relayer_repo),
1184 ctx.network_repository.clone(),
1185 Arc::new(tx_repo),
1186 Arc::new(counter),
1187 Arc::new(job_producer),
1188 ),
1189 )
1190 .await
1191 .unwrap();
1192
1193 let result = relayer.initialize_relayer().await;
1194 assert!(result.is_ok());
1195 }
1196
1197 #[tokio::test]
1198 async fn test_initialize_relayer_enables_when_validation_passes_and_was_disabled() {
1199 let ctx = TestCtx::default();
1200 ctx.setup_network().await;
1201 let mut relayer_model = ctx.relayer_model.clone();
1202 relayer_model.system_disabled = true; let mut provider = MockStellarProviderTrait::new();
1205 let mut relayer_repo = MockRelayerRepository::new();
1206
1207 provider.expect_get_account().returning(|_| {
1209 Box::pin(ready(Ok(AccountEntry {
1210 account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32]))),
1211 balance: 1000000000, seq_num: SequenceNumber(1),
1213 num_sub_entries: 0,
1214 inflation_dest: None,
1215 flags: 0,
1216 home_domain: String32::default(),
1217 thresholds: Thresholds([0; 4]),
1218 signers: VecM::default(),
1219 ext: AccountEntryExt::V0,
1220 })))
1221 });
1222
1223 let mut enabled_relayer = relayer_model.clone();
1225 enabled_relayer.system_disabled = false;
1226 relayer_repo
1227 .expect_enable_relayer()
1228 .with(eq("test-relayer-id".to_string()))
1229 .returning(move |_| Ok(enabled_relayer.clone()));
1230
1231 let tx_repo = MockTransactionRepository::new();
1232 let mut counter = MockTransactionCounterServiceTrait::new();
1233 counter
1234 .expect_set()
1235 .returning(|_| Box::pin(async { Ok(()) }));
1236 let signer = MockStellarSignTrait::new();
1237 let job_producer = MockJobProducerTrait::new();
1238
1239 let relayer = StellarRelayer::new(
1240 relayer_model.clone(),
1241 signer,
1242 provider,
1243 StellarRelayerDependencies::new(
1244 Arc::new(relayer_repo),
1245 ctx.network_repository.clone(),
1246 Arc::new(tx_repo),
1247 Arc::new(counter),
1248 Arc::new(job_producer),
1249 ),
1250 )
1251 .await
1252 .unwrap();
1253
1254 let result = relayer.initialize_relayer().await;
1255 assert!(result.is_ok());
1256 }
1257
1258 #[tokio::test]
1259 async fn test_initialize_relayer_no_action_when_enabled_and_validation_passes() {
1260 let ctx = TestCtx::default();
1261 ctx.setup_network().await;
1262 let mut relayer_model = ctx.relayer_model.clone();
1263 relayer_model.system_disabled = false; let mut provider = MockStellarProviderTrait::new();
1266
1267 provider.expect_get_account().returning(|_| {
1269 Box::pin(ready(Ok(AccountEntry {
1270 account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32]))),
1271 balance: 1000000000, seq_num: SequenceNumber(1),
1273 num_sub_entries: 0,
1274 inflation_dest: None,
1275 flags: 0,
1276 home_domain: String32::default(),
1277 thresholds: Thresholds([0; 4]),
1278 signers: VecM::default(),
1279 ext: AccountEntryExt::V0,
1280 })))
1281 });
1282
1283 let tx_repo = MockTransactionRepository::new();
1286 let mut counter = MockTransactionCounterServiceTrait::new();
1287 counter
1288 .expect_set()
1289 .returning(|_| Box::pin(async { Ok(()) }));
1290 let signer = MockStellarSignTrait::new();
1291 let job_producer = MockJobProducerTrait::new();
1292 let relayer_repo = MockRelayerRepository::new();
1293
1294 let relayer = StellarRelayer::new(
1295 relayer_model.clone(),
1296 signer,
1297 provider,
1298 StellarRelayerDependencies::new(
1299 Arc::new(relayer_repo),
1300 ctx.network_repository.clone(),
1301 Arc::new(tx_repo),
1302 Arc::new(counter),
1303 Arc::new(job_producer),
1304 ),
1305 )
1306 .await
1307 .unwrap();
1308
1309 let result = relayer.initialize_relayer().await;
1310 assert!(result.is_ok());
1311 }
1312
1313 #[tokio::test]
1314 async fn test_initialize_relayer_sends_notification_when_disabled() {
1315 let ctx = TestCtx::default();
1316 ctx.setup_network().await;
1317 let mut relayer_model = ctx.relayer_model.clone();
1318 relayer_model.system_disabled = false; relayer_model.notification_id = Some("test-notification-id".to_string());
1320
1321 let mut provider = MockStellarProviderTrait::new();
1322 let mut relayer_repo = MockRelayerRepository::new();
1323 let mut job_producer = MockJobProducerTrait::new();
1324
1325 provider.expect_get_account().returning(|_| {
1327 Box::pin(ready(Err(ProviderError::Other(
1328 "Sequence sync failed".to_string(),
1329 ))))
1330 });
1331
1332 let mut disabled_relayer = relayer_model.clone();
1334 disabled_relayer.system_disabled = true;
1335 relayer_repo
1336 .expect_disable_relayer()
1337 .withf(|id, reason| {
1338 id == "test-relayer-id"
1339 && matches!(reason, crate::models::DisabledReason::SequenceSyncFailed(_))
1340 })
1341 .returning(move |_, _| Ok(disabled_relayer.clone()));
1342
1343 job_producer
1345 .expect_produce_send_notification_job()
1346 .returning(|_, _| Box::pin(async { Ok(()) }));
1347
1348 job_producer
1350 .expect_produce_relayer_health_check_job()
1351 .returning(|_, _| Box::pin(async { Ok(()) }));
1352
1353 let tx_repo = MockTransactionRepository::new();
1354 let counter = MockTransactionCounterServiceTrait::new();
1355 let signer = MockStellarSignTrait::new();
1356
1357 let relayer = StellarRelayer::new(
1358 relayer_model.clone(),
1359 signer,
1360 provider,
1361 StellarRelayerDependencies::new(
1362 Arc::new(relayer_repo),
1363 ctx.network_repository.clone(),
1364 Arc::new(tx_repo),
1365 Arc::new(counter),
1366 Arc::new(job_producer),
1367 ),
1368 )
1369 .await
1370 .unwrap();
1371
1372 let result = relayer.initialize_relayer().await;
1373 assert!(result.is_ok());
1374 }
1375
1376 #[tokio::test]
1377 async fn test_initialize_relayer_no_notification_when_no_notification_id() {
1378 let ctx = TestCtx::default();
1379 ctx.setup_network().await;
1380 let mut relayer_model = ctx.relayer_model.clone();
1381 relayer_model.system_disabled = false; relayer_model.notification_id = None; let mut provider = MockStellarProviderTrait::new();
1385 let mut relayer_repo = MockRelayerRepository::new();
1386
1387 provider.expect_get_account().returning(|_| {
1389 Box::pin(ready(Err(ProviderError::Other(
1390 "Sequence sync failed".to_string(),
1391 ))))
1392 });
1393
1394 let mut disabled_relayer = relayer_model.clone();
1396 disabled_relayer.system_disabled = true;
1397 relayer_repo
1398 .expect_disable_relayer()
1399 .withf(|id, reason| {
1400 id == "test-relayer-id"
1401 && matches!(reason, crate::models::DisabledReason::SequenceSyncFailed(_))
1402 })
1403 .returning(move |_, _| Ok(disabled_relayer.clone()));
1404
1405 let mut job_producer = MockJobProducerTrait::new();
1408 job_producer
1409 .expect_produce_relayer_health_check_job()
1410 .returning(|_, _| Box::pin(async { Ok(()) }));
1411
1412 let tx_repo = MockTransactionRepository::new();
1413 let counter = MockTransactionCounterServiceTrait::new();
1414 let signer = MockStellarSignTrait::new();
1415
1416 let relayer = StellarRelayer::new(
1417 relayer_model.clone(),
1418 signer,
1419 provider,
1420 StellarRelayerDependencies::new(
1421 Arc::new(relayer_repo),
1422 ctx.network_repository.clone(),
1423 Arc::new(tx_repo),
1424 Arc::new(counter),
1425 Arc::new(job_producer),
1426 ),
1427 )
1428 .await
1429 .unwrap();
1430
1431 let result = relayer.initialize_relayer().await;
1432 assert!(result.is_ok());
1433 }
1434
1435 mod process_transaction_request_tests {
1436 use super::*;
1437 use crate::constants::STELLAR_STATUS_CHECK_INITIAL_DELAY_SECONDS;
1438 use crate::models::{
1439 NetworkTransactionRequest, NetworkType, StellarTransactionRequest, TransactionStatus,
1440 };
1441 use chrono::Utc;
1442
1443 fn create_test_transaction_request() -> NetworkTransactionRequest {
1445 NetworkTransactionRequest::Stellar(StellarTransactionRequest {
1446 source_account: None,
1447 network: "testnet".to_string(),
1448 operations: None,
1449 memo: None,
1450 valid_until: None,
1451 transaction_xdr: Some("AAAAAgAAAACige4lTdwSB/sto4SniEdJ2kOa2X65s5bqkd40J4DjSwAAAAEAAHAkAAAADwAAAAAAAAAAAAAAAQAAAAAAAAABAAAAAKKB7iVN3BIH+y2jhKeIR0naQ5rZfrmzluqR3jQngONLAAAAAAAAAAAAD0JAAAAAAAAAAAA=".to_string()),
1452 fee_bump: None,
1453 max_fee: None,
1454 })
1455 }
1456
1457 #[tokio::test]
1458 async fn test_process_transaction_request_calls_job_producer_methods() {
1459 let ctx = TestCtx::default();
1460 ctx.setup_network().await;
1461 let relayer_model = ctx.relayer_model.clone();
1462
1463 let provider = MockStellarProviderTrait::new();
1464 let signer = MockStellarSignTrait::new();
1465
1466 let tx_request = create_test_transaction_request();
1468
1469 let mut tx_repo = MockTransactionRepository::new();
1471 tx_repo.expect_create().returning(|t| Ok(t.clone()));
1472
1473 let mut job_producer = MockJobProducerTrait::new();
1475
1476 job_producer
1478 .expect_produce_transaction_request_job()
1479 .withf(|req, delay| {
1480 !req.transaction_id.is_empty() && !req.relayer_id.is_empty() && delay.is_none()
1481 })
1482 .times(1)
1483 .returning(|_, _| Box::pin(async { Ok(()) }));
1484
1485 job_producer
1487 .expect_produce_check_transaction_status_job()
1488 .withf(|check, delay| {
1489 !check.transaction_id.is_empty()
1490 && !check.relayer_id.is_empty()
1491 && check.network_type == Some(NetworkType::Stellar)
1492 && delay.is_some()
1493 })
1494 .times(1)
1495 .returning(|_, _| Box::pin(async { Ok(()) }));
1496
1497 let relayer_repo = Arc::new(MockRelayerRepository::new());
1498 let counter = MockTransactionCounterServiceTrait::new();
1499
1500 let relayer = StellarRelayer::new(
1501 relayer_model,
1502 signer,
1503 provider,
1504 StellarRelayerDependencies::new(
1505 relayer_repo,
1506 ctx.network_repository.clone(),
1507 Arc::new(tx_repo),
1508 Arc::new(counter),
1509 Arc::new(job_producer),
1510 ),
1511 )
1512 .await
1513 .unwrap();
1514
1515 let result = relayer.process_transaction_request(tx_request).await;
1516 if let Err(e) = &result {
1517 panic!("process_transaction_request failed: {}", e);
1518 }
1519 assert!(result.is_ok());
1520 }
1521
1522 #[tokio::test]
1523 async fn test_process_transaction_request_with_scheduled_delay() {
1524 let ctx = TestCtx::default();
1525 ctx.setup_network().await;
1526 let relayer_model = ctx.relayer_model.clone();
1527
1528 let provider = MockStellarProviderTrait::new();
1529 let signer = MockStellarSignTrait::new();
1530
1531 let tx_request = create_test_transaction_request();
1532
1533 let mut tx_repo = MockTransactionRepository::new();
1534 tx_repo.expect_create().returning(|t| Ok(t.clone()));
1535
1536 let mut job_producer = MockJobProducerTrait::new();
1537
1538 job_producer
1539 .expect_produce_transaction_request_job()
1540 .returning(|_, _| Box::pin(async { Ok(()) }));
1541
1542 job_producer
1544 .expect_produce_check_transaction_status_job()
1545 .withf(|_, delay| {
1546 if let Some(scheduled_at) = delay {
1548 let now = Utc::now().timestamp();
1550 let diff = scheduled_at - now;
1551 diff >= (STELLAR_STATUS_CHECK_INITIAL_DELAY_SECONDS - 2)
1553 && diff <= (STELLAR_STATUS_CHECK_INITIAL_DELAY_SECONDS + 2)
1554 } else {
1555 false
1556 }
1557 })
1558 .times(1)
1559 .returning(|_, _| Box::pin(async { Ok(()) }));
1560
1561 let relayer_repo = Arc::new(MockRelayerRepository::new());
1562 let counter = MockTransactionCounterServiceTrait::new();
1563
1564 let relayer = StellarRelayer::new(
1565 relayer_model,
1566 signer,
1567 provider,
1568 StellarRelayerDependencies::new(
1569 relayer_repo,
1570 ctx.network_repository.clone(),
1571 Arc::new(tx_repo),
1572 Arc::new(counter),
1573 Arc::new(job_producer),
1574 ),
1575 )
1576 .await
1577 .unwrap();
1578
1579 let result = relayer.process_transaction_request(tx_request).await;
1580 assert!(result.is_ok());
1581 }
1582
1583 #[tokio::test]
1584 async fn test_process_transaction_request_repository_failure() {
1585 let ctx = TestCtx::default();
1586 ctx.setup_network().await;
1587 let relayer_model = ctx.relayer_model.clone();
1588
1589 let provider = MockStellarProviderTrait::new();
1590 let signer = MockStellarSignTrait::new();
1591
1592 let tx_request = create_test_transaction_request();
1593
1594 let mut tx_repo = MockTransactionRepository::new();
1596 tx_repo.expect_create().returning(|_| {
1597 Err(RepositoryError::TransactionFailure(
1598 "Database connection failed".to_string(),
1599 ))
1600 });
1601
1602 let job_producer = MockJobProducerTrait::new();
1604
1605 let relayer_repo = Arc::new(MockRelayerRepository::new());
1606 let counter = MockTransactionCounterServiceTrait::new();
1607
1608 let relayer = StellarRelayer::new(
1609 relayer_model,
1610 signer,
1611 provider,
1612 StellarRelayerDependencies::new(
1613 relayer_repo,
1614 ctx.network_repository.clone(),
1615 Arc::new(tx_repo),
1616 Arc::new(counter),
1617 Arc::new(job_producer),
1618 ),
1619 )
1620 .await
1621 .unwrap();
1622
1623 let result = relayer.process_transaction_request(tx_request).await;
1624 assert!(result.is_err());
1625 let err_msg = result.err().unwrap().to_string();
1627 assert!(
1628 err_msg.contains("Database connection failed"),
1629 "Error was: {}",
1630 err_msg
1631 );
1632 }
1633
1634 #[tokio::test]
1635 async fn test_process_transaction_request_job_producer_request_failure() {
1636 let ctx = TestCtx::default();
1637 ctx.setup_network().await;
1638 let relayer_model = ctx.relayer_model.clone();
1639
1640 let provider = MockStellarProviderTrait::new();
1641 let signer = MockStellarSignTrait::new();
1642
1643 let tx_request = create_test_transaction_request();
1644
1645 let mut tx_repo = MockTransactionRepository::new();
1646 tx_repo.expect_create().returning(|t| Ok(t.clone()));
1647
1648 let mut job_producer = MockJobProducerTrait::new();
1650 job_producer
1651 .expect_produce_transaction_request_job()
1652 .returning(|_, _| {
1653 Box::pin(async {
1654 Err(crate::jobs::JobProducerError::QueueError(
1655 "Queue is full".to_string(),
1656 ))
1657 })
1658 });
1659
1660 let relayer_repo = Arc::new(MockRelayerRepository::new());
1663 let counter = MockTransactionCounterServiceTrait::new();
1664
1665 let relayer = StellarRelayer::new(
1666 relayer_model,
1667 signer,
1668 provider,
1669 StellarRelayerDependencies::new(
1670 relayer_repo,
1671 ctx.network_repository.clone(),
1672 Arc::new(tx_repo),
1673 Arc::new(counter),
1674 Arc::new(job_producer),
1675 ),
1676 )
1677 .await
1678 .unwrap();
1679
1680 let result = relayer.process_transaction_request(tx_request).await;
1681 assert!(result.is_err());
1682 }
1683
1684 #[tokio::test]
1685 async fn test_process_transaction_request_job_producer_status_check_failure() {
1686 let ctx = TestCtx::default();
1687 ctx.setup_network().await;
1688 let relayer_model = ctx.relayer_model.clone();
1689
1690 let provider = MockStellarProviderTrait::new();
1691 let signer = MockStellarSignTrait::new();
1692
1693 let tx_request = create_test_transaction_request();
1694
1695 let mut tx_repo = MockTransactionRepository::new();
1696 tx_repo.expect_create().returning(|t| Ok(t.clone()));
1697
1698 let mut job_producer = MockJobProducerTrait::new();
1699
1700 job_producer
1702 .expect_produce_transaction_request_job()
1703 .returning(|_, _| Box::pin(async { Ok(()) }));
1704
1705 job_producer
1707 .expect_produce_check_transaction_status_job()
1708 .returning(|_, _| {
1709 Box::pin(async {
1710 Err(crate::jobs::JobProducerError::QueueError(
1711 "Failed to queue job".to_string(),
1712 ))
1713 })
1714 });
1715
1716 let relayer_repo = Arc::new(MockRelayerRepository::new());
1717 let counter = MockTransactionCounterServiceTrait::new();
1718
1719 let relayer = StellarRelayer::new(
1720 relayer_model,
1721 signer,
1722 provider,
1723 StellarRelayerDependencies::new(
1724 relayer_repo,
1725 ctx.network_repository.clone(),
1726 Arc::new(tx_repo),
1727 Arc::new(counter),
1728 Arc::new(job_producer),
1729 ),
1730 )
1731 .await
1732 .unwrap();
1733
1734 let result = relayer.process_transaction_request(tx_request).await;
1735 assert!(result.is_err());
1736 }
1737
1738 #[tokio::test]
1739 async fn test_process_transaction_request_preserves_transaction_data() {
1740 let ctx = TestCtx::default();
1741 ctx.setup_network().await;
1742 let relayer_model = ctx.relayer_model.clone();
1743
1744 let provider = MockStellarProviderTrait::new();
1745 let signer = MockStellarSignTrait::new();
1746
1747 let tx_request = create_test_transaction_request();
1748
1749 let mut tx_repo = MockTransactionRepository::new();
1750 tx_repo.expect_create().returning(|t| Ok(t.clone()));
1751
1752 let mut job_producer = MockJobProducerTrait::new();
1753 job_producer
1754 .expect_produce_transaction_request_job()
1755 .returning(|_, _| Box::pin(async { Ok(()) }));
1756 job_producer
1757 .expect_produce_check_transaction_status_job()
1758 .returning(|_, _| Box::pin(async { Ok(()) }));
1759
1760 let relayer_repo = Arc::new(MockRelayerRepository::new());
1761 let counter = MockTransactionCounterServiceTrait::new();
1762
1763 let relayer = StellarRelayer::new(
1764 relayer_model.clone(),
1765 signer,
1766 provider,
1767 StellarRelayerDependencies::new(
1768 relayer_repo,
1769 ctx.network_repository.clone(),
1770 Arc::new(tx_repo),
1771 Arc::new(counter),
1772 Arc::new(job_producer),
1773 ),
1774 )
1775 .await
1776 .unwrap();
1777
1778 let result = relayer.process_transaction_request(tx_request).await;
1779 assert!(result.is_ok());
1780
1781 let returned_tx = result.unwrap();
1782 assert_eq!(returned_tx.relayer_id, relayer_model.id);
1783 assert_eq!(returned_tx.network_type, NetworkType::Stellar);
1784 assert_eq!(returned_tx.status, TransactionStatus::Pending);
1785 }
1786 }
1787}