1use crate::{
24 constants::STELLAR_SMALLEST_UNIT_NAME,
25 domain::{
26 stellar::next_sequence_u64, BalanceResponse, SignDataRequest, SignDataResponse,
27 SignTypedDataRequest,
28 },
29 jobs::{JobProducerTrait, TransactionRequest},
30 models::{
31 produce_relayer_disabled_payload, DeletePendingTransactionsResponse, JsonRpcRequest,
32 JsonRpcResponse, NetworkRpcRequest, NetworkRpcResult, NetworkTransactionRequest,
33 NetworkType, RelayerRepoModel, RelayerStatus, RepositoryError, StellarNetwork,
34 StellarRpcResult, TransactionRepoModel, TransactionStatus,
35 },
36 repositories::{
37 InMemoryNetworkRepository, InMemoryRelayerRepository, InMemoryTransactionCounter,
38 InMemoryTransactionRepository, NetworkRepository, RelayerRepository,
39 RelayerRepositoryStorage, Repository, TransactionRepository,
40 },
41 services::{
42 StellarProvider, StellarProviderTrait, TransactionCounterService,
43 TransactionCounterServiceTrait,
44 },
45};
46use async_trait::async_trait;
47use eyre::Result;
48use log::{info, warn};
49use std::sync::Arc;
50
51use crate::domain::relayer::{Relayer, RelayerError};
52
53pub struct StellarRelayerDependencies<R, N, T, J, C>
55where
56 R: Repository<RelayerRepoModel, String> + RelayerRepository + Send + Sync,
57 N: NetworkRepository + Send + Sync,
58 T: Repository<TransactionRepoModel, String> + Send + Sync,
59 J: JobProducerTrait + Send + Sync,
60 C: TransactionCounterServiceTrait + Send + Sync,
61{
62 pub relayer_repository: Arc<R>,
63 pub network_repository: Arc<N>,
64 pub transaction_repository: Arc<T>,
65 pub transaction_counter_service: Arc<C>,
66 pub job_producer: Arc<J>,
67}
68
69impl<R, N, T, J, C> StellarRelayerDependencies<R, N, T, J, C>
70where
71 R: Repository<RelayerRepoModel, String> + RelayerRepository + Send + Sync,
72 N: NetworkRepository + Send + Sync,
73 T: Repository<TransactionRepoModel, String> + Send + Sync,
74 J: JobProducerTrait + Send + Sync,
75 C: TransactionCounterServiceTrait + Send + Sync,
76{
77 pub fn new(
91 relayer_repository: Arc<R>,
92 network_repository: Arc<N>,
93 transaction_repository: Arc<T>,
94 transaction_counter_service: Arc<C>,
95 job_producer: Arc<J>,
96 ) -> Self {
97 Self {
98 relayer_repository,
99 network_repository,
100 transaction_repository,
101 transaction_counter_service,
102 job_producer,
103 }
104 }
105}
106
107#[allow(dead_code)]
108pub struct StellarRelayer<P, R, N, T, J, C>
109where
110 P: StellarProviderTrait + Send + Sync,
111 R: Repository<RelayerRepoModel, String> + RelayerRepository + Send + Sync,
112 N: NetworkRepository + Send + Sync,
113 T: Repository<TransactionRepoModel, String> + TransactionRepository + Send + Sync,
114 J: JobProducerTrait + Send + Sync,
115 C: TransactionCounterServiceTrait + Send + Sync,
116{
117 relayer: RelayerRepoModel,
118 network: StellarNetwork,
119 provider: P,
120 relayer_repository: Arc<R>,
121 network_repository: Arc<N>,
122 transaction_repository: Arc<T>,
123 transaction_counter_service: Arc<C>,
124 job_producer: Arc<J>,
125}
126
127pub type DefaultStellarRelayer<J> = StellarRelayer<
128 StellarProvider,
129 RelayerRepositoryStorage<InMemoryRelayerRepository>,
130 InMemoryNetworkRepository,
131 InMemoryTransactionRepository,
132 J,
133 TransactionCounterService<InMemoryTransactionCounter>,
134>;
135
136impl<P, R, N, T, J, C> StellarRelayer<P, R, N, T, J, C>
137where
138 P: StellarProviderTrait + Send + Sync,
139 R: Repository<RelayerRepoModel, String> + RelayerRepository + Send + Sync,
140 N: NetworkRepository + Send + Sync,
141 T: Repository<TransactionRepoModel, String> + TransactionRepository + Send + Sync,
142 J: JobProducerTrait + Send + Sync,
143 C: TransactionCounterServiceTrait + Send + Sync,
144{
145 #[allow(clippy::too_many_arguments)]
162 pub async fn new(
163 relayer: RelayerRepoModel,
164 provider: P,
165 dependencies: StellarRelayerDependencies<R, N, T, J, C>,
166 ) -> Result<Self, RelayerError> {
167 let network_repo = dependencies
168 .network_repository
169 .get_by_name(NetworkType::Stellar, &relayer.network)
170 .await
171 .ok()
172 .flatten()
173 .ok_or_else(|| {
174 RelayerError::NetworkConfiguration(format!("Network {} not found", relayer.network))
175 })?;
176
177 let network = StellarNetwork::try_from(network_repo)?;
178
179 Ok(Self {
180 relayer,
181 network,
182 provider,
183 relayer_repository: dependencies.relayer_repository,
184 network_repository: dependencies.network_repository,
185 transaction_repository: dependencies.transaction_repository,
186 transaction_counter_service: dependencies.transaction_counter_service,
187 job_producer: dependencies.job_producer,
188 })
189 }
190
191 async fn sync_sequence(&self) -> Result<(), RelayerError> {
192 info!(
193 "Fetching sequence for relayer: {} ({})",
194 self.relayer.id, self.relayer.address
195 );
196
197 let account_entry = self
198 .provider
199 .get_account(&self.relayer.address)
200 .await
201 .map_err(|e| RelayerError::ProviderError(format!("Failed to fetch account: {}", e)))?;
202
203 let next = next_sequence_u64(account_entry.seq_num.0)?;
204
205 info!(
206 "Setting next sequence {} for relayer {}",
207 next, self.relayer.id
208 );
209 self.transaction_counter_service
210 .set(next)
211 .await
212 .map_err(RelayerError::from)?;
213 Ok(())
214 }
215
216 async fn disable_relayer(&self, reasons: &[String]) -> Result<(), RelayerError> {
217 let reason = reasons.join(", ");
218 warn!("Disabling relayer {} due to: {}", self.relayer.id, reason);
219
220 let updated = self
221 .relayer_repository
222 .disable_relayer(self.relayer.id.clone())
223 .await?;
224
225 if let Some(nid) = &self.relayer.notification_id {
226 self.job_producer
227 .produce_send_notification_job(
228 produce_relayer_disabled_payload(nid, &updated, &reason),
229 None,
230 )
231 .await?;
232 }
233 Ok(())
234 }
235}
236
237#[async_trait]
238impl<P, R, N, T, J, C> Relayer for StellarRelayer<P, R, N, T, J, C>
239where
240 P: StellarProviderTrait + Send + Sync,
241 R: Repository<RelayerRepoModel, String> + RelayerRepository + Send + Sync,
242 N: NetworkRepository + Send + Sync,
243 T: Repository<TransactionRepoModel, String> + TransactionRepository + Send + Sync,
244 J: JobProducerTrait + Send + Sync,
245 C: TransactionCounterServiceTrait + Send + Sync,
246{
247 async fn process_transaction_request(
248 &self,
249 network_transaction: NetworkTransactionRequest,
250 ) -> Result<TransactionRepoModel, RelayerError> {
251 let network_model = self
252 .network_repository
253 .get_by_name(NetworkType::Stellar, &self.relayer.network)
254 .await?
255 .ok_or_else(|| {
256 RelayerError::NetworkConfiguration(format!(
257 "Network {} not found",
258 self.relayer.network
259 ))
260 })?;
261 let transaction =
262 TransactionRepoModel::try_from((&network_transaction, &self.relayer, &network_model))?;
263
264 self.transaction_repository
265 .create(transaction.clone())
266 .await
267 .map_err(|e| RepositoryError::TransactionFailure(e.to_string()))?;
268
269 self.job_producer
270 .produce_transaction_request_job(
271 TransactionRequest::new(transaction.id.clone(), transaction.relayer_id.clone()),
272 None,
273 )
274 .await?;
275
276 Ok(transaction)
277 }
278
279 async fn get_balance(&self) -> Result<BalanceResponse, RelayerError> {
280 let account_entry = self
281 .provider
282 .get_account(&self.relayer.address)
283 .await
284 .map_err(|e| {
285 RelayerError::ProviderError(format!("Failed to fetch account for balance: {}", e))
286 })?;
287
288 Ok(BalanceResponse {
289 balance: account_entry.balance as u128,
290 unit: STELLAR_SMALLEST_UNIT_NAME.to_string(),
291 })
292 }
293
294 async fn get_status(&self) -> Result<RelayerStatus, RelayerError> {
295 let relayer_model = &self.relayer;
296
297 let account_entry = self
298 .provider
299 .get_account(&relayer_model.address)
300 .await
301 .map_err(|e| {
302 RelayerError::ProviderError(format!("Failed to get account details: {}", e))
303 })?;
304
305 let sequence_number_str = account_entry.seq_num.0.to_string();
306
307 let balance_response = self.get_balance().await?;
308
309 let pending_statuses = [TransactionStatus::Pending, TransactionStatus::Submitted];
310 let pending_transactions = self
311 .transaction_repository
312 .find_by_status(&relayer_model.id, &pending_statuses[..])
313 .await
314 .map_err(RelayerError::from)?;
315 let pending_transactions_count = pending_transactions.len() as u64;
316
317 let confirmed_statuses = [TransactionStatus::Confirmed];
318 let confirmed_transactions = self
319 .transaction_repository
320 .find_by_status(&relayer_model.id, &confirmed_statuses[..])
321 .await
322 .map_err(RelayerError::from)?;
323
324 let last_confirmed_transaction_timestamp = confirmed_transactions
325 .iter()
326 .filter_map(|tx| tx.confirmed_at.as_ref())
327 .max()
328 .cloned();
329
330 Ok(RelayerStatus::Stellar {
331 balance: balance_response.balance.to_string(),
332 pending_transactions_count,
333 last_confirmed_transaction_timestamp,
334 system_disabled: relayer_model.system_disabled,
335 paused: relayer_model.paused,
336 sequence_number: sequence_number_str,
337 })
338 }
339
340 async fn delete_pending_transactions(
341 &self,
342 ) -> Result<DeletePendingTransactionsResponse, RelayerError> {
343 println!("Stellar delete_pending_transactions...");
344 Ok(DeletePendingTransactionsResponse {
345 queued_for_cancellation_transaction_ids: vec![],
346 failed_to_queue_transaction_ids: vec![],
347 total_processed: 0,
348 })
349 }
350
351 async fn sign_data(&self, _request: SignDataRequest) -> Result<SignDataResponse, RelayerError> {
352 Err(RelayerError::NotSupported(
353 "Signing data not supported for Stellar".to_string(),
354 ))
355 }
356
357 async fn sign_typed_data(
358 &self,
359 _request: SignTypedDataRequest,
360 ) -> Result<SignDataResponse, RelayerError> {
361 Err(RelayerError::NotSupported(
362 "Signing typed data not supported for Stellar".to_string(),
363 ))
364 }
365
366 async fn rpc(
367 &self,
368 _request: JsonRpcRequest<NetworkRpcRequest>,
369 ) -> Result<JsonRpcResponse<NetworkRpcResult>, RelayerError> {
370 println!("Stellar rpc...");
371 Ok(JsonRpcResponse {
372 id: None,
373 jsonrpc: "2.0".to_string(),
374 result: Some(NetworkRpcResult::Stellar(
375 StellarRpcResult::GenericRpcResult("".to_string()),
376 )),
377 error: None,
378 })
379 }
380
381 async fn validate_min_balance(&self) -> Result<(), RelayerError> {
382 Ok(())
383 }
384
385 async fn initialize_relayer(&self) -> Result<(), RelayerError> {
386 info!("Initializing Stellar relayer: {}", self.relayer.id);
387
388 let seq_res = self.sync_sequence().await.err();
389
390 let mut failures: Vec<String> = Vec::new();
391 if let Some(e) = seq_res {
392 failures.push(format!("Sequence sync failed: {}", e));
393 }
394
395 if !failures.is_empty() {
396 self.disable_relayer(&failures).await?;
397 return Ok(()); }
399
400 info!(
401 "Stellar relayer initialized successfully: {}",
402 self.relayer.id
403 );
404 Ok(())
405 }
406}
407
408#[cfg(test)]
409mod tests {
410 use super::*;
411 use crate::{
412 config::{NetworkConfigCommon, StellarNetworkConfig},
413 constants::STELLAR_SMALLEST_UNIT_NAME,
414 jobs::MockJobProducerTrait,
415 models::{
416 NetworkConfigData, NetworkRepoModel, NetworkType, RelayerNetworkPolicy,
417 RelayerRepoModel, RelayerStellarPolicy,
418 },
419 repositories::{
420 InMemoryNetworkRepository, MockRelayerRepository, MockTransactionRepository,
421 },
422 services::{MockStellarProviderTrait, MockTransactionCounterServiceTrait},
423 };
424 use eyre::eyre;
425 use mockall::predicate::*;
426 use soroban_rs::xdr::{
427 AccountEntry, AccountEntryExt, AccountId, PublicKey, SequenceNumber, String32, Thresholds,
428 Uint256, VecM,
429 };
430 use std::future::ready;
431 use std::sync::Arc;
432
433 struct TestCtx {
435 relayer_model: RelayerRepoModel,
436 network_repository: Arc<InMemoryNetworkRepository>,
437 }
438
439 impl Default for TestCtx {
440 fn default() -> Self {
441 let network_repository = Arc::new(InMemoryNetworkRepository::new());
442
443 let relayer_model = RelayerRepoModel {
444 id: "test-relayer-id".to_string(),
445 name: "Test Relayer".to_string(),
446 network: "testnet".to_string(),
447 paused: false,
448 network_type: NetworkType::Stellar,
449 signer_id: "signer-id".to_string(),
450 policies: RelayerNetworkPolicy::Stellar(RelayerStellarPolicy::default()),
451 address: "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF".to_string(),
452 notification_id: Some("notification-id".to_string()),
453 system_disabled: false,
454 custom_rpc_urls: None,
455 };
456
457 TestCtx {
458 relayer_model,
459 network_repository,
460 }
461 }
462 }
463
464 impl TestCtx {
465 async fn setup_network(&self) {
466 let test_network = NetworkRepoModel {
467 id: "stellar:testnet".to_string(),
468 name: "testnet".to_string(),
469 network_type: NetworkType::Stellar,
470 config: NetworkConfigData::Stellar(StellarNetworkConfig {
471 common: NetworkConfigCommon {
472 network: "testnet".to_string(),
473 from: None,
474 rpc_urls: Some(vec!["https://horizon-testnet.stellar.org".to_string()]),
475 explorer_urls: None,
476 average_blocktime_ms: Some(5000),
477 is_testnet: Some(true),
478 tags: None,
479 },
480 passphrase: Some("Test SDF Network ; September 2015".to_string()),
481 }),
482 };
483
484 self.network_repository.create(test_network).await.unwrap();
485 }
486 }
487
488 #[tokio::test]
489 async fn test_sync_sequence_success() {
490 let ctx = TestCtx::default();
491 ctx.setup_network().await;
492 let relayer_model = ctx.relayer_model.clone();
493 let mut provider = MockStellarProviderTrait::new();
494 provider
495 .expect_get_account()
496 .with(eq(relayer_model.address.clone()))
497 .returning(|_| {
498 Box::pin(async {
499 Ok(AccountEntry {
500 account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32]))),
501 balance: 0,
502 ext: AccountEntryExt::V0,
503 flags: 0,
504 home_domain: String32::default(),
505 inflation_dest: None,
506 seq_num: SequenceNumber(5),
507 num_sub_entries: 0,
508 signers: VecM::default(),
509 thresholds: Thresholds([0, 0, 0, 0]),
510 })
511 })
512 });
513 let mut counter = MockTransactionCounterServiceTrait::new();
514 counter
515 .expect_set()
516 .with(eq(6u64))
517 .returning(|_| Box::pin(async { Ok(()) }));
518 let relayer_repo = MockRelayerRepository::new();
519 let tx_repo = MockTransactionRepository::new();
520 let job_producer = MockJobProducerTrait::new();
521
522 let relayer = StellarRelayer::new(
523 relayer_model.clone(),
524 provider,
525 StellarRelayerDependencies::new(
526 Arc::new(relayer_repo),
527 ctx.network_repository.clone(),
528 Arc::new(tx_repo),
529 Arc::new(counter),
530 Arc::new(job_producer),
531 ),
532 )
533 .await
534 .unwrap();
535
536 let result = relayer.sync_sequence().await;
537 assert!(result.is_ok());
538 }
539
540 #[tokio::test]
541 async fn test_sync_sequence_provider_error() {
542 let ctx = TestCtx::default();
543 ctx.setup_network().await;
544 let relayer_model = ctx.relayer_model.clone();
545 let mut provider = MockStellarProviderTrait::new();
546 provider
547 .expect_get_account()
548 .with(eq(relayer_model.address.clone()))
549 .returning(|_| Box::pin(async { Err(eyre!("fail")) }));
550 let counter = MockTransactionCounterServiceTrait::new();
551 let relayer_repo = MockRelayerRepository::new();
552 let tx_repo = MockTransactionRepository::new();
553 let job_producer = MockJobProducerTrait::new();
554
555 let relayer = StellarRelayer::new(
556 relayer_model.clone(),
557 provider,
558 StellarRelayerDependencies::new(
559 Arc::new(relayer_repo),
560 ctx.network_repository.clone(),
561 Arc::new(tx_repo),
562 Arc::new(counter),
563 Arc::new(job_producer),
564 ),
565 )
566 .await
567 .unwrap();
568
569 let result = relayer.sync_sequence().await;
570 assert!(matches!(result, Err(RelayerError::ProviderError(_))));
571 }
572
573 #[tokio::test]
574 async fn test_disable_relayer() {
575 let ctx = TestCtx::default();
576 ctx.setup_network().await;
577 let relayer_model = ctx.relayer_model.clone();
578 let provider = MockStellarProviderTrait::new();
579 let mut relayer_repo = MockRelayerRepository::new();
580 let mut updated_model = relayer_model.clone();
581 updated_model.system_disabled = true;
582 relayer_repo
583 .expect_disable_relayer()
584 .with(eq(relayer_model.id.clone()))
585 .returning(move |_| Ok::<RelayerRepoModel, RepositoryError>(updated_model.clone()));
586 let mut job_producer = MockJobProducerTrait::new();
587 job_producer
588 .expect_produce_send_notification_job()
589 .returning(|_, _| Box::pin(async { Ok(()) }));
590 let tx_repo = MockTransactionRepository::new();
591 let counter = MockTransactionCounterServiceTrait::new();
592
593 let relayer = StellarRelayer::new(
594 relayer_model.clone(),
595 provider,
596 StellarRelayerDependencies::new(
597 Arc::new(relayer_repo),
598 ctx.network_repository.clone(),
599 Arc::new(tx_repo),
600 Arc::new(counter),
601 Arc::new(job_producer),
602 ),
603 )
604 .await
605 .unwrap();
606
607 let reasons = vec!["reason1".to_string(), "reason2".to_string()];
608 let result = relayer.disable_relayer(&reasons).await;
609 assert!(result.is_ok());
610 }
611
612 #[tokio::test]
613 async fn test_get_status_success_stellar() {
614 let ctx = TestCtx::default();
615 ctx.setup_network().await;
616 let relayer_model = ctx.relayer_model.clone();
617 let mut provider_mock = MockStellarProviderTrait::new();
618 let mut tx_repo_mock = MockTransactionRepository::new();
619 let relayer_repo_mock = MockRelayerRepository::new();
620 let job_producer_mock = MockJobProducerTrait::new();
621 let counter_mock = MockTransactionCounterServiceTrait::new();
622
623 provider_mock.expect_get_account().times(2).returning(|_| {
624 Box::pin(ready(Ok(AccountEntry {
625 account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32]))),
626 balance: 10000000,
627 seq_num: SequenceNumber(12345),
628 ext: AccountEntryExt::V0,
629 flags: 0,
630 home_domain: String32::default(),
631 inflation_dest: None,
632 num_sub_entries: 0,
633 signers: VecM::default(),
634 thresholds: Thresholds([0, 0, 0, 0]),
635 })))
636 });
637
638 tx_repo_mock
639 .expect_find_by_status()
640 .withf(|relayer_id, statuses| {
641 relayer_id == "test-relayer-id"
642 && statuses == [TransactionStatus::Pending, TransactionStatus::Submitted]
643 })
644 .returning(|_, _| Ok(vec![]) as Result<Vec<TransactionRepoModel>, RepositoryError>)
645 .once();
646
647 let confirmed_tx = TransactionRepoModel {
648 id: "tx1_stellar".to_string(),
649 relayer_id: relayer_model.id.clone(),
650 status: TransactionStatus::Confirmed,
651 confirmed_at: Some("2023-02-01T12:00:00Z".to_string()),
652 ..TransactionRepoModel::default()
653 };
654 tx_repo_mock
655 .expect_find_by_status()
656 .withf(|relayer_id, statuses| {
657 relayer_id == "test-relayer-id" && statuses == [TransactionStatus::Confirmed]
658 })
659 .returning(move |_, _| {
660 Ok(vec![confirmed_tx.clone()]) as Result<Vec<TransactionRepoModel>, RepositoryError>
661 })
662 .once();
663
664 let stellar_relayer = StellarRelayer::new(
665 relayer_model.clone(),
666 provider_mock,
667 StellarRelayerDependencies::new(
668 Arc::new(relayer_repo_mock),
669 ctx.network_repository.clone(),
670 Arc::new(tx_repo_mock),
671 Arc::new(counter_mock),
672 Arc::new(job_producer_mock),
673 ),
674 )
675 .await
676 .unwrap();
677
678 let status = stellar_relayer.get_status().await.unwrap();
679
680 match status {
681 RelayerStatus::Stellar {
682 balance,
683 pending_transactions_count,
684 last_confirmed_transaction_timestamp,
685 system_disabled,
686 paused,
687 sequence_number,
688 } => {
689 assert_eq!(balance, "10000000");
690 assert_eq!(pending_transactions_count, 0);
691 assert_eq!(
692 last_confirmed_transaction_timestamp,
693 Some("2023-02-01T12:00:00Z".to_string())
694 );
695 assert_eq!(system_disabled, relayer_model.system_disabled);
696 assert_eq!(paused, relayer_model.paused);
697 assert_eq!(sequence_number, "12345");
698 }
699 _ => panic!("Expected Stellar RelayerStatus"),
700 }
701 }
702
703 #[tokio::test]
704 async fn test_get_status_stellar_provider_error() {
705 let ctx = TestCtx::default();
706 ctx.setup_network().await;
707 let relayer_model = ctx.relayer_model.clone();
708 let mut provider_mock = MockStellarProviderTrait::new();
709 let tx_repo_mock = MockTransactionRepository::new();
710 let relayer_repo_mock = MockRelayerRepository::new();
711 let job_producer_mock = MockJobProducerTrait::new();
712 let counter_mock = MockTransactionCounterServiceTrait::new();
713
714 provider_mock
715 .expect_get_account()
716 .with(eq(relayer_model.address.clone()))
717 .returning(|_| Box::pin(async { Err(eyre!("Stellar provider down")) }));
718
719 let stellar_relayer = StellarRelayer::new(
720 relayer_model.clone(),
721 provider_mock,
722 StellarRelayerDependencies::new(
723 Arc::new(relayer_repo_mock),
724 ctx.network_repository.clone(),
725 Arc::new(tx_repo_mock),
726 Arc::new(counter_mock),
727 Arc::new(job_producer_mock),
728 ),
729 )
730 .await
731 .unwrap();
732
733 let result = stellar_relayer.get_status().await;
734 assert!(result.is_err());
735 match result.err().unwrap() {
736 RelayerError::ProviderError(msg) => {
737 assert!(msg.contains("Failed to get account details"))
738 }
739 _ => panic!("Expected ProviderError for get_account failure"),
740 }
741 }
742
743 #[tokio::test]
744 async fn test_get_balance_success() {
745 let ctx = TestCtx::default();
746 ctx.setup_network().await;
747 let relayer_model = ctx.relayer_model.clone();
748 let mut provider = MockStellarProviderTrait::new();
749 let expected_balance = 100_000_000i64; provider
752 .expect_get_account()
753 .with(eq(relayer_model.address.clone()))
754 .returning(move |_| {
755 Box::pin(async move {
756 Ok(AccountEntry {
757 account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32]))),
758 balance: expected_balance,
759 ext: AccountEntryExt::V0,
760 flags: 0,
761 home_domain: String32::default(),
762 inflation_dest: None,
763 seq_num: SequenceNumber(5),
764 num_sub_entries: 0,
765 signers: VecM::default(),
766 thresholds: Thresholds([0, 0, 0, 0]),
767 })
768 })
769 });
770
771 let relayer_repo = Arc::new(MockRelayerRepository::new());
772 let tx_repo = Arc::new(MockTransactionRepository::new());
773 let job_producer = Arc::new(MockJobProducerTrait::new());
774 let counter = Arc::new(MockTransactionCounterServiceTrait::new());
775
776 let relayer = StellarRelayer::new(
777 relayer_model,
778 provider,
779 StellarRelayerDependencies::new(
780 relayer_repo,
781 ctx.network_repository.clone(),
782 tx_repo,
783 counter,
784 job_producer,
785 ),
786 )
787 .await
788 .unwrap();
789
790 let result = relayer.get_balance().await;
791 assert!(result.is_ok());
792 let balance_response = result.unwrap();
793 assert_eq!(balance_response.balance, expected_balance as u128);
794 assert_eq!(balance_response.unit, STELLAR_SMALLEST_UNIT_NAME);
795 }
796
797 #[tokio::test]
798 async fn test_get_balance_provider_error() {
799 let ctx = TestCtx::default();
800 ctx.setup_network().await;
801 let relayer_model = ctx.relayer_model.clone();
802 let mut provider = MockStellarProviderTrait::new();
803
804 provider
805 .expect_get_account()
806 .with(eq(relayer_model.address.clone()))
807 .returning(|_| Box::pin(async { Err(eyre!("provider failed")) }));
808
809 let relayer_repo = Arc::new(MockRelayerRepository::new());
810 let tx_repo = Arc::new(MockTransactionRepository::new());
811 let job_producer = Arc::new(MockJobProducerTrait::new());
812 let counter = Arc::new(MockTransactionCounterServiceTrait::new());
813
814 let relayer = StellarRelayer::new(
815 relayer_model,
816 provider,
817 StellarRelayerDependencies::new(
818 relayer_repo,
819 ctx.network_repository.clone(),
820 tx_repo,
821 counter,
822 job_producer,
823 ),
824 )
825 .await
826 .unwrap();
827
828 let result = relayer.get_balance().await;
829 assert!(result.is_err());
830 match result.err().unwrap() {
831 RelayerError::ProviderError(msg) => {
832 assert!(msg.contains("Failed to fetch account for balance: provider failed"));
833 }
834 _ => panic!("Unexpected error type"),
835 }
836 }
837}