1use crate::constants::get_stellar_sponsored_transaction_validity_duration;
2use crate::domain::map_provider_error;
3use crate::domain::relayer::evm::create_error_response;
4use crate::services::stellar_dex::StellarDexService;
5use crate::{
28 constants::{STELLAR_SMALLEST_UNIT_NAME, STELLAR_STATUS_CHECK_INITIAL_DELAY_SECONDS},
29 domain::{
30 create_success_response, transaction::stellar::fetch_next_sequence_from_chain,
31 BalanceResponse, SignDataRequest, SignDataResponse, SignTransactionExternalResponse,
32 SignTransactionExternalResponseStellar, SignTransactionRequest, SignTypedDataRequest,
33 },
34 jobs::{JobProducerTrait, RelayerHealthCheck, TransactionRequest, TransactionStatusCheck},
35 models::{
36 produce_relayer_disabled_payload, DeletePendingTransactionsResponse, DisabledReason,
37 HealthCheckFailure, JsonRpcRequest, JsonRpcResponse, NetworkRepoModel, NetworkRpcRequest,
38 NetworkRpcResult, NetworkTransactionRequest, NetworkType, RelayerNetworkPolicy,
39 RelayerRepoModel, RelayerStatus, RelayerStellarPolicy, RepositoryError, RpcErrorCodes,
40 StellarAllowedTokensPolicy, StellarFeePaymentStrategy, StellarNetwork, StellarRpcRequest,
41 TransactionRepoModel, TransactionStatus,
42 },
43 repositories::{NetworkRepository, RelayerRepository, Repository, TransactionRepository},
44 services::{
45 provider::{StellarProvider, StellarProviderTrait},
46 signer::{StellarSignTrait, StellarSigner},
47 stellar_dex::StellarDexServiceTrait,
48 TransactionCounterService, TransactionCounterServiceTrait,
49 },
50 utils::calculate_scheduled_timestamp,
51};
52use async_trait::async_trait;
53use eyre::Result;
54use futures::future::try_join_all;
55use std::sync::Arc;
56use tracing::{debug, info, warn};
57
58use crate::domain::relayer::stellar::xdr_utils::parse_transaction_xdr;
59use crate::domain::relayer::{Relayer, RelayerError, StellarRelayerDexTrait};
60use crate::domain::transaction::stellar::token::get_token_metadata;
61use crate::domain::transaction::stellar::StellarTransactionValidator;
62
63pub struct StellarRelayerDependencies<RR, NR, TR, J, TCS>
65where
66 RR: Repository<RelayerRepoModel, String> + RelayerRepository + Send + Sync + 'static,
67 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
68 TR: Repository<TransactionRepoModel, String> + TransactionRepository + Send + Sync + 'static,
69 J: JobProducerTrait + Send + Sync + 'static,
70 TCS: TransactionCounterServiceTrait + Send + Sync + 'static,
71{
72 pub relayer_repository: Arc<RR>,
73 pub network_repository: Arc<NR>,
74 pub transaction_repository: Arc<TR>,
75 pub transaction_counter_service: Arc<TCS>,
76 pub job_producer: Arc<J>,
77}
78
79impl<RR, NR, TR, J, TCS> StellarRelayerDependencies<RR, NR, TR, J, TCS>
80where
81 RR: Repository<RelayerRepoModel, String> + RelayerRepository + Send + Sync + 'static,
82 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
83 TR: Repository<TransactionRepoModel, String> + TransactionRepository + Send + Sync + 'static,
84 J: JobProducerTrait + Send + Sync,
85 TCS: TransactionCounterServiceTrait + Send + Sync + 'static,
86{
87 pub fn new(
101 relayer_repository: Arc<RR>,
102 network_repository: Arc<NR>,
103 transaction_repository: Arc<TR>,
104 transaction_counter_service: Arc<TCS>,
105 job_producer: Arc<J>,
106 ) -> Self {
107 Self {
108 relayer_repository,
109 network_repository,
110 transaction_repository,
111 transaction_counter_service,
112 job_producer,
113 }
114 }
115}
116
117#[allow(dead_code)]
118pub struct StellarRelayer<P, RR, NR, TR, J, TCS, S, D>
119where
120 P: StellarProviderTrait + Send + Sync + 'static,
121 RR: Repository<RelayerRepoModel, String> + RelayerRepository + Send + Sync + 'static,
122 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
123 TR: Repository<TransactionRepoModel, String> + TransactionRepository + Send + Sync + 'static,
124 J: JobProducerTrait + Send + Sync + 'static,
125 TCS: TransactionCounterServiceTrait + Send + Sync + 'static,
126 S: StellarSignTrait + Send + Sync + 'static,
127 D: StellarDexServiceTrait + Send + Sync + 'static,
128{
129 pub(crate) relayer: RelayerRepoModel,
130 signer: Arc<S>,
131 pub(crate) network: StellarNetwork,
132 pub(crate) provider: P,
133 pub(crate) relayer_repository: Arc<RR>,
134 network_repository: Arc<NR>,
135 transaction_repository: Arc<TR>,
136 transaction_counter_service: Arc<TCS>,
137 pub(crate) job_producer: Arc<J>,
138 pub(crate) dex_service: Arc<D>,
139}
140
141pub type DefaultStellarRelayer<J, TR, NR, RR, TCR> = StellarRelayer<
142 StellarProvider,
143 RR,
144 NR,
145 TR,
146 J,
147 TransactionCounterService<TCR>,
148 StellarSigner,
149 StellarDexService<StellarProvider, StellarSigner>,
150>;
151
152impl<P, RR, NR, TR, J, TCS, S, D> StellarRelayer<P, RR, NR, TR, J, TCS, S, D>
153where
154 P: StellarProviderTrait + Send + Sync,
155 RR: Repository<RelayerRepoModel, String> + RelayerRepository + Send + Sync + 'static,
156 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
157 TR: Repository<TransactionRepoModel, String> + TransactionRepository + Send + Sync + 'static,
158 J: JobProducerTrait + Send + Sync + 'static,
159 TCS: TransactionCounterServiceTrait + Send + Sync + 'static,
160 S: StellarSignTrait + Send + Sync + 'static,
161 D: StellarDexServiceTrait + Send + Sync + 'static,
162{
163 #[allow(clippy::too_many_arguments)]
182 pub async fn new(
183 relayer: RelayerRepoModel,
184 signer: Arc<S>,
185 provider: P,
186 dependencies: StellarRelayerDependencies<RR, NR, TR, J, TCS>,
187 dex_service: Arc<D>,
188 ) -> Result<Self, RelayerError> {
189 let network_repo = dependencies
190 .network_repository
191 .get_by_name(NetworkType::Stellar, &relayer.network)
192 .await
193 .ok()
194 .flatten()
195 .ok_or_else(|| {
196 RelayerError::NetworkConfiguration(format!("Network {} not found", relayer.network))
197 })?;
198
199 let network = StellarNetwork::try_from(network_repo.clone())?;
200
201 Ok(Self {
202 relayer,
203 signer,
204 network,
205 provider,
206 relayer_repository: dependencies.relayer_repository,
207 network_repository: dependencies.network_repository,
208 transaction_repository: dependencies.transaction_repository,
209 transaction_counter_service: dependencies.transaction_counter_service,
210 job_producer: dependencies.job_producer,
211 dex_service,
212 })
213 }
214
215 async fn sync_sequence(&self) -> Result<(), RelayerError> {
216 info!(
217 "Syncing sequence for relayer: {} ({})",
218 self.relayer.id, self.relayer.address
219 );
220
221 let next = fetch_next_sequence_from_chain(&self.provider, &self.relayer.address)
222 .await
223 .map_err(RelayerError::ProviderError)?;
224
225 info!(
226 "Setting next sequence {} for relayer {}",
227 next, self.relayer.id
228 );
229 self.transaction_counter_service
230 .set(next)
231 .await
232 .map_err(RelayerError::from)?;
233 Ok(())
234 }
235
236 async fn populate_allowed_tokens_metadata(&self) -> Result<RelayerStellarPolicy, RelayerError> {
246 let mut policy = self.relayer.policies.get_stellar_policy();
247 let allowed_tokens = match policy.allowed_tokens.as_ref() {
249 Some(tokens) if !tokens.is_empty() => tokens,
250 _ => {
251 info!("No allowed tokens specified; skipping token metadata population.");
252 return Ok(policy);
253 }
254 };
255
256 let token_metadata_futures = allowed_tokens.iter().map(|token| {
257 let asset_id = token.asset.clone();
258 let provider = &self.provider;
259 async move {
260 let metadata = get_token_metadata(provider, &asset_id)
261 .await
262 .map_err(RelayerError::from)?;
263
264 Ok::<StellarAllowedTokensPolicy, RelayerError>(StellarAllowedTokensPolicy {
265 asset: asset_id,
266 metadata: Some(metadata),
267 max_allowed_fee: token.max_allowed_fee,
268 swap_config: token.swap_config.clone(),
269 })
270 }
271 });
272
273 let updated_allowed_tokens = try_join_all(token_metadata_futures).await?;
274
275 policy.allowed_tokens = Some(updated_allowed_tokens.clone());
276
277 self.relayer_repository
278 .update_policy(
279 self.relayer.id.clone(),
280 RelayerNetworkPolicy::Stellar(policy.clone()),
281 )
282 .await?;
283
284 Ok(policy)
285 }
286
287 async fn migrate_fee_payment_strategy_if_needed(&self) -> Result<(), RelayerError> {
296 if !self.relayer_repository.is_persistent_storage() {
299 debug!(
300 relayer_id = %self.relayer.id,
301 "Skipping migration: using in-memory storage"
302 );
303 return Ok(());
304 }
305
306 let policy = self.relayer.policies.get_stellar_policy();
307
308 if policy.fee_payment_strategy.is_some() {
310 return Ok(());
311 }
312
313 info!(
315 relayer_id = %self.relayer.id,
316 "Migrating Stellar relayer: setting fee_payment_strategy to 'Relayer' (old default behavior)"
317 );
318
319 let mut updated_policy = policy;
321 updated_policy.fee_payment_strategy = Some(StellarFeePaymentStrategy::Relayer);
322
323 self.relayer_repository
325 .update_policy(
326 self.relayer.id.clone(),
327 RelayerNetworkPolicy::Stellar(updated_policy),
328 )
329 .await
330 .map_err(|e| {
331 RelayerError::PolicyConfigurationError(format!(
332 "Failed to migrate fee_payment_strategy policy: {e}"
333 ))
334 })?;
335
336 debug!(
337 relayer_id = %self.relayer.id,
338 "Successfully migrated fee_payment_strategy policy"
339 );
340
341 Ok(())
342 }
343
344 async fn check_balance_and_trigger_token_swap_if_needed(&self) -> Result<(), RelayerError> {
348 let policy = self.relayer.policies.get_stellar_policy();
349
350 let swap_config = match policy.get_swap_config() {
352 Some(config) => config,
353 None => {
354 debug!(
355 relayer_id = %self.relayer.id,
356 "No swap configuration specified; skipping balance check"
357 );
358 return Ok(());
359 }
360 };
361
362 let threshold = match swap_config.min_balance_threshold {
364 Some(threshold) => threshold,
365 None => {
366 debug!(
367 relayer_id = %self.relayer.id,
368 "No swap min balance threshold specified; skipping validation"
369 );
370 return Ok(());
371 }
372 };
373
374 let balance_response = self.get_balance().await?;
376 let current_balance = u64::try_from(balance_response.balance).map_err(|_| {
377 RelayerError::Internal("Account balance exceeds u64 maximum value".to_string())
378 })?;
379
380 if current_balance < threshold {
382 debug!(
383 relayer_id = %self.relayer.id,
384 balance = current_balance,
385 threshold = threshold,
386 "XLM balance is below threshold, triggering token swap"
387 );
388
389 let _swap_results = self
390 .handle_token_swap_request(self.relayer.id.clone())
391 .await?;
392 } else {
393 debug!(
394 relayer_id = %self.relayer.id,
395 balance = current_balance,
396 threshold = threshold,
397 "XLM balance is above threshold, no swap needed"
398 );
399 }
400
401 Ok(())
402 }
403}
404
405#[async_trait]
406impl<P, RR, NR, TR, J, TCS, S, D> Relayer for StellarRelayer<P, RR, NR, TR, J, TCS, S, D>
407where
408 P: StellarProviderTrait + Send + Sync + 'static,
409 D: StellarDexServiceTrait + Send + Sync + 'static,
410 RR: Repository<RelayerRepoModel, String> + RelayerRepository + Send + Sync + 'static,
411 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
412 TR: Repository<TransactionRepoModel, String> + TransactionRepository + Send + Sync + 'static,
413 J: JobProducerTrait + Send + Sync + 'static,
414 TCS: TransactionCounterServiceTrait + Send + Sync + 'static,
415 S: StellarSignTrait + Send + Sync + 'static,
416{
417 async fn process_transaction_request(
418 &self,
419 network_transaction: NetworkTransactionRequest,
420 ) -> Result<TransactionRepoModel, RelayerError> {
421 let network_model = self
422 .network_repository
423 .get_by_name(NetworkType::Stellar, &self.relayer.network)
424 .await?
425 .ok_or_else(|| {
426 RelayerError::NetworkConfiguration(format!(
427 "Network {} not found",
428 self.relayer.network
429 ))
430 })?;
431 let transaction =
432 TransactionRepoModel::try_from((&network_transaction, &self.relayer, &network_model))?;
433
434 self.transaction_repository
435 .create(transaction.clone())
436 .await
437 .map_err(|e| RepositoryError::TransactionFailure(e.to_string()))?;
438
439 self.job_producer
440 .produce_transaction_request_job(
441 TransactionRequest::new(transaction.id.clone(), transaction.relayer_id.clone()),
442 None,
443 )
444 .await?;
445
446 self.job_producer
447 .produce_check_transaction_status_job(
448 TransactionStatusCheck::new(
449 transaction.id.clone(),
450 transaction.relayer_id.clone(),
451 NetworkType::Stellar,
452 ),
453 Some(calculate_scheduled_timestamp(
454 STELLAR_STATUS_CHECK_INITIAL_DELAY_SECONDS,
455 )),
456 )
457 .await?;
458
459 Ok(transaction)
460 }
461
462 async fn get_balance(&self) -> Result<BalanceResponse, RelayerError> {
463 let account_entry = self
464 .provider
465 .get_account(&self.relayer.address)
466 .await
467 .map_err(|e| {
468 RelayerError::ProviderError(format!("Failed to fetch account for balance: {e}"))
469 })?;
470
471 Ok(BalanceResponse {
472 balance: account_entry.balance as u128,
473 unit: STELLAR_SMALLEST_UNIT_NAME.to_string(),
474 })
475 }
476
477 async fn get_status(&self) -> Result<RelayerStatus, RelayerError> {
478 let relayer_model = &self.relayer;
479
480 let account_entry = self
481 .provider
482 .get_account(&relayer_model.address)
483 .await
484 .map_err(|e| {
485 RelayerError::ProviderError(format!("Failed to get account details: {e}"))
486 })?;
487
488 let sequence_number_str = account_entry.seq_num.0.to_string();
489
490 let balance_response = self.get_balance().await?;
491
492 let pending_statuses = [TransactionStatus::Pending, TransactionStatus::Submitted];
493 let pending_transactions = self
494 .transaction_repository
495 .find_by_status(&relayer_model.id, &pending_statuses[..])
496 .await
497 .map_err(RelayerError::from)?;
498 let pending_transactions_count = pending_transactions.len() as u64;
499
500 let confirmed_statuses = [TransactionStatus::Confirmed];
501 let confirmed_transactions = self
502 .transaction_repository
503 .find_by_status(&relayer_model.id, &confirmed_statuses[..])
504 .await
505 .map_err(RelayerError::from)?;
506
507 let last_confirmed_transaction_timestamp = confirmed_transactions
508 .iter()
509 .filter_map(|tx| tx.confirmed_at.as_ref())
510 .max()
511 .cloned();
512
513 Ok(RelayerStatus::Stellar {
514 balance: balance_response.balance.to_string(),
515 pending_transactions_count,
516 last_confirmed_transaction_timestamp,
517 system_disabled: relayer_model.system_disabled,
518 paused: relayer_model.paused,
519 sequence_number: sequence_number_str,
520 })
521 }
522
523 async fn delete_pending_transactions(
524 &self,
525 ) -> Result<DeletePendingTransactionsResponse, RelayerError> {
526 println!("Stellar delete_pending_transactions...");
527 Ok(DeletePendingTransactionsResponse {
528 queued_for_cancellation_transaction_ids: vec![],
529 failed_to_queue_transaction_ids: vec![],
530 total_processed: 0,
531 })
532 }
533
534 async fn sign_data(&self, _request: SignDataRequest) -> Result<SignDataResponse, RelayerError> {
535 Err(RelayerError::NotSupported(
536 "Signing data not supported for Stellar".to_string(),
537 ))
538 }
539
540 async fn sign_typed_data(
541 &self,
542 _request: SignTypedDataRequest,
543 ) -> Result<SignDataResponse, RelayerError> {
544 Err(RelayerError::NotSupported(
545 "Signing typed data not supported for Stellar".to_string(),
546 ))
547 }
548
549 async fn rpc(
550 &self,
551 request: JsonRpcRequest<NetworkRpcRequest>,
552 ) -> Result<JsonRpcResponse<NetworkRpcResult>, RelayerError> {
553 let JsonRpcRequest { id, params, .. } = request;
554 let stellar_request = match params {
555 NetworkRpcRequest::Stellar(stellar_req) => stellar_req,
556 _ => {
557 return Ok(create_error_response(
558 id.clone(),
559 RpcErrorCodes::INVALID_PARAMS,
560 "Invalid params",
561 "Expected Stellar network request",
562 ))
563 }
564 };
565
566 let (method, params_json) = match stellar_request {
568 StellarRpcRequest::RawRpcRequest { method, params } => (method, params),
569 };
570
571 match self
572 .provider
573 .raw_request_dyn(&method, params_json, id.clone())
574 .await
575 {
576 Ok(result_value) => Ok(create_success_response(id.clone(), result_value)),
577 Err(provider_error) => {
578 let (error_code, error_message) = map_provider_error(&provider_error);
579 Ok(create_error_response(
580 id.clone(),
581 error_code,
582 error_message,
583 &provider_error.to_string(),
584 ))
585 }
586 }
587 }
588
589 async fn validate_min_balance(&self) -> Result<(), RelayerError> {
590 Ok(())
591 }
592
593 async fn initialize_relayer(&self) -> Result<(), RelayerError> {
594 debug!("initializing Stellar relayer {}", self.relayer.id);
595
596 self.migrate_fee_payment_strategy_if_needed().await?;
600
601 self.populate_allowed_tokens_metadata().await.map_err(|e| {
604 RelayerError::PolicyConfigurationError(format!(
605 "Error while processing allowed tokens policy: {e}"
606 ))
607 })?;
608
609 match self.check_health().await {
610 Ok(_) => {
611 if self.relayer.system_disabled {
613 self.relayer_repository
615 .enable_relayer(self.relayer.id.clone())
616 .await?;
617 }
618 }
619 Err(failures) => {
620 let reason = DisabledReason::from_health_failures(failures).unwrap_or_else(|| {
622 DisabledReason::SequenceSyncFailed("Unknown error".to_string())
623 });
624
625 warn!(reason = %reason, "disabling relayer");
626 let updated_relayer = self
627 .relayer_repository
628 .disable_relayer(self.relayer.id.clone(), reason.clone())
629 .await?;
630
631 if let Some(notification_id) = &self.relayer.notification_id {
633 self.job_producer
634 .produce_send_notification_job(
635 produce_relayer_disabled_payload(
636 notification_id,
637 &updated_relayer,
638 &reason.safe_description(),
639 ),
640 None,
641 )
642 .await?;
643 }
644
645 self.job_producer
647 .produce_relayer_health_check_job(
648 RelayerHealthCheck::new(self.relayer.id.clone()),
649 Some(calculate_scheduled_timestamp(10)),
650 )
651 .await?;
652 }
653 }
654 debug!(
655 "Stellar relayer initialized successfully: {}",
656 self.relayer.id
657 );
658 Ok(())
659 }
660
661 async fn check_health(&self) -> Result<(), Vec<HealthCheckFailure>> {
662 debug!(
663 "running health checks for Stellar relayer {}",
664 self.relayer.id
665 );
666
667 let mut failures = Vec::new();
668
669 match self.sync_sequence().await {
671 Ok(_) => {
672 debug!(
673 "sequence sync passed for Stellar relayer {}",
674 self.relayer.id
675 );
676 }
677 Err(e) => {
678 let reason = HealthCheckFailure::SequenceSyncFailed(e.to_string());
679 warn!("sequence sync failed: {:?}", reason);
680 failures.push(reason);
681 }
682 }
683
684 let policy = self.relayer.policies.get_stellar_policy();
688 if matches!(
689 policy.fee_payment_strategy,
690 Some(StellarFeePaymentStrategy::User)
691 ) {
692 debug!(
693 "checking balance and attempting token swap for user fee payment strategy relayer {}",
694 self.relayer.id
695 );
696 if let Err(e) = self.check_balance_and_trigger_token_swap_if_needed().await {
697 warn!(
698 relayer_id = %self.relayer.id,
699 error = %e,
700 "Balance check or token swap failed, but not treating as health check failure"
701 );
702 } else {
703 debug!(
704 "balance check and token swap completed for Stellar relayer {}",
705 self.relayer.id
706 );
707 }
708 }
709
710 if failures.is_empty() {
711 debug!(
712 "all health checks passed for Stellar relayer {}",
713 self.relayer.id
714 );
715 Ok(())
716 } else {
717 warn!(
718 "health checks failed for Stellar relayer {}: {:?}",
719 self.relayer.id, failures
720 );
721 Err(failures)
722 }
723 }
724
725 async fn sign_transaction(
726 &self,
727 request: &SignTransactionRequest,
728 ) -> Result<SignTransactionExternalResponse, RelayerError> {
729 let stellar_req = match request {
730 SignTransactionRequest::Stellar(req) => req,
731 _ => {
732 return Err(RelayerError::NotSupported(
733 "Invalid request type for Stellar relayer".to_string(),
734 ))
735 }
736 };
737
738 let policy = self.relayer.policies.get_stellar_policy();
739 let user_pays_fee = matches!(
740 policy.fee_payment_strategy,
741 Some(StellarFeePaymentStrategy::User)
742 );
743
744 if user_pays_fee {
746 let envelope = parse_transaction_xdr(&stellar_req.unsigned_xdr, false)
748 .map_err(|e| RelayerError::ValidationError(format!("Failed to parse XDR: {e}")))?;
749
750 StellarTransactionValidator::validate_user_fee_payment_transaction(
753 &envelope,
754 &self.relayer.address,
755 &policy,
756 &self.provider,
757 self.dex_service.as_ref(),
758 Some(get_stellar_sponsored_transaction_validity_duration()), )
760 .await
761 .map_err(|e| {
762 RelayerError::ValidationError(format!("Failed to validate transaction: {e}"))
763 })?;
764 }
765
766 let response = self
768 .signer
769 .sign_xdr_transaction(&stellar_req.unsigned_xdr, &self.network.passphrase)
770 .await
771 .map_err(RelayerError::SignerError)?;
772
773 let signature_bytes = &response.signature.signature.0;
775 let signature_string =
776 base64::Engine::encode(&base64::engine::general_purpose::STANDARD, signature_bytes);
777
778 Ok(SignTransactionExternalResponse::Stellar(
779 SignTransactionExternalResponseStellar {
780 signed_xdr: response.signed_xdr,
781 signature: signature_string,
782 },
783 ))
784 }
785}
786
787#[cfg(test)]
788mod tests {
789 use super::*;
790 use crate::{
791 config::{NetworkConfigCommon, StellarNetworkConfig},
792 constants::STELLAR_SMALLEST_UNIT_NAME,
793 domain::{SignTransactionRequestStellar, SignXdrTransactionResponseStellar},
794 jobs::MockJobProducerTrait,
795 models::{
796 NetworkConfigData, NetworkRepoModel, NetworkType, RelayerNetworkPolicy,
797 RelayerRepoModel, RelayerStellarPolicy, SignerError,
798 },
799 repositories::{
800 InMemoryNetworkRepository, MockRelayerRepository, MockTransactionRepository,
801 },
802 services::{
803 provider::{MockStellarProviderTrait, ProviderError},
804 signer::MockStellarSignTrait,
805 stellar_dex::MockStellarDexServiceTrait,
806 MockTransactionCounterServiceTrait,
807 },
808 };
809 use mockall::predicate::*;
810 use soroban_rs::xdr::{
811 AccountEntry, AccountEntryExt, AccountId, DecoratedSignature, PublicKey, SequenceNumber,
812 Signature, SignatureHint, String32, Thresholds, Uint256, VecM,
813 };
814 use std::future::ready;
815 use std::sync::Arc;
816
817 fn create_mock_dex_service() -> Arc<MockStellarDexServiceTrait> {
819 let mut mock_dex = MockStellarDexServiceTrait::new();
820 mock_dex.expect_supported_asset_types().returning(|| {
821 use crate::services::stellar_dex::AssetType;
822 std::collections::HashSet::from([AssetType::Native, AssetType::Classic])
823 });
824 Arc::new(mock_dex)
825 }
826
827 struct TestCtx {
829 relayer_model: RelayerRepoModel,
830 network_repository: Arc<InMemoryNetworkRepository>,
831 }
832
833 impl Default for TestCtx {
834 fn default() -> Self {
835 let network_repository = Arc::new(InMemoryNetworkRepository::new());
836
837 let relayer_model = RelayerRepoModel {
838 id: "test-relayer-id".to_string(),
839 name: "Test Relayer".to_string(),
840 network: "testnet".to_string(),
841 paused: false,
842 network_type: NetworkType::Stellar,
843 signer_id: "signer-id".to_string(),
844 policies: RelayerNetworkPolicy::Stellar(RelayerStellarPolicy::default()),
845 address: "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF".to_string(),
846 notification_id: Some("notification-id".to_string()),
847 system_disabled: false,
848 custom_rpc_urls: None,
849 ..Default::default()
850 };
851
852 TestCtx {
853 relayer_model,
854 network_repository,
855 }
856 }
857 }
858
859 impl TestCtx {
860 async fn setup_network(&self) {
861 let test_network = NetworkRepoModel {
862 id: "stellar:testnet".to_string(),
863 name: "testnet".to_string(),
864 network_type: NetworkType::Stellar,
865 config: NetworkConfigData::Stellar(StellarNetworkConfig {
866 common: NetworkConfigCommon {
867 network: "testnet".to_string(),
868 from: None,
869 rpc_urls: Some(vec!["https://horizon-testnet.stellar.org".to_string()]),
870 explorer_urls: None,
871 average_blocktime_ms: Some(5000),
872 is_testnet: Some(true),
873 tags: None,
874 },
875 passphrase: Some("Test SDF Network ; September 2015".to_string()),
876 horizon_url: Some("https://horizon-testnet.stellar.org".to_string()),
877 }),
878 };
879
880 self.network_repository.create(test_network).await.unwrap();
881 }
882 }
883
884 #[tokio::test]
885 async fn test_sync_sequence_success() {
886 let ctx = TestCtx::default();
887 ctx.setup_network().await;
888 let relayer_model = ctx.relayer_model.clone();
889 let mut provider = MockStellarProviderTrait::new();
890 provider
891 .expect_get_account()
892 .with(eq(relayer_model.address.clone()))
893 .returning(|_| {
894 Box::pin(async {
895 Ok(AccountEntry {
896 account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32]))),
897 balance: 0,
898 ext: AccountEntryExt::V0,
899 flags: 0,
900 home_domain: String32::default(),
901 inflation_dest: None,
902 seq_num: SequenceNumber(5),
903 num_sub_entries: 0,
904 signers: VecM::default(),
905 thresholds: Thresholds([0, 0, 0, 0]),
906 })
907 })
908 });
909 let mut counter = MockTransactionCounterServiceTrait::new();
910 counter
911 .expect_set()
912 .with(eq(6u64))
913 .returning(|_| Box::pin(async { Ok(()) }));
914 let relayer_repo = MockRelayerRepository::new();
915 let tx_repo = MockTransactionRepository::new();
916 let job_producer = MockJobProducerTrait::new();
917 let signer = Arc::new(MockStellarSignTrait::new());
918 let dex_service = create_mock_dex_service();
919
920 let relayer = StellarRelayer::new(
921 relayer_model.clone(),
922 signer,
923 provider,
924 StellarRelayerDependencies::new(
925 Arc::new(relayer_repo),
926 ctx.network_repository.clone(),
927 Arc::new(tx_repo),
928 Arc::new(counter),
929 Arc::new(job_producer),
930 ),
931 dex_service,
932 )
933 .await
934 .unwrap();
935
936 let result = relayer.sync_sequence().await;
937 assert!(result.is_ok());
938 }
939
940 #[tokio::test]
941 async fn test_sync_sequence_provider_error() {
942 let ctx = TestCtx::default();
943 ctx.setup_network().await;
944 let relayer_model = ctx.relayer_model.clone();
945 let mut provider = MockStellarProviderTrait::new();
946 provider
947 .expect_get_account()
948 .with(eq(relayer_model.address.clone()))
949 .returning(|_| Box::pin(async { Err(ProviderError::Other("fail".to_string())) }));
950 let counter = MockTransactionCounterServiceTrait::new();
951 let relayer_repo = MockRelayerRepository::new();
952 let tx_repo = MockTransactionRepository::new();
953 let job_producer = MockJobProducerTrait::new();
954 let signer = Arc::new(MockStellarSignTrait::new());
955 let dex_service = create_mock_dex_service();
956
957 let relayer = StellarRelayer::new(
958 relayer_model.clone(),
959 signer,
960 provider,
961 StellarRelayerDependencies::new(
962 Arc::new(relayer_repo),
963 ctx.network_repository.clone(),
964 Arc::new(tx_repo),
965 Arc::new(counter),
966 Arc::new(job_producer),
967 ),
968 dex_service,
969 )
970 .await
971 .unwrap();
972
973 let result = relayer.sync_sequence().await;
974 assert!(matches!(result, Err(RelayerError::ProviderError(_))));
975 }
976
977 #[tokio::test]
978 async fn test_get_status_success_stellar() {
979 let ctx = TestCtx::default();
980 ctx.setup_network().await;
981 let relayer_model = ctx.relayer_model.clone();
982 let mut provider_mock = MockStellarProviderTrait::new();
983 let mut tx_repo_mock = MockTransactionRepository::new();
984 let relayer_repo_mock = MockRelayerRepository::new();
985 let job_producer_mock = MockJobProducerTrait::new();
986 let counter_mock = MockTransactionCounterServiceTrait::new();
987
988 provider_mock.expect_get_account().times(2).returning(|_| {
989 Box::pin(ready(Ok(AccountEntry {
990 account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32]))),
991 balance: 10000000,
992 seq_num: SequenceNumber(12345),
993 ext: AccountEntryExt::V0,
994 flags: 0,
995 home_domain: String32::default(),
996 inflation_dest: None,
997 num_sub_entries: 0,
998 signers: VecM::default(),
999 thresholds: Thresholds([0, 0, 0, 0]),
1000 })))
1001 });
1002
1003 tx_repo_mock
1004 .expect_find_by_status()
1005 .withf(|relayer_id, statuses| {
1006 relayer_id == "test-relayer-id"
1007 && statuses == [TransactionStatus::Pending, TransactionStatus::Submitted]
1008 })
1009 .returning(|_, _| Ok(vec![]) as Result<Vec<TransactionRepoModel>, RepositoryError>)
1010 .once();
1011
1012 let confirmed_tx = TransactionRepoModel {
1013 id: "tx1_stellar".to_string(),
1014 relayer_id: relayer_model.id.clone(),
1015 status: TransactionStatus::Confirmed,
1016 confirmed_at: Some("2023-02-01T12:00:00Z".to_string()),
1017 ..TransactionRepoModel::default()
1018 };
1019 tx_repo_mock
1020 .expect_find_by_status()
1021 .withf(|relayer_id, statuses| {
1022 relayer_id == "test-relayer-id" && statuses == [TransactionStatus::Confirmed]
1023 })
1024 .returning(move |_, _| {
1025 Ok(vec![confirmed_tx.clone()]) as Result<Vec<TransactionRepoModel>, RepositoryError>
1026 })
1027 .once();
1028 let signer = Arc::new(MockStellarSignTrait::new());
1029 let dex_service = create_mock_dex_service();
1030
1031 let stellar_relayer = StellarRelayer::new(
1032 relayer_model.clone(),
1033 signer,
1034 provider_mock,
1035 StellarRelayerDependencies::new(
1036 Arc::new(relayer_repo_mock),
1037 ctx.network_repository.clone(),
1038 Arc::new(tx_repo_mock),
1039 Arc::new(counter_mock),
1040 Arc::new(job_producer_mock),
1041 ),
1042 dex_service,
1043 )
1044 .await
1045 .unwrap();
1046
1047 let status = stellar_relayer.get_status().await.unwrap();
1048
1049 match status {
1050 RelayerStatus::Stellar {
1051 balance,
1052 pending_transactions_count,
1053 last_confirmed_transaction_timestamp,
1054 system_disabled,
1055 paused,
1056 sequence_number,
1057 } => {
1058 assert_eq!(balance, "10000000");
1059 assert_eq!(pending_transactions_count, 0);
1060 assert_eq!(
1061 last_confirmed_transaction_timestamp,
1062 Some("2023-02-01T12:00:00Z".to_string())
1063 );
1064 assert_eq!(system_disabled, relayer_model.system_disabled);
1065 assert_eq!(paused, relayer_model.paused);
1066 assert_eq!(sequence_number, "12345");
1067 }
1068 _ => panic!("Expected Stellar RelayerStatus"),
1069 }
1070 }
1071
1072 #[tokio::test]
1073 async fn test_get_status_stellar_provider_error() {
1074 let ctx = TestCtx::default();
1075 ctx.setup_network().await;
1076 let relayer_model = ctx.relayer_model.clone();
1077 let mut provider_mock = MockStellarProviderTrait::new();
1078 let tx_repo_mock = MockTransactionRepository::new();
1079 let relayer_repo_mock = MockRelayerRepository::new();
1080 let job_producer_mock = MockJobProducerTrait::new();
1081 let counter_mock = MockTransactionCounterServiceTrait::new();
1082
1083 provider_mock
1084 .expect_get_account()
1085 .with(eq(relayer_model.address.clone()))
1086 .returning(|_| {
1087 Box::pin(async { Err(ProviderError::Other("Stellar provider down".to_string())) })
1088 });
1089 let signer = Arc::new(MockStellarSignTrait::new());
1090 let dex_service = create_mock_dex_service();
1091
1092 let stellar_relayer = StellarRelayer::new(
1093 relayer_model.clone(),
1094 signer,
1095 provider_mock,
1096 StellarRelayerDependencies::new(
1097 Arc::new(relayer_repo_mock),
1098 ctx.network_repository.clone(),
1099 Arc::new(tx_repo_mock),
1100 Arc::new(counter_mock),
1101 Arc::new(job_producer_mock),
1102 ),
1103 dex_service,
1104 )
1105 .await
1106 .unwrap();
1107
1108 let result = stellar_relayer.get_status().await;
1109 assert!(result.is_err());
1110 match result.err().unwrap() {
1111 RelayerError::ProviderError(msg) => {
1112 assert!(msg.contains("Failed to get account details"))
1113 }
1114 _ => panic!("Expected ProviderError for get_account failure"),
1115 }
1116 }
1117
1118 #[tokio::test]
1119 async fn test_get_balance_success() {
1120 let ctx = TestCtx::default();
1121 ctx.setup_network().await;
1122 let relayer_model = ctx.relayer_model.clone();
1123 let mut provider = MockStellarProviderTrait::new();
1124 let expected_balance = 100_000_000i64; provider
1127 .expect_get_account()
1128 .with(eq(relayer_model.address.clone()))
1129 .returning(move |_| {
1130 Box::pin(async move {
1131 Ok(AccountEntry {
1132 account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32]))),
1133 balance: expected_balance,
1134 ext: AccountEntryExt::V0,
1135 flags: 0,
1136 home_domain: String32::default(),
1137 inflation_dest: None,
1138 seq_num: SequenceNumber(5),
1139 num_sub_entries: 0,
1140 signers: VecM::default(),
1141 thresholds: Thresholds([0, 0, 0, 0]),
1142 })
1143 })
1144 });
1145
1146 let relayer_repo = Arc::new(MockRelayerRepository::new());
1147 let tx_repo = Arc::new(MockTransactionRepository::new());
1148 let job_producer = Arc::new(MockJobProducerTrait::new());
1149 let counter = Arc::new(MockTransactionCounterServiceTrait::new());
1150 let signer = Arc::new(MockStellarSignTrait::new());
1151 let dex_service = create_mock_dex_service();
1152
1153 let relayer = StellarRelayer::new(
1154 relayer_model,
1155 signer,
1156 provider,
1157 StellarRelayerDependencies::new(
1158 relayer_repo,
1159 ctx.network_repository.clone(),
1160 tx_repo,
1161 counter,
1162 job_producer,
1163 ),
1164 dex_service,
1165 )
1166 .await
1167 .unwrap();
1168
1169 let result = relayer.get_balance().await;
1170 assert!(result.is_ok());
1171 let balance_response = result.unwrap();
1172 assert_eq!(balance_response.balance, expected_balance as u128);
1173 assert_eq!(balance_response.unit, STELLAR_SMALLEST_UNIT_NAME);
1174 }
1175
1176 #[tokio::test]
1177 async fn test_get_balance_provider_error() {
1178 let ctx = TestCtx::default();
1179 ctx.setup_network().await;
1180 let relayer_model = ctx.relayer_model.clone();
1181 let mut provider = MockStellarProviderTrait::new();
1182
1183 provider
1184 .expect_get_account()
1185 .with(eq(relayer_model.address.clone()))
1186 .returning(|_| {
1187 Box::pin(async { Err(ProviderError::Other("provider failed".to_string())) })
1188 });
1189
1190 let relayer_repo = Arc::new(MockRelayerRepository::new());
1191 let tx_repo = Arc::new(MockTransactionRepository::new());
1192 let job_producer = Arc::new(MockJobProducerTrait::new());
1193 let counter = Arc::new(MockTransactionCounterServiceTrait::new());
1194 let signer = Arc::new(MockStellarSignTrait::new());
1195 let dex_service = create_mock_dex_service();
1196
1197 let relayer = StellarRelayer::new(
1198 relayer_model,
1199 signer,
1200 provider,
1201 StellarRelayerDependencies::new(
1202 relayer_repo,
1203 ctx.network_repository.clone(),
1204 tx_repo,
1205 counter,
1206 job_producer,
1207 ),
1208 dex_service,
1209 )
1210 .await
1211 .unwrap();
1212
1213 let result = relayer.get_balance().await;
1214 assert!(result.is_err());
1215 match result.err().unwrap() {
1216 RelayerError::ProviderError(msg) => {
1217 assert!(msg.contains("Failed to fetch account for balance"));
1218 }
1219 _ => panic!("Unexpected error type"),
1220 }
1221 }
1222
1223 #[tokio::test]
1224 async fn test_sign_transaction_success() {
1225 let ctx = TestCtx::default();
1226 ctx.setup_network().await;
1227 let relayer_model = ctx.relayer_model.clone();
1228 let provider = MockStellarProviderTrait::new();
1229 let mut signer = MockStellarSignTrait::new();
1230
1231 let unsigned_xdr = "AAAAAgAAAAD///8AAAAAAAAAAQAAAAAAAAACAAAAAQAAAAAAAAAB";
1232 let expected_signed_xdr =
1233 "AAAAAgAAAAD///8AAAAAAAABAAAAAAAAAAIAAAABAAAAAAAAAAEAAAABAAAAA...";
1234 let expected_signature = DecoratedSignature {
1235 hint: SignatureHint([1, 2, 3, 4]),
1236 signature: Signature([5u8; 64].try_into().unwrap()),
1237 };
1238 let expected_signature_for_closure = expected_signature.clone();
1239
1240 signer
1241 .expect_sign_xdr_transaction()
1242 .with(eq(unsigned_xdr), eq("Test SDF Network ; September 2015"))
1243 .returning(move |_, _| {
1244 Ok(SignXdrTransactionResponseStellar {
1245 signed_xdr: expected_signed_xdr.to_string(),
1246 signature: expected_signature_for_closure.clone(),
1247 })
1248 });
1249
1250 let relayer_repo = Arc::new(MockRelayerRepository::new());
1251 let tx_repo = Arc::new(MockTransactionRepository::new());
1252 let job_producer = Arc::new(MockJobProducerTrait::new());
1253 let counter = Arc::new(MockTransactionCounterServiceTrait::new());
1254 let dex_service = create_mock_dex_service();
1255
1256 let relayer = StellarRelayer::new(
1257 relayer_model,
1258 Arc::new(signer),
1259 provider,
1260 StellarRelayerDependencies::new(
1261 relayer_repo,
1262 ctx.network_repository.clone(),
1263 tx_repo,
1264 counter,
1265 job_producer,
1266 ),
1267 dex_service,
1268 )
1269 .await
1270 .unwrap();
1271
1272 let request = SignTransactionRequest::Stellar(SignTransactionRequestStellar {
1273 unsigned_xdr: unsigned_xdr.to_string(),
1274 });
1275 let result = relayer.sign_transaction(&request).await;
1276 assert!(result.is_ok());
1277
1278 match result.unwrap() {
1279 SignTransactionExternalResponse::Stellar(response) => {
1280 assert_eq!(response.signed_xdr, expected_signed_xdr);
1281 let expected_signature_base64 = base64::Engine::encode(
1283 &base64::engine::general_purpose::STANDARD,
1284 &expected_signature.signature.0,
1285 );
1286 assert_eq!(response.signature, expected_signature_base64);
1287 }
1288 _ => panic!("Expected Stellar response"),
1289 }
1290 }
1291
1292 #[tokio::test]
1293 async fn test_sign_transaction_signer_error() {
1294 let ctx = TestCtx::default();
1295 ctx.setup_network().await;
1296 let relayer_model = ctx.relayer_model.clone();
1297 let provider = MockStellarProviderTrait::new();
1298 let mut signer = MockStellarSignTrait::new();
1299
1300 let unsigned_xdr = "INVALID_XDR";
1301
1302 signer
1303 .expect_sign_xdr_transaction()
1304 .with(eq(unsigned_xdr), eq("Test SDF Network ; September 2015"))
1305 .returning(|_, _| Err(SignerError::SigningError("Invalid XDR format".to_string())));
1306
1307 let relayer_repo = Arc::new(MockRelayerRepository::new());
1308 let tx_repo = Arc::new(MockTransactionRepository::new());
1309 let job_producer = Arc::new(MockJobProducerTrait::new());
1310 let counter = Arc::new(MockTransactionCounterServiceTrait::new());
1311 let dex_service = create_mock_dex_service();
1312
1313 let relayer = StellarRelayer::new(
1314 relayer_model,
1315 Arc::new(signer),
1316 provider,
1317 StellarRelayerDependencies::new(
1318 relayer_repo,
1319 ctx.network_repository.clone(),
1320 tx_repo,
1321 counter,
1322 job_producer,
1323 ),
1324 dex_service,
1325 )
1326 .await
1327 .unwrap();
1328
1329 let request = SignTransactionRequest::Stellar(SignTransactionRequestStellar {
1330 unsigned_xdr: unsigned_xdr.to_string(),
1331 });
1332 let result = relayer.sign_transaction(&request).await;
1333 assert!(result.is_err());
1334
1335 match result.err().unwrap() {
1336 RelayerError::SignerError(err) => match err {
1337 SignerError::SigningError(msg) => {
1338 assert_eq!(msg, "Invalid XDR format");
1339 }
1340 _ => panic!("Expected SigningError"),
1341 },
1342 _ => panic!("Expected RelayerError::SignerError"),
1343 }
1344 }
1345
1346 #[tokio::test]
1347 async fn test_sign_transaction_with_different_network_passphrase() {
1348 let ctx = TestCtx::default();
1349 let custom_network = NetworkRepoModel {
1351 id: "stellar:mainnet".to_string(),
1352 name: "mainnet".to_string(),
1353 network_type: NetworkType::Stellar,
1354 config: NetworkConfigData::Stellar(StellarNetworkConfig {
1355 common: NetworkConfigCommon {
1356 network: "mainnet".to_string(),
1357 from: None,
1358 rpc_urls: Some(vec!["https://horizon.stellar.org".to_string()]),
1359 explorer_urls: None,
1360 average_blocktime_ms: Some(5000),
1361 is_testnet: Some(false),
1362 tags: None,
1363 },
1364 passphrase: Some("Public Global Stellar Network ; September 2015".to_string()),
1365 horizon_url: Some("https://horizon.stellar.org".to_string()),
1366 }),
1367 };
1368 ctx.network_repository.create(custom_network).await.unwrap();
1369
1370 let mut relayer_model = ctx.relayer_model.clone();
1371 relayer_model.network = "mainnet".to_string();
1372
1373 let provider = MockStellarProviderTrait::new();
1374 let mut signer = MockStellarSignTrait::new();
1375
1376 let unsigned_xdr = "AAAAAgAAAAD///8AAAAAAAAAAQAAAAAAAAACAAAAAQAAAAAAAAAB";
1377 let expected_signature = DecoratedSignature {
1378 hint: SignatureHint([10, 20, 30, 40]),
1379 signature: Signature([15u8; 64].try_into().unwrap()),
1380 };
1381 let expected_signature_for_closure = expected_signature.clone();
1382
1383 signer
1384 .expect_sign_xdr_transaction()
1385 .with(
1386 eq(unsigned_xdr),
1387 eq("Public Global Stellar Network ; September 2015"),
1388 )
1389 .returning(move |_, _| {
1390 Ok(SignXdrTransactionResponseStellar {
1391 signed_xdr: "mainnet_signed_xdr".to_string(),
1392 signature: expected_signature_for_closure.clone(),
1393 })
1394 });
1395
1396 let relayer_repo = Arc::new(MockRelayerRepository::new());
1397 let tx_repo = Arc::new(MockTransactionRepository::new());
1398 let job_producer = Arc::new(MockJobProducerTrait::new());
1399 let counter = Arc::new(MockTransactionCounterServiceTrait::new());
1400 let dex_service = create_mock_dex_service();
1401
1402 let relayer = StellarRelayer::new(
1403 relayer_model,
1404 Arc::new(signer),
1405 provider,
1406 StellarRelayerDependencies::new(
1407 relayer_repo,
1408 ctx.network_repository.clone(),
1409 tx_repo,
1410 counter,
1411 job_producer,
1412 ),
1413 dex_service,
1414 )
1415 .await
1416 .unwrap();
1417
1418 let request = SignTransactionRequest::Stellar(SignTransactionRequestStellar {
1419 unsigned_xdr: unsigned_xdr.to_string(),
1420 });
1421 let result = relayer.sign_transaction(&request).await;
1422 assert!(result.is_ok());
1423
1424 match result.unwrap() {
1425 SignTransactionExternalResponse::Stellar(response) => {
1426 assert_eq!(response.signed_xdr, "mainnet_signed_xdr");
1427 let expected_signature_string = base64::Engine::encode(
1429 &base64::engine::general_purpose::STANDARD,
1430 &expected_signature.signature.0,
1431 );
1432 assert_eq!(response.signature, expected_signature_string);
1433 }
1434 _ => panic!("Expected Stellar response"),
1435 }
1436 }
1437
1438 #[tokio::test]
1439 async fn test_initialize_relayer_disables_when_validation_fails() {
1440 let ctx = TestCtx::default();
1441 ctx.setup_network().await;
1442 let mut relayer_model = ctx.relayer_model.clone();
1443 relayer_model.system_disabled = false; relayer_model.notification_id = Some("test-notification-id".to_string());
1445
1446 let mut provider = MockStellarProviderTrait::new();
1447 let mut relayer_repo = MockRelayerRepository::new();
1448 let mut job_producer = MockJobProducerTrait::new();
1449
1450 relayer_repo
1451 .expect_is_persistent_storage()
1452 .returning(|| false);
1453
1454 provider
1456 .expect_get_account()
1457 .returning(|_| Box::pin(ready(Err(ProviderError::Other("RPC error".to_string())))));
1458
1459 let mut disabled_relayer = relayer_model.clone();
1461 disabled_relayer.system_disabled = true;
1462 relayer_repo
1463 .expect_disable_relayer()
1464 .withf(|id, reason| {
1465 id == "test-relayer-id"
1466 && matches!(reason, crate::models::DisabledReason::SequenceSyncFailed(_))
1467 })
1468 .returning(move |_, _| Ok(disabled_relayer.clone()));
1469
1470 job_producer
1472 .expect_produce_send_notification_job()
1473 .returning(|_, _| Box::pin(async { Ok(()) }));
1474
1475 job_producer
1477 .expect_produce_relayer_health_check_job()
1478 .returning(|_, _| Box::pin(async { Ok(()) }));
1479
1480 let tx_repo = MockTransactionRepository::new();
1481 let counter = MockTransactionCounterServiceTrait::new();
1482 let signer = Arc::new(MockStellarSignTrait::new());
1483 let dex_service = create_mock_dex_service();
1484
1485 let relayer = StellarRelayer::new(
1486 relayer_model.clone(),
1487 signer,
1488 provider,
1489 StellarRelayerDependencies::new(
1490 Arc::new(relayer_repo),
1491 ctx.network_repository.clone(),
1492 Arc::new(tx_repo),
1493 Arc::new(counter),
1494 Arc::new(job_producer),
1495 ),
1496 dex_service,
1497 )
1498 .await
1499 .unwrap();
1500
1501 let result = relayer.initialize_relayer().await;
1502 assert!(result.is_ok());
1503 }
1504
1505 #[tokio::test]
1506 async fn test_initialize_relayer_enables_when_validation_passes_and_was_disabled() {
1507 let ctx = TestCtx::default();
1508 ctx.setup_network().await;
1509 let mut relayer_model = ctx.relayer_model.clone();
1510 relayer_model.system_disabled = true; let mut provider = MockStellarProviderTrait::new();
1513 let mut relayer_repo = MockRelayerRepository::new();
1514
1515 relayer_repo
1516 .expect_is_persistent_storage()
1517 .returning(|| false);
1518
1519 provider.expect_get_account().returning(|_| {
1521 Box::pin(ready(Ok(AccountEntry {
1522 account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32]))),
1523 balance: 1000000000, seq_num: SequenceNumber(1),
1525 num_sub_entries: 0,
1526 inflation_dest: None,
1527 flags: 0,
1528 home_domain: String32::default(),
1529 thresholds: Thresholds([0; 4]),
1530 signers: VecM::default(),
1531 ext: AccountEntryExt::V0,
1532 })))
1533 });
1534
1535 let mut enabled_relayer = relayer_model.clone();
1537 enabled_relayer.system_disabled = false;
1538 relayer_repo
1539 .expect_enable_relayer()
1540 .with(eq("test-relayer-id".to_string()))
1541 .returning(move |_| Ok(enabled_relayer.clone()));
1542
1543 let tx_repo = MockTransactionRepository::new();
1544 let mut counter = MockTransactionCounterServiceTrait::new();
1545 counter
1546 .expect_set()
1547 .returning(|_| Box::pin(async { Ok(()) }));
1548 let signer = Arc::new(MockStellarSignTrait::new());
1549 let dex_service = create_mock_dex_service();
1550 let job_producer = MockJobProducerTrait::new();
1551
1552 let relayer = StellarRelayer::new(
1553 relayer_model.clone(),
1554 signer,
1555 provider,
1556 StellarRelayerDependencies::new(
1557 Arc::new(relayer_repo),
1558 ctx.network_repository.clone(),
1559 Arc::new(tx_repo),
1560 Arc::new(counter),
1561 Arc::new(job_producer),
1562 ),
1563 dex_service,
1564 )
1565 .await
1566 .unwrap();
1567
1568 let result = relayer.initialize_relayer().await;
1569 assert!(result.is_ok());
1570 }
1571
1572 #[tokio::test]
1573 async fn test_initialize_relayer_no_action_when_enabled_and_validation_passes() {
1574 let ctx = TestCtx::default();
1575 ctx.setup_network().await;
1576 let mut relayer_model = ctx.relayer_model.clone();
1577 relayer_model.system_disabled = false; let mut provider = MockStellarProviderTrait::new();
1580
1581 provider.expect_get_account().returning(|_| {
1583 Box::pin(ready(Ok(AccountEntry {
1584 account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32]))),
1585 balance: 1000000000, seq_num: SequenceNumber(1),
1587 num_sub_entries: 0,
1588 inflation_dest: None,
1589 flags: 0,
1590 home_domain: String32::default(),
1591 thresholds: Thresholds([0; 4]),
1592 signers: VecM::default(),
1593 ext: AccountEntryExt::V0,
1594 })))
1595 });
1596
1597 let tx_repo = MockTransactionRepository::new();
1600 let mut counter = MockTransactionCounterServiceTrait::new();
1601 counter
1602 .expect_set()
1603 .returning(|_| Box::pin(async { Ok(()) }));
1604 let signer = Arc::new(MockStellarSignTrait::new());
1605 let dex_service = create_mock_dex_service();
1606 let job_producer = MockJobProducerTrait::new();
1607 let mut relayer_repo = MockRelayerRepository::new();
1608
1609 relayer_repo
1610 .expect_is_persistent_storage()
1611 .returning(|| false);
1612
1613 let relayer = StellarRelayer::new(
1614 relayer_model.clone(),
1615 signer,
1616 provider,
1617 StellarRelayerDependencies::new(
1618 Arc::new(relayer_repo),
1619 ctx.network_repository.clone(),
1620 Arc::new(tx_repo),
1621 Arc::new(counter),
1622 Arc::new(job_producer),
1623 ),
1624 dex_service,
1625 )
1626 .await
1627 .unwrap();
1628
1629 let result = relayer.initialize_relayer().await;
1630 assert!(result.is_ok());
1631 }
1632
1633 #[tokio::test]
1634 async fn test_initialize_relayer_sends_notification_when_disabled() {
1635 let ctx = TestCtx::default();
1636 ctx.setup_network().await;
1637 let mut relayer_model = ctx.relayer_model.clone();
1638 relayer_model.system_disabled = false; relayer_model.notification_id = Some("test-notification-id".to_string());
1640
1641 let mut provider = MockStellarProviderTrait::new();
1642 let mut relayer_repo = MockRelayerRepository::new();
1643 let mut job_producer = MockJobProducerTrait::new();
1644
1645 relayer_repo
1646 .expect_is_persistent_storage()
1647 .returning(|| false);
1648
1649 provider.expect_get_account().returning(|_| {
1651 Box::pin(ready(Err(ProviderError::Other(
1652 "Sequence sync failed".to_string(),
1653 ))))
1654 });
1655
1656 let mut disabled_relayer = relayer_model.clone();
1658 disabled_relayer.system_disabled = true;
1659 relayer_repo
1660 .expect_disable_relayer()
1661 .withf(|id, reason| {
1662 id == "test-relayer-id"
1663 && matches!(reason, crate::models::DisabledReason::SequenceSyncFailed(_))
1664 })
1665 .returning(move |_, _| Ok(disabled_relayer.clone()));
1666
1667 job_producer
1669 .expect_produce_send_notification_job()
1670 .returning(|_, _| Box::pin(async { Ok(()) }));
1671
1672 job_producer
1674 .expect_produce_relayer_health_check_job()
1675 .returning(|_, _| Box::pin(async { Ok(()) }));
1676
1677 let tx_repo = MockTransactionRepository::new();
1678 let counter = MockTransactionCounterServiceTrait::new();
1679 let signer = Arc::new(MockStellarSignTrait::new());
1680 let dex_service = create_mock_dex_service();
1681
1682 let relayer = StellarRelayer::new(
1683 relayer_model.clone(),
1684 signer,
1685 provider,
1686 StellarRelayerDependencies::new(
1687 Arc::new(relayer_repo),
1688 ctx.network_repository.clone(),
1689 Arc::new(tx_repo),
1690 Arc::new(counter),
1691 Arc::new(job_producer),
1692 ),
1693 dex_service,
1694 )
1695 .await
1696 .unwrap();
1697
1698 let result = relayer.initialize_relayer().await;
1699 assert!(result.is_ok());
1700 }
1701
1702 #[tokio::test]
1703 async fn test_initialize_relayer_no_notification_when_no_notification_id() {
1704 let ctx = TestCtx::default();
1705 ctx.setup_network().await;
1706 let mut relayer_model = ctx.relayer_model.clone();
1707 relayer_model.system_disabled = false; relayer_model.notification_id = None; let mut provider = MockStellarProviderTrait::new();
1711 let mut relayer_repo = MockRelayerRepository::new();
1712 relayer_repo
1713 .expect_is_persistent_storage()
1714 .returning(|| false);
1715
1716 provider.expect_get_account().returning(|_| {
1718 Box::pin(ready(Err(ProviderError::Other(
1719 "Sequence sync failed".to_string(),
1720 ))))
1721 });
1722
1723 let mut disabled_relayer = relayer_model.clone();
1725 disabled_relayer.system_disabled = true;
1726 relayer_repo
1727 .expect_disable_relayer()
1728 .withf(|id, reason| {
1729 id == "test-relayer-id"
1730 && matches!(reason, crate::models::DisabledReason::SequenceSyncFailed(_))
1731 })
1732 .returning(move |_, _| Ok(disabled_relayer.clone()));
1733
1734 let mut job_producer = MockJobProducerTrait::new();
1737 job_producer
1738 .expect_produce_relayer_health_check_job()
1739 .returning(|_, _| Box::pin(async { Ok(()) }));
1740
1741 let tx_repo = MockTransactionRepository::new();
1742 let counter = MockTransactionCounterServiceTrait::new();
1743 let signer = Arc::new(MockStellarSignTrait::new());
1744 let dex_service = create_mock_dex_service();
1745
1746 let relayer = StellarRelayer::new(
1747 relayer_model.clone(),
1748 signer,
1749 provider,
1750 StellarRelayerDependencies::new(
1751 Arc::new(relayer_repo),
1752 ctx.network_repository.clone(),
1753 Arc::new(tx_repo),
1754 Arc::new(counter),
1755 Arc::new(job_producer),
1756 ),
1757 dex_service,
1758 )
1759 .await
1760 .unwrap();
1761
1762 let result = relayer.initialize_relayer().await;
1763 assert!(result.is_ok());
1764 }
1765
1766 mod process_transaction_request_tests {
1767 use super::*;
1768 use crate::constants::STELLAR_STATUS_CHECK_INITIAL_DELAY_SECONDS;
1769 use crate::models::{
1770 NetworkTransactionRequest, NetworkType, StellarTransactionRequest, TransactionStatus,
1771 };
1772 use chrono::Utc;
1773
1774 fn create_test_transaction_request() -> NetworkTransactionRequest {
1776 NetworkTransactionRequest::Stellar(StellarTransactionRequest {
1777 source_account: None,
1778 network: "testnet".to_string(),
1779 operations: None,
1780 memo: None,
1781 valid_until: None,
1782 transaction_xdr: Some("AAAAAgAAAACige4lTdwSB/sto4SniEdJ2kOa2X65s5bqkd40J4DjSwAAAAEAAHAkAAAADwAAAAAAAAAAAAAAAQAAAAAAAAABAAAAAKKB7iVN3BIH+y2jhKeIR0naQ5rZfrmzluqR3jQngONLAAAAAAAAAAAAD0JAAAAAAAAAAAA=".to_string()),
1783 fee_bump: None,
1784 max_fee: None,
1785 })
1786 }
1787
1788 #[tokio::test]
1789 async fn test_process_transaction_request_calls_job_producer_methods() {
1790 let ctx = TestCtx::default();
1791 ctx.setup_network().await;
1792 let relayer_model = ctx.relayer_model.clone();
1793
1794 let provider = MockStellarProviderTrait::new();
1795 let signer = Arc::new(MockStellarSignTrait::new());
1796 let dex_service = create_mock_dex_service();
1797
1798 let tx_request = create_test_transaction_request();
1800
1801 let mut tx_repo = MockTransactionRepository::new();
1803 tx_repo.expect_create().returning(|t| Ok(t.clone()));
1804
1805 let mut job_producer = MockJobProducerTrait::new();
1807
1808 job_producer
1810 .expect_produce_transaction_request_job()
1811 .withf(|req, delay| {
1812 !req.transaction_id.is_empty() && !req.relayer_id.is_empty() && delay.is_none()
1813 })
1814 .times(1)
1815 .returning(|_, _| Box::pin(async { Ok(()) }));
1816
1817 job_producer
1819 .expect_produce_check_transaction_status_job()
1820 .withf(|check, delay| {
1821 !check.transaction_id.is_empty()
1822 && !check.relayer_id.is_empty()
1823 && check.network_type == Some(NetworkType::Stellar)
1824 && delay.is_some()
1825 })
1826 .times(1)
1827 .returning(|_, _| Box::pin(async { Ok(()) }));
1828
1829 let relayer_repo = Arc::new(MockRelayerRepository::new());
1830 let counter = MockTransactionCounterServiceTrait::new();
1831
1832 let relayer = StellarRelayer::new(
1833 relayer_model,
1834 signer,
1835 provider,
1836 StellarRelayerDependencies::new(
1837 relayer_repo,
1838 ctx.network_repository.clone(),
1839 Arc::new(tx_repo),
1840 Arc::new(counter),
1841 Arc::new(job_producer),
1842 ),
1843 dex_service,
1844 )
1845 .await
1846 .unwrap();
1847
1848 let result = relayer.process_transaction_request(tx_request).await;
1849 if let Err(e) = &result {
1850 panic!("process_transaction_request failed: {}", e);
1851 }
1852 assert!(result.is_ok());
1853 }
1854
1855 #[tokio::test]
1856 async fn test_process_transaction_request_with_scheduled_delay() {
1857 let ctx = TestCtx::default();
1858 ctx.setup_network().await;
1859 let relayer_model = ctx.relayer_model.clone();
1860
1861 let provider = MockStellarProviderTrait::new();
1862 let signer = Arc::new(MockStellarSignTrait::new());
1863 let dex_service = create_mock_dex_service();
1864
1865 let tx_request = create_test_transaction_request();
1866
1867 let mut tx_repo = MockTransactionRepository::new();
1868 tx_repo.expect_create().returning(|t| Ok(t.clone()));
1869
1870 let mut job_producer = MockJobProducerTrait::new();
1871
1872 job_producer
1873 .expect_produce_transaction_request_job()
1874 .returning(|_, _| Box::pin(async { Ok(()) }));
1875
1876 job_producer
1878 .expect_produce_check_transaction_status_job()
1879 .withf(|_, delay| {
1880 if let Some(scheduled_at) = delay {
1882 let now = Utc::now().timestamp();
1884 let diff = scheduled_at - now;
1885 diff >= (STELLAR_STATUS_CHECK_INITIAL_DELAY_SECONDS - 2)
1887 && diff <= (STELLAR_STATUS_CHECK_INITIAL_DELAY_SECONDS + 2)
1888 } else {
1889 false
1890 }
1891 })
1892 .times(1)
1893 .returning(|_, _| Box::pin(async { Ok(()) }));
1894
1895 let relayer_repo = Arc::new(MockRelayerRepository::new());
1896 let counter = MockTransactionCounterServiceTrait::new();
1897
1898 let relayer = StellarRelayer::new(
1899 relayer_model,
1900 signer,
1901 provider,
1902 StellarRelayerDependencies::new(
1903 relayer_repo,
1904 ctx.network_repository.clone(),
1905 Arc::new(tx_repo),
1906 Arc::new(counter),
1907 Arc::new(job_producer),
1908 ),
1909 dex_service,
1910 )
1911 .await
1912 .unwrap();
1913
1914 let result = relayer.process_transaction_request(tx_request).await;
1915 assert!(result.is_ok());
1916 }
1917
1918 #[tokio::test]
1919 async fn test_process_transaction_request_repository_failure() {
1920 let ctx = TestCtx::default();
1921 ctx.setup_network().await;
1922 let relayer_model = ctx.relayer_model.clone();
1923
1924 let provider = MockStellarProviderTrait::new();
1925 let signer = Arc::new(MockStellarSignTrait::new());
1926 let dex_service = create_mock_dex_service();
1927
1928 let tx_request = create_test_transaction_request();
1929
1930 let mut tx_repo = MockTransactionRepository::new();
1932 tx_repo.expect_create().returning(|_| {
1933 Err(RepositoryError::TransactionFailure(
1934 "Database connection failed".to_string(),
1935 ))
1936 });
1937
1938 let job_producer = MockJobProducerTrait::new();
1940
1941 let relayer_repo = Arc::new(MockRelayerRepository::new());
1942 let counter = MockTransactionCounterServiceTrait::new();
1943
1944 let relayer = StellarRelayer::new(
1945 relayer_model,
1946 signer,
1947 provider,
1948 StellarRelayerDependencies::new(
1949 relayer_repo,
1950 ctx.network_repository.clone(),
1951 Arc::new(tx_repo),
1952 Arc::new(counter),
1953 Arc::new(job_producer),
1954 ),
1955 dex_service,
1956 )
1957 .await
1958 .unwrap();
1959
1960 let result = relayer.process_transaction_request(tx_request).await;
1961 assert!(result.is_err());
1962 let err_msg = result.err().unwrap().to_string();
1964 assert!(
1965 err_msg.contains("Database connection failed"),
1966 "Error was: {}",
1967 err_msg
1968 );
1969 }
1970
1971 #[tokio::test]
1972 async fn test_process_transaction_request_job_producer_request_failure() {
1973 let ctx = TestCtx::default();
1974 ctx.setup_network().await;
1975 let relayer_model = ctx.relayer_model.clone();
1976
1977 let provider = MockStellarProviderTrait::new();
1978 let signer = Arc::new(MockStellarSignTrait::new());
1979 let dex_service = create_mock_dex_service();
1980
1981 let tx_request = create_test_transaction_request();
1982
1983 let mut tx_repo = MockTransactionRepository::new();
1984 tx_repo.expect_create().returning(|t| Ok(t.clone()));
1985
1986 let mut job_producer = MockJobProducerTrait::new();
1988 job_producer
1989 .expect_produce_transaction_request_job()
1990 .returning(|_, _| {
1991 Box::pin(async {
1992 Err(crate::jobs::JobProducerError::QueueError(
1993 "Queue is full".to_string(),
1994 ))
1995 })
1996 });
1997
1998 let relayer_repo = Arc::new(MockRelayerRepository::new());
2001 let counter = MockTransactionCounterServiceTrait::new();
2002
2003 let relayer = StellarRelayer::new(
2004 relayer_model,
2005 signer,
2006 provider,
2007 StellarRelayerDependencies::new(
2008 relayer_repo,
2009 ctx.network_repository.clone(),
2010 Arc::new(tx_repo),
2011 Arc::new(counter),
2012 Arc::new(job_producer),
2013 ),
2014 dex_service,
2015 )
2016 .await
2017 .unwrap();
2018
2019 let result = relayer.process_transaction_request(tx_request).await;
2020 assert!(result.is_err());
2021 }
2022
2023 #[tokio::test]
2024 async fn test_process_transaction_request_job_producer_status_check_failure() {
2025 let ctx = TestCtx::default();
2026 ctx.setup_network().await;
2027 let relayer_model = ctx.relayer_model.clone();
2028
2029 let provider = MockStellarProviderTrait::new();
2030 let signer = Arc::new(MockStellarSignTrait::new());
2031 let dex_service = create_mock_dex_service();
2032
2033 let tx_request = create_test_transaction_request();
2034
2035 let mut tx_repo = MockTransactionRepository::new();
2036 tx_repo.expect_create().returning(|t| Ok(t.clone()));
2037
2038 let mut job_producer = MockJobProducerTrait::new();
2039
2040 job_producer
2042 .expect_produce_transaction_request_job()
2043 .returning(|_, _| Box::pin(async { Ok(()) }));
2044
2045 job_producer
2047 .expect_produce_check_transaction_status_job()
2048 .returning(|_, _| {
2049 Box::pin(async {
2050 Err(crate::jobs::JobProducerError::QueueError(
2051 "Failed to queue job".to_string(),
2052 ))
2053 })
2054 });
2055
2056 let relayer_repo = Arc::new(MockRelayerRepository::new());
2057 let counter = MockTransactionCounterServiceTrait::new();
2058
2059 let relayer = StellarRelayer::new(
2060 relayer_model,
2061 signer,
2062 provider,
2063 StellarRelayerDependencies::new(
2064 relayer_repo,
2065 ctx.network_repository.clone(),
2066 Arc::new(tx_repo),
2067 Arc::new(counter),
2068 Arc::new(job_producer),
2069 ),
2070 dex_service,
2071 )
2072 .await
2073 .unwrap();
2074
2075 let result = relayer.process_transaction_request(tx_request).await;
2076 assert!(result.is_err());
2077 }
2078
2079 #[tokio::test]
2080 async fn test_process_transaction_request_preserves_transaction_data() {
2081 let ctx = TestCtx::default();
2082 ctx.setup_network().await;
2083 let relayer_model = ctx.relayer_model.clone();
2084
2085 let provider = MockStellarProviderTrait::new();
2086 let signer = Arc::new(MockStellarSignTrait::new());
2087 let dex_service = create_mock_dex_service();
2088
2089 let tx_request = create_test_transaction_request();
2090
2091 let mut tx_repo = MockTransactionRepository::new();
2092 tx_repo.expect_create().returning(|t| Ok(t.clone()));
2093
2094 let mut job_producer = MockJobProducerTrait::new();
2095 job_producer
2096 .expect_produce_transaction_request_job()
2097 .returning(|_, _| Box::pin(async { Ok(()) }));
2098 job_producer
2099 .expect_produce_check_transaction_status_job()
2100 .returning(|_, _| Box::pin(async { Ok(()) }));
2101
2102 let relayer_repo = Arc::new(MockRelayerRepository::new());
2103 let counter = MockTransactionCounterServiceTrait::new();
2104
2105 let relayer = StellarRelayer::new(
2106 relayer_model.clone(),
2107 signer,
2108 provider,
2109 StellarRelayerDependencies::new(
2110 relayer_repo,
2111 ctx.network_repository.clone(),
2112 Arc::new(tx_repo),
2113 Arc::new(counter),
2114 Arc::new(job_producer),
2115 ),
2116 dex_service,
2117 )
2118 .await
2119 .unwrap();
2120
2121 let result = relayer.process_transaction_request(tx_request).await;
2122 assert!(result.is_ok());
2123
2124 let returned_tx = result.unwrap();
2125 assert_eq!(returned_tx.relayer_id, relayer_model.id);
2126 assert_eq!(returned_tx.network_type, NetworkType::Stellar);
2127 assert_eq!(returned_tx.status, TransactionStatus::Pending);
2128 }
2129 }
2130
2131 mod populate_allowed_tokens_metadata_tests {
2133 use super::*;
2134 use crate::models::StellarTokenKind;
2135
2136 #[tokio::test]
2137 async fn test_populate_allowed_tokens_metadata_no_tokens() {
2138 let ctx = TestCtx::default();
2139 ctx.setup_network().await;
2140 let relayer_model = ctx.relayer_model.clone();
2141
2142 let provider = MockStellarProviderTrait::new();
2143 let signer = Arc::new(MockStellarSignTrait::new());
2144 let dex_service = create_mock_dex_service();
2145
2146 let mut relayer_repo = MockRelayerRepository::new();
2147 relayer_repo.expect_update_policy().times(0);
2149
2150 let tx_repo = MockTransactionRepository::new();
2151 let job_producer = MockJobProducerTrait::new();
2152 let counter = MockTransactionCounterServiceTrait::new();
2153
2154 let relayer = StellarRelayer::new(
2155 relayer_model.clone(),
2156 signer,
2157 provider,
2158 StellarRelayerDependencies::new(
2159 Arc::new(relayer_repo),
2160 ctx.network_repository.clone(),
2161 Arc::new(tx_repo),
2162 Arc::new(counter),
2163 Arc::new(job_producer),
2164 ),
2165 dex_service,
2166 )
2167 .await
2168 .unwrap();
2169
2170 let result = relayer.populate_allowed_tokens_metadata().await;
2171 assert!(result.is_ok());
2172 }
2173
2174 #[tokio::test]
2175 async fn test_populate_allowed_tokens_metadata_empty_tokens() {
2176 let ctx = TestCtx::default();
2177 ctx.setup_network().await;
2178 let mut relayer_model = ctx.relayer_model.clone();
2179
2180 let mut policy = RelayerStellarPolicy::default();
2182 policy.allowed_tokens = Some(vec![]);
2183 relayer_model.policies = RelayerNetworkPolicy::Stellar(policy);
2184
2185 let provider = MockStellarProviderTrait::new();
2186 let signer = Arc::new(MockStellarSignTrait::new());
2187 let dex_service = create_mock_dex_service();
2188
2189 let mut relayer_repo = MockRelayerRepository::new();
2190 relayer_repo.expect_update_policy().times(0);
2192
2193 let tx_repo = MockTransactionRepository::new();
2194 let job_producer = MockJobProducerTrait::new();
2195 let counter = MockTransactionCounterServiceTrait::new();
2196
2197 let relayer = StellarRelayer::new(
2198 relayer_model.clone(),
2199 signer,
2200 provider,
2201 StellarRelayerDependencies::new(
2202 Arc::new(relayer_repo),
2203 ctx.network_repository.clone(),
2204 Arc::new(tx_repo),
2205 Arc::new(counter),
2206 Arc::new(job_producer),
2207 ),
2208 dex_service,
2209 )
2210 .await
2211 .unwrap();
2212
2213 let result = relayer.populate_allowed_tokens_metadata().await;
2214 assert!(result.is_ok());
2215 }
2216
2217 #[tokio::test]
2218 async fn test_populate_allowed_tokens_metadata_classic_asset_success() {
2219 let ctx = TestCtx::default();
2220 ctx.setup_network().await;
2221 let mut relayer_model = ctx.relayer_model.clone();
2222
2223 let mut policy = RelayerStellarPolicy::default();
2225 policy.allowed_tokens = Some(vec![crate::models::StellarAllowedTokensPolicy {
2226 asset: "USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5".to_string(),
2227 metadata: None,
2228 max_allowed_fee: None,
2229 swap_config: None,
2230 }]);
2231 relayer_model.policies = RelayerNetworkPolicy::Stellar(policy);
2232
2233 let provider = MockStellarProviderTrait::new();
2234 let signer = Arc::new(MockStellarSignTrait::new());
2235 let dex_service = create_mock_dex_service();
2236
2237 let mut relayer_repo = MockRelayerRepository::new();
2238 relayer_repo
2239 .expect_update_policy()
2240 .times(1)
2241 .returning(|_, _| Ok(RelayerRepoModel::default()));
2242
2243 let tx_repo = MockTransactionRepository::new();
2244 let job_producer = MockJobProducerTrait::new();
2245 let counter = MockTransactionCounterServiceTrait::new();
2246
2247 let relayer = StellarRelayer::new(
2248 relayer_model.clone(),
2249 signer,
2250 provider,
2251 StellarRelayerDependencies::new(
2252 Arc::new(relayer_repo),
2253 ctx.network_repository.clone(),
2254 Arc::new(tx_repo),
2255 Arc::new(counter),
2256 Arc::new(job_producer),
2257 ),
2258 dex_service,
2259 )
2260 .await
2261 .unwrap();
2262
2263 let result = relayer.populate_allowed_tokens_metadata().await;
2264 assert!(result.is_ok());
2265
2266 let updated_policy = result.unwrap();
2267 assert!(updated_policy.allowed_tokens.is_some());
2268
2269 let tokens = updated_policy.allowed_tokens.unwrap();
2270 assert_eq!(tokens.len(), 1);
2271
2272 let token = &tokens[0];
2274 assert!(token.metadata.is_some());
2275
2276 let metadata = token.metadata.as_ref().unwrap();
2277 assert_eq!(metadata.decimals, 7); assert_eq!(
2279 metadata.canonical_asset_id,
2280 "USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5"
2281 );
2282
2283 match &metadata.kind {
2285 StellarTokenKind::Classic { code, issuer } => {
2286 assert_eq!(code, "USDC");
2287 assert_eq!(
2288 issuer,
2289 "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5"
2290 );
2291 }
2292 _ => panic!("Expected Classic token kind"),
2293 }
2294 }
2295
2296 #[tokio::test]
2297 async fn test_populate_allowed_tokens_metadata_multiple_tokens() {
2298 let ctx = TestCtx::default();
2299 ctx.setup_network().await;
2300 let mut relayer_model = ctx.relayer_model.clone();
2301
2302 let mut policy = RelayerStellarPolicy::default();
2304 policy.allowed_tokens = Some(vec![
2305 crate::models::StellarAllowedTokensPolicy {
2306 asset: "USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5"
2307 .to_string(),
2308 metadata: None,
2309 max_allowed_fee: None,
2310 swap_config: None,
2311 },
2312 crate::models::StellarAllowedTokensPolicy {
2313 asset: "AQUA:GAHPYWLK6YRN7CVYZOO4H3VDRZ7PVF5UJGLZCSPAEIKJE2XSWF5LAGER"
2314 .to_string(),
2315 metadata: None,
2316 max_allowed_fee: Some(1000000),
2317 swap_config: None,
2318 },
2319 ]);
2320 relayer_model.policies = RelayerNetworkPolicy::Stellar(policy);
2321
2322 let provider = MockStellarProviderTrait::new();
2323 let signer = Arc::new(MockStellarSignTrait::new());
2324 let dex_service = create_mock_dex_service();
2325
2326 let mut relayer_repo = MockRelayerRepository::new();
2327 relayer_repo
2328 .expect_update_policy()
2329 .times(1)
2330 .returning(|_, _| Ok(RelayerRepoModel::default()));
2331
2332 let tx_repo = MockTransactionRepository::new();
2333 let job_producer = MockJobProducerTrait::new();
2334 let counter = MockTransactionCounterServiceTrait::new();
2335
2336 let relayer = StellarRelayer::new(
2337 relayer_model.clone(),
2338 signer,
2339 provider,
2340 StellarRelayerDependencies::new(
2341 Arc::new(relayer_repo),
2342 ctx.network_repository.clone(),
2343 Arc::new(tx_repo),
2344 Arc::new(counter),
2345 Arc::new(job_producer),
2346 ),
2347 dex_service,
2348 )
2349 .await
2350 .unwrap();
2351
2352 let result = relayer.populate_allowed_tokens_metadata().await;
2353 assert!(result.is_ok());
2354
2355 let updated_policy = result.unwrap();
2356 let tokens = updated_policy.allowed_tokens.unwrap();
2357 assert_eq!(tokens.len(), 2);
2358
2359 assert!(tokens[0].metadata.is_some());
2361 assert!(tokens[1].metadata.is_some());
2362
2363 let usdc_metadata = tokens[0].metadata.as_ref().unwrap();
2365 match &usdc_metadata.kind {
2366 StellarTokenKind::Classic { code, .. } => {
2367 assert_eq!(code, "USDC");
2368 }
2369 _ => panic!("Expected Classic token kind for USDC"),
2370 }
2371
2372 let aqua_metadata = tokens[1].metadata.as_ref().unwrap();
2374 match &aqua_metadata.kind {
2375 StellarTokenKind::Classic { code, .. } => {
2376 assert_eq!(code, "AQUA");
2377 }
2378 _ => panic!("Expected Classic token kind for AQUA"),
2379 }
2380
2381 assert_eq!(tokens[1].max_allowed_fee, Some(1000000));
2383 }
2384
2385 #[tokio::test]
2386 async fn test_populate_allowed_tokens_metadata_invalid_asset() {
2387 let ctx = TestCtx::default();
2388 ctx.setup_network().await;
2389 let mut relayer_model = ctx.relayer_model.clone();
2390
2391 let mut policy = RelayerStellarPolicy::default();
2393 policy.allowed_tokens = Some(vec![crate::models::StellarAllowedTokensPolicy {
2394 asset: "INVALID_FORMAT".to_string(), metadata: None,
2396 max_allowed_fee: None,
2397 swap_config: None,
2398 }]);
2399 relayer_model.policies = RelayerNetworkPolicy::Stellar(policy);
2400
2401 let provider = MockStellarProviderTrait::new();
2402 let signer = Arc::new(MockStellarSignTrait::new());
2403 let dex_service = create_mock_dex_service();
2404
2405 let relayer_repo = MockRelayerRepository::new();
2406 let tx_repo = MockTransactionRepository::new();
2407 let job_producer = MockJobProducerTrait::new();
2408 let counter = MockTransactionCounterServiceTrait::new();
2409
2410 let relayer = StellarRelayer::new(
2411 relayer_model.clone(),
2412 signer,
2413 provider,
2414 StellarRelayerDependencies::new(
2415 Arc::new(relayer_repo),
2416 ctx.network_repository.clone(),
2417 Arc::new(tx_repo),
2418 Arc::new(counter),
2419 Arc::new(job_producer),
2420 ),
2421 dex_service,
2422 )
2423 .await
2424 .unwrap();
2425
2426 let result = relayer.populate_allowed_tokens_metadata().await;
2427 assert!(result.is_err());
2428 }
2429 }
2430
2431 mod migrate_fee_payment_strategy_tests {
2433 use super::*;
2434
2435 #[tokio::test]
2436 async fn test_migrate_fee_payment_strategy_in_memory_storage() {
2437 let ctx = TestCtx::default();
2438 ctx.setup_network().await;
2439 let relayer_model = ctx.relayer_model.clone();
2440
2441 let provider = MockStellarProviderTrait::new();
2442 let signer = Arc::new(MockStellarSignTrait::new());
2443 let dex_service = create_mock_dex_service();
2444
2445 let mut relayer_repo = MockRelayerRepository::new();
2446 relayer_repo
2448 .expect_is_persistent_storage()
2449 .returning(|| false);
2450 relayer_repo.expect_update_policy().times(0);
2452
2453 let tx_repo = MockTransactionRepository::new();
2454 let job_producer = MockJobProducerTrait::new();
2455 let counter = MockTransactionCounterServiceTrait::new();
2456
2457 let relayer = StellarRelayer::new(
2458 relayer_model.clone(),
2459 signer,
2460 provider,
2461 StellarRelayerDependencies::new(
2462 Arc::new(relayer_repo),
2463 ctx.network_repository.clone(),
2464 Arc::new(tx_repo),
2465 Arc::new(counter),
2466 Arc::new(job_producer),
2467 ),
2468 dex_service,
2469 )
2470 .await
2471 .unwrap();
2472
2473 let result = relayer.migrate_fee_payment_strategy_if_needed().await;
2474 assert!(result.is_ok());
2475 }
2476
2477 #[tokio::test]
2478 async fn test_migrate_fee_payment_strategy_already_set() {
2479 let ctx = TestCtx::default();
2480 ctx.setup_network().await;
2481 let mut relayer_model = ctx.relayer_model.clone();
2482
2483 let mut policy = RelayerStellarPolicy::default();
2485 policy.fee_payment_strategy = Some(StellarFeePaymentStrategy::User);
2486 relayer_model.policies = RelayerNetworkPolicy::Stellar(policy);
2487
2488 let provider = MockStellarProviderTrait::new();
2489 let signer = Arc::new(MockStellarSignTrait::new());
2490 let dex_service = create_mock_dex_service();
2491
2492 let mut relayer_repo = MockRelayerRepository::new();
2493 relayer_repo
2494 .expect_is_persistent_storage()
2495 .returning(|| true);
2496 relayer_repo.expect_update_policy().times(0);
2498
2499 let tx_repo = MockTransactionRepository::new();
2500 let job_producer = MockJobProducerTrait::new();
2501 let counter = MockTransactionCounterServiceTrait::new();
2502
2503 let relayer = StellarRelayer::new(
2504 relayer_model.clone(),
2505 signer,
2506 provider,
2507 StellarRelayerDependencies::new(
2508 Arc::new(relayer_repo),
2509 ctx.network_repository.clone(),
2510 Arc::new(tx_repo),
2511 Arc::new(counter),
2512 Arc::new(job_producer),
2513 ),
2514 dex_service,
2515 )
2516 .await
2517 .unwrap();
2518
2519 let result = relayer.migrate_fee_payment_strategy_if_needed().await;
2520 assert!(result.is_ok());
2521 }
2522
2523 #[tokio::test]
2524 async fn test_migrate_fee_payment_strategy_migration_needed() {
2525 let ctx = TestCtx::default();
2526 ctx.setup_network().await;
2527 let relayer_model = ctx.relayer_model.clone();
2528
2529 let provider = MockStellarProviderTrait::new();
2530 let signer = Arc::new(MockStellarSignTrait::new());
2531 let dex_service = create_mock_dex_service();
2532
2533 let mut relayer_repo = MockRelayerRepository::new();
2534 relayer_repo
2535 .expect_is_persistent_storage()
2536 .returning(|| true);
2537 relayer_repo
2538 .expect_update_policy()
2539 .times(1)
2540 .returning(|_, policy| {
2541 if let RelayerNetworkPolicy::Stellar(stellar_policy) = &policy {
2543 assert_eq!(
2544 stellar_policy.fee_payment_strategy,
2545 Some(StellarFeePaymentStrategy::Relayer)
2546 );
2547 }
2548 Ok(RelayerRepoModel::default())
2549 });
2550
2551 let tx_repo = MockTransactionRepository::new();
2552 let job_producer = MockJobProducerTrait::new();
2553 let counter = MockTransactionCounterServiceTrait::new();
2554
2555 let relayer = StellarRelayer::new(
2556 relayer_model.clone(),
2557 signer,
2558 provider,
2559 StellarRelayerDependencies::new(
2560 Arc::new(relayer_repo),
2561 ctx.network_repository.clone(),
2562 Arc::new(tx_repo),
2563 Arc::new(counter),
2564 Arc::new(job_producer),
2565 ),
2566 dex_service,
2567 )
2568 .await
2569 .unwrap();
2570
2571 let result = relayer.migrate_fee_payment_strategy_if_needed().await;
2572 assert!(result.is_ok());
2573 }
2574
2575 #[tokio::test]
2576 async fn test_migrate_fee_payment_strategy_update_fails() {
2577 let ctx = TestCtx::default();
2578 ctx.setup_network().await;
2579 let relayer_model = ctx.relayer_model.clone();
2580
2581 let provider = MockStellarProviderTrait::new();
2582 let signer = Arc::new(MockStellarSignTrait::new());
2583 let dex_service = create_mock_dex_service();
2584
2585 let mut relayer_repo = MockRelayerRepository::new();
2586 relayer_repo
2587 .expect_is_persistent_storage()
2588 .returning(|| true);
2589 relayer_repo
2590 .expect_update_policy()
2591 .times(1)
2592 .returning(|_, _| {
2593 Err(RepositoryError::TransactionFailure(
2594 "Database error".to_string(),
2595 ))
2596 });
2597
2598 let tx_repo = MockTransactionRepository::new();
2599 let job_producer = MockJobProducerTrait::new();
2600 let counter = MockTransactionCounterServiceTrait::new();
2601
2602 let relayer = StellarRelayer::new(
2603 relayer_model.clone(),
2604 signer,
2605 provider,
2606 StellarRelayerDependencies::new(
2607 Arc::new(relayer_repo),
2608 ctx.network_repository.clone(),
2609 Arc::new(tx_repo),
2610 Arc::new(counter),
2611 Arc::new(job_producer),
2612 ),
2613 dex_service,
2614 )
2615 .await
2616 .unwrap();
2617
2618 let result = relayer.migrate_fee_payment_strategy_if_needed().await;
2619 assert!(result.is_err());
2620 assert!(matches!(
2621 result.unwrap_err(),
2622 RelayerError::PolicyConfigurationError(_)
2623 ));
2624 }
2625 }
2626
2627 mod check_balance_and_trigger_token_swap_tests {
2629 use super::*;
2630 use crate::models::RelayerStellarSwapConfig;
2631
2632 #[tokio::test]
2633 async fn test_check_balance_no_swap_config() {
2634 let ctx = TestCtx::default();
2635 ctx.setup_network().await;
2636 let relayer_model = ctx.relayer_model.clone();
2637
2638 let provider = MockStellarProviderTrait::new();
2639 let signer = Arc::new(MockStellarSignTrait::new());
2640 let dex_service = create_mock_dex_service();
2641
2642 let relayer_repo = MockRelayerRepository::new();
2643 let tx_repo = MockTransactionRepository::new();
2644 let job_producer = MockJobProducerTrait::new();
2645 let counter = MockTransactionCounterServiceTrait::new();
2646
2647 let relayer = StellarRelayer::new(
2648 relayer_model.clone(),
2649 signer,
2650 provider,
2651 StellarRelayerDependencies::new(
2652 Arc::new(relayer_repo),
2653 ctx.network_repository.clone(),
2654 Arc::new(tx_repo),
2655 Arc::new(counter),
2656 Arc::new(job_producer),
2657 ),
2658 dex_service,
2659 )
2660 .await
2661 .unwrap();
2662
2663 let result = relayer
2664 .check_balance_and_trigger_token_swap_if_needed()
2665 .await;
2666 assert!(result.is_ok());
2667 }
2668
2669 #[tokio::test]
2670 async fn test_check_balance_no_threshold() {
2671 let ctx = TestCtx::default();
2672 ctx.setup_network().await;
2673 let mut relayer_model = ctx.relayer_model.clone();
2674
2675 let mut policy = RelayerStellarPolicy::default();
2677 policy.swap_config = Some(RelayerStellarSwapConfig {
2678 strategies: vec![],
2679 min_balance_threshold: None,
2680 cron_schedule: None,
2681 });
2682 relayer_model.policies = RelayerNetworkPolicy::Stellar(policy);
2683
2684 let provider = MockStellarProviderTrait::new();
2685 let signer = Arc::new(MockStellarSignTrait::new());
2686 let dex_service = create_mock_dex_service();
2687
2688 let relayer_repo = MockRelayerRepository::new();
2689 let tx_repo = MockTransactionRepository::new();
2690 let job_producer = MockJobProducerTrait::new();
2691 let counter = MockTransactionCounterServiceTrait::new();
2692
2693 let relayer = StellarRelayer::new(
2694 relayer_model.clone(),
2695 signer,
2696 provider,
2697 StellarRelayerDependencies::new(
2698 Arc::new(relayer_repo),
2699 ctx.network_repository.clone(),
2700 Arc::new(tx_repo),
2701 Arc::new(counter),
2702 Arc::new(job_producer),
2703 ),
2704 dex_service,
2705 )
2706 .await
2707 .unwrap();
2708
2709 let result = relayer
2710 .check_balance_and_trigger_token_swap_if_needed()
2711 .await;
2712 assert!(result.is_ok());
2713 }
2714
2715 #[tokio::test]
2716 async fn test_check_balance_above_threshold() {
2717 let ctx = TestCtx::default();
2718 ctx.setup_network().await;
2719 let mut relayer_model = ctx.relayer_model.clone();
2720
2721 let mut policy = RelayerStellarPolicy::default();
2723 policy.swap_config = Some(RelayerStellarSwapConfig {
2724 strategies: vec![],
2725 min_balance_threshold: Some(1000000), cron_schedule: None,
2727 });
2728 relayer_model.policies = RelayerNetworkPolicy::Stellar(policy);
2729
2730 let mut provider = MockStellarProviderTrait::new();
2731 provider.expect_get_account().returning(|_| {
2733 Box::pin(async {
2734 Ok(AccountEntry {
2735 account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32]))),
2736 balance: 10000000, ext: AccountEntryExt::V0,
2738 flags: 0,
2739 home_domain: String32::default(),
2740 inflation_dest: None,
2741 seq_num: SequenceNumber(5),
2742 num_sub_entries: 0,
2743 signers: VecM::default(),
2744 thresholds: Thresholds([0, 0, 0, 0]),
2745 })
2746 })
2747 });
2748
2749 let signer = Arc::new(MockStellarSignTrait::new());
2750 let dex_service = create_mock_dex_service();
2751
2752 let relayer_repo = MockRelayerRepository::new();
2753 let tx_repo = MockTransactionRepository::new();
2754 let job_producer = MockJobProducerTrait::new();
2755 let counter = MockTransactionCounterServiceTrait::new();
2756
2757 let relayer = StellarRelayer::new(
2758 relayer_model.clone(),
2759 signer,
2760 provider,
2761 StellarRelayerDependencies::new(
2762 Arc::new(relayer_repo),
2763 ctx.network_repository.clone(),
2764 Arc::new(tx_repo),
2765 Arc::new(counter),
2766 Arc::new(job_producer),
2767 ),
2768 dex_service,
2769 )
2770 .await
2771 .unwrap();
2772
2773 let result = relayer
2774 .check_balance_and_trigger_token_swap_if_needed()
2775 .await;
2776 assert!(result.is_ok());
2777 }
2778
2779 #[tokio::test]
2780 async fn test_check_balance_provider_error() {
2781 let ctx = TestCtx::default();
2782 ctx.setup_network().await;
2783 let mut relayer_model = ctx.relayer_model.clone();
2784
2785 let mut policy = RelayerStellarPolicy::default();
2787 policy.swap_config = Some(RelayerStellarSwapConfig {
2788 strategies: vec![],
2789 min_balance_threshold: Some(1000000),
2790 cron_schedule: None,
2791 });
2792 relayer_model.policies = RelayerNetworkPolicy::Stellar(policy);
2793
2794 let mut provider = MockStellarProviderTrait::new();
2795 provider.expect_get_account().returning(|_| {
2796 Box::pin(async { Err(ProviderError::Other("Network error".to_string())) })
2797 });
2798
2799 let signer = Arc::new(MockStellarSignTrait::new());
2800 let dex_service = create_mock_dex_service();
2801
2802 let relayer_repo = MockRelayerRepository::new();
2803 let tx_repo = MockTransactionRepository::new();
2804 let job_producer = MockJobProducerTrait::new();
2805 let counter = MockTransactionCounterServiceTrait::new();
2806
2807 let relayer = StellarRelayer::new(
2808 relayer_model.clone(),
2809 signer,
2810 provider,
2811 StellarRelayerDependencies::new(
2812 Arc::new(relayer_repo),
2813 ctx.network_repository.clone(),
2814 Arc::new(tx_repo),
2815 Arc::new(counter),
2816 Arc::new(job_producer),
2817 ),
2818 dex_service,
2819 )
2820 .await
2821 .unwrap();
2822
2823 let result = relayer
2824 .check_balance_and_trigger_token_swap_if_needed()
2825 .await;
2826 assert!(result.is_err());
2827 }
2828 }
2829
2830 mod check_health_tests {
2832 use super::*;
2833 use crate::models::RelayerStellarSwapConfig;
2834
2835 #[tokio::test]
2836 async fn test_check_health_success() {
2837 let ctx = TestCtx::default();
2838 ctx.setup_network().await;
2839 let relayer_model = ctx.relayer_model.clone();
2840
2841 let mut provider = MockStellarProviderTrait::new();
2842 provider.expect_get_account().returning(|_| {
2843 Box::pin(async {
2844 Ok(AccountEntry {
2845 account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32]))),
2846 balance: 10000000,
2847 ext: AccountEntryExt::V0,
2848 flags: 0,
2849 home_domain: String32::default(),
2850 inflation_dest: None,
2851 seq_num: SequenceNumber(5),
2852 num_sub_entries: 0,
2853 signers: VecM::default(),
2854 thresholds: Thresholds([0, 0, 0, 0]),
2855 })
2856 })
2857 });
2858
2859 let signer = Arc::new(MockStellarSignTrait::new());
2860 let dex_service = create_mock_dex_service();
2861
2862 let relayer_repo = MockRelayerRepository::new();
2863 let tx_repo = MockTransactionRepository::new();
2864 let job_producer = MockJobProducerTrait::new();
2865
2866 let mut counter = MockTransactionCounterServiceTrait::new();
2867 counter
2868 .expect_set()
2869 .returning(|_| Box::pin(async { Ok(()) }));
2870
2871 let relayer = StellarRelayer::new(
2872 relayer_model.clone(),
2873 signer,
2874 provider,
2875 StellarRelayerDependencies::new(
2876 Arc::new(relayer_repo),
2877 ctx.network_repository.clone(),
2878 Arc::new(tx_repo),
2879 Arc::new(counter),
2880 Arc::new(job_producer),
2881 ),
2882 dex_service,
2883 )
2884 .await
2885 .unwrap();
2886
2887 let result = relayer.check_health().await;
2888 assert!(result.is_ok());
2889 }
2890
2891 #[tokio::test]
2892 async fn test_check_health_sequence_sync_fails() {
2893 let ctx = TestCtx::default();
2894 ctx.setup_network().await;
2895 let relayer_model = ctx.relayer_model.clone();
2896
2897 let mut provider = MockStellarProviderTrait::new();
2898 provider.expect_get_account().returning(|_| {
2899 Box::pin(async { Err(ProviderError::Other("Network error".to_string())) })
2900 });
2901
2902 let signer = Arc::new(MockStellarSignTrait::new());
2903 let dex_service = create_mock_dex_service();
2904
2905 let relayer_repo = MockRelayerRepository::new();
2906 let tx_repo = MockTransactionRepository::new();
2907 let job_producer = MockJobProducerTrait::new();
2908 let counter = MockTransactionCounterServiceTrait::new();
2909
2910 let relayer = StellarRelayer::new(
2911 relayer_model.clone(),
2912 signer,
2913 provider,
2914 StellarRelayerDependencies::new(
2915 Arc::new(relayer_repo),
2916 ctx.network_repository.clone(),
2917 Arc::new(tx_repo),
2918 Arc::new(counter),
2919 Arc::new(job_producer),
2920 ),
2921 dex_service,
2922 )
2923 .await
2924 .unwrap();
2925
2926 let result = relayer.check_health().await;
2927 assert!(result.is_err());
2928 let failures = result.unwrap_err();
2929 assert_eq!(failures.len(), 1);
2930 assert!(matches!(
2931 failures[0],
2932 HealthCheckFailure::SequenceSyncFailed(_)
2933 ));
2934 }
2935
2936 #[tokio::test]
2937 async fn test_check_health_with_user_fee_strategy() {
2938 let ctx = TestCtx::default();
2939 ctx.setup_network().await;
2940 let mut relayer_model = ctx.relayer_model.clone();
2941
2942 let mut policy = RelayerStellarPolicy::default();
2944 policy.fee_payment_strategy = Some(StellarFeePaymentStrategy::User);
2945 policy.swap_config = Some(RelayerStellarSwapConfig {
2946 strategies: vec![],
2947 min_balance_threshold: Some(1000000),
2948 cron_schedule: None,
2949 });
2950 relayer_model.policies = RelayerNetworkPolicy::Stellar(policy);
2951
2952 let mut provider = MockStellarProviderTrait::new();
2953 provider.expect_get_account().returning(|_| {
2954 Box::pin(async {
2955 Ok(AccountEntry {
2956 account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32]))),
2957 balance: 10000000, ext: AccountEntryExt::V0,
2959 flags: 0,
2960 home_domain: String32::default(),
2961 inflation_dest: None,
2962 seq_num: SequenceNumber(5),
2963 num_sub_entries: 0,
2964 signers: VecM::default(),
2965 thresholds: Thresholds([0, 0, 0, 0]),
2966 })
2967 })
2968 });
2969
2970 let signer = Arc::new(MockStellarSignTrait::new());
2971 let dex_service = create_mock_dex_service();
2972
2973 let relayer_repo = MockRelayerRepository::new();
2974 let tx_repo = MockTransactionRepository::new();
2975 let job_producer = MockJobProducerTrait::new();
2976
2977 let mut counter = MockTransactionCounterServiceTrait::new();
2978 counter
2979 .expect_set()
2980 .returning(|_| Box::pin(async { Ok(()) }));
2981
2982 let relayer = StellarRelayer::new(
2983 relayer_model.clone(),
2984 signer,
2985 provider,
2986 StellarRelayerDependencies::new(
2987 Arc::new(relayer_repo),
2988 ctx.network_repository.clone(),
2989 Arc::new(tx_repo),
2990 Arc::new(counter),
2991 Arc::new(job_producer),
2992 ),
2993 dex_service,
2994 )
2995 .await
2996 .unwrap();
2997
2998 let result = relayer.check_health().await;
2999 assert!(result.is_ok());
3001 }
3002 }
3003
3004 mod rpc_tests {
3006 use super::*;
3007 use crate::models::{JsonRpcId, StellarRpcRequest};
3008
3009 #[tokio::test]
3010 async fn test_rpc_invalid_network_request() {
3011 let ctx = TestCtx::default();
3012 ctx.setup_network().await;
3013 let relayer_model = ctx.relayer_model.clone();
3014
3015 let provider = MockStellarProviderTrait::new();
3016 let signer = Arc::new(MockStellarSignTrait::new());
3017 let dex_service = create_mock_dex_service();
3018
3019 let relayer_repo = MockRelayerRepository::new();
3020 let tx_repo = MockTransactionRepository::new();
3021 let job_producer = MockJobProducerTrait::new();
3022 let counter = MockTransactionCounterServiceTrait::new();
3023
3024 let relayer = StellarRelayer::new(
3025 relayer_model.clone(),
3026 signer,
3027 provider,
3028 StellarRelayerDependencies::new(
3029 Arc::new(relayer_repo),
3030 ctx.network_repository.clone(),
3031 Arc::new(tx_repo),
3032 Arc::new(counter),
3033 Arc::new(job_producer),
3034 ),
3035 dex_service,
3036 )
3037 .await
3038 .unwrap();
3039
3040 let request = JsonRpcRequest {
3042 jsonrpc: "2.0".to_string(),
3043 id: Some(JsonRpcId::Number(1)),
3044 params: NetworkRpcRequest::Evm(crate::models::EvmRpcRequest::RawRpcRequest {
3045 method: "eth_blockNumber".to_string(),
3046 params: serde_json::Value::Null,
3047 }),
3048 };
3049
3050 let result = relayer.rpc(request).await;
3051 assert!(result.is_ok());
3052 let response = result.unwrap();
3053 assert!(response.error.is_some());
3055 }
3056
3057 #[tokio::test]
3058 async fn test_rpc_provider_error() {
3059 let ctx = TestCtx::default();
3060 ctx.setup_network().await;
3061 let relayer_model = ctx.relayer_model.clone();
3062
3063 let mut provider = MockStellarProviderTrait::new();
3064 provider.expect_raw_request_dyn().returning(|_, _, _| {
3065 Box::pin(async { Err(ProviderError::Other("RPC error".to_string())) })
3066 });
3067
3068 let signer = Arc::new(MockStellarSignTrait::new());
3069 let dex_service = create_mock_dex_service();
3070
3071 let relayer_repo = MockRelayerRepository::new();
3072 let tx_repo = MockTransactionRepository::new();
3073 let job_producer = MockJobProducerTrait::new();
3074 let counter = MockTransactionCounterServiceTrait::new();
3075
3076 let relayer = StellarRelayer::new(
3077 relayer_model.clone(),
3078 signer,
3079 provider,
3080 StellarRelayerDependencies::new(
3081 Arc::new(relayer_repo),
3082 ctx.network_repository.clone(),
3083 Arc::new(tx_repo),
3084 Arc::new(counter),
3085 Arc::new(job_producer),
3086 ),
3087 dex_service,
3088 )
3089 .await
3090 .unwrap();
3091
3092 let request = JsonRpcRequest {
3093 jsonrpc: "2.0".to_string(),
3094 id: Some(JsonRpcId::Number(1)),
3095 params: NetworkRpcRequest::Stellar(StellarRpcRequest::RawRpcRequest {
3096 method: "getHealth".to_string(),
3097 params: serde_json::Value::Null,
3098 }),
3099 };
3100
3101 let result = relayer.rpc(request).await;
3102 assert!(result.is_ok());
3103 let response = result.unwrap();
3104 assert!(response.error.is_some());
3106 }
3107
3108 #[tokio::test]
3109 async fn test_rpc_success() {
3110 let ctx = TestCtx::default();
3111 ctx.setup_network().await;
3112 let relayer_model = ctx.relayer_model.clone();
3113
3114 let mut provider = MockStellarProviderTrait::new();
3115 provider.expect_raw_request_dyn().returning(|_, _, _| {
3116 Box::pin(async { Ok(serde_json::json!({"status": "healthy"})) })
3117 });
3118
3119 let signer = Arc::new(MockStellarSignTrait::new());
3120 let dex_service = create_mock_dex_service();
3121
3122 let relayer_repo = MockRelayerRepository::new();
3123 let tx_repo = MockTransactionRepository::new();
3124 let job_producer = MockJobProducerTrait::new();
3125 let counter = MockTransactionCounterServiceTrait::new();
3126
3127 let relayer = StellarRelayer::new(
3128 relayer_model.clone(),
3129 signer,
3130 provider,
3131 StellarRelayerDependencies::new(
3132 Arc::new(relayer_repo),
3133 ctx.network_repository.clone(),
3134 Arc::new(tx_repo),
3135 Arc::new(counter),
3136 Arc::new(job_producer),
3137 ),
3138 dex_service,
3139 )
3140 .await
3141 .unwrap();
3142
3143 let request = JsonRpcRequest {
3144 jsonrpc: "2.0".to_string(),
3145 id: Some(JsonRpcId::Number(1)),
3146 params: NetworkRpcRequest::Stellar(StellarRpcRequest::RawRpcRequest {
3147 method: "getHealth".to_string(),
3148 params: serde_json::Value::Null,
3149 }),
3150 };
3151
3152 let result = relayer.rpc(request).await;
3153 assert!(result.is_ok());
3154 let response = result.unwrap();
3155 assert!(response.error.is_none());
3156 assert!(response.result.is_some());
3157 }
3158 }
3159}