1use std::collections::{BTreeMap, HashSet};
2use std::convert::Infallible;
3use std::fmt::{self, Formatter};
4use std::future::{Future, pending};
5use std::ops::Range;
6use std::pin::Pin;
7use std::sync::Arc;
8use std::time::{Duration, SystemTime, UNIX_EPOCH};
9
10use async_stream::try_stream;
11use bitcoin::key::Secp256k1;
12use bitcoin::key::rand::thread_rng;
13use bitcoin::secp256k1::{self, PublicKey};
14use fedimint_api_client::api::global_api::with_request_hook::ApiRequestHook;
15use fedimint_api_client::api::{
16 ApiVersionSet, DynGlobalApi, FederationApiExt as _, FederationError, FederationResult,
17 IGlobalFederationApi,
18};
19use fedimint_bitcoind::DynBitcoindRpc;
20use fedimint_client_module::module::recovery::RecoveryProgress;
21use fedimint_client_module::module::{
22 ClientContextIface, ClientModule, ClientModuleRegistry, DynClientModule, FinalClientIface,
23 IClientModule, IdxRange, OutPointRange, PrimaryModulePriority,
24};
25use fedimint_client_module::oplog::IOperationLog;
26use fedimint_client_module::secret::{PlainRootSecretStrategy, RootSecretStrategy as _};
27use fedimint_client_module::sm::executor::{ActiveStateKey, IExecutor, InactiveStateKey};
28use fedimint_client_module::sm::{ActiveStateMeta, DynState, InactiveStateMeta};
29use fedimint_client_module::transaction::{
30 FeeQuote, FeeQuoteRequest, TRANSACTION_SUBMISSION_MODULE_INSTANCE, TransactionBuilder,
31 TxSubmissionStates, TxSubmissionStatesSM,
32};
33use fedimint_client_module::{
34 AddStateMachinesResult, ClientModuleInstance, GetInviteCodeRequest, ModuleGlobalContextGen,
35 ModuleRecoveryCompleted, TransactionUpdates, TxCreatedEvent,
36};
37use fedimint_connectors::{ConnectorRegistry, PeerStatus};
38use fedimint_core::config::{
39 ClientConfig, FederationId, GlobalClientConfig, JsonClientConfig, ModuleInitRegistry,
40};
41use fedimint_core::core::{DynInput, DynOutput, ModuleInstanceId, ModuleKind, OperationId};
42use fedimint_core::db::{
43 AutocommitError, Database, DatabaseRecord, DatabaseTransaction, DbMigrationError,
44 IDatabaseTransactionOpsCore as _, IDatabaseTransactionOpsCoreTyped as _, NonCommittable,
45};
46use fedimint_core::encoding::{Decodable, Encodable};
47use fedimint_core::endpoint_constants::{CLIENT_CONFIG_ENDPOINT, VERSION_ENDPOINT};
48use fedimint_core::envs::is_running_in_test_env;
49use fedimint_core::invite_code::InviteCode;
50use fedimint_core::module::registry::{ModuleDecoderRegistry, ModuleRegistry};
51use fedimint_core::module::{
52 AmountUnit, Amounts, ApiRequestErased, ApiVersion, MultiApiVersion,
53 SupportedApiVersionsSummary, SupportedCoreApiVersions, SupportedModuleApiVersions,
54};
55use fedimint_core::net::api_announcement::SignedApiAnnouncement;
56use fedimint_core::peer_availability::{MergedPeerAvailability, merge_peer_availability};
57use fedimint_core::runtime::sleep;
58use fedimint_core::task::{
59 Elapsed, MaybeSend, MaybeSync, ShuttingDownError, TaskGroup, TaskHandle,
60};
61use fedimint_core::transaction::Transaction;
62use fedimint_core::util::backoff_util::custom_backoff;
63use fedimint_core::util::{BoxStream, FmtCompact as _, SafeUrl, backoff_util, retry};
64use fedimint_core::{
65 Amount, ChainId, NumPeers, OutPoint, PeerId, apply, async_trait_maybe_send, maybe_add_send,
66 maybe_add_send_sync, runtime,
67};
68use fedimint_derive_secret::DerivableSecret;
69use fedimint_eventlog::{
70 DBTransactionEventLogExt as _, DynEventLogTrimableTracker, Event, EventHandlerError, EventKind,
71 EventLogEntry, EventLogId, EventLogTrackerError, EventLogTrimableId, EventLogTrimableTracker,
72 EventPersistence, PersistedLogEntry,
73};
74use fedimint_logging::{LOG_CLIENT, LOG_CLIENT_NET_API, LOG_CLIENT_RECOVERY};
75use futures::stream::FuturesUnordered;
76use futures::{Stream, StreamExt as _};
77use global_ctx::ModuleGlobalClientContext;
78use serde::{Deserialize, Serialize};
79use tokio::sync::{broadcast, oneshot, watch};
80use tokio_stream::wrappers::WatchStream;
81use tracing::{Span, debug, info, warn};
82
83use crate::ClientBuilder;
84use crate::api_announcements::{ApiAnnouncementPrefix, get_api_urls};
85use crate::backup::Metadata;
86use crate::client::event_log::DefaultApplicationEventLogKey;
87use crate::db::{
88 ApiSecretKey, CachedApiVersionSet, CachedApiVersionSetKey, ChainIdKey,
89 ChronologicalOperationLogKey, ClientConfigKey, ClientMetadataKey, ClientModuleRecovery,
90 ClientModuleRecoveryState, EncodedClientSecretKey, OperationLogKey, PeerLastApiVersionsSummary,
91 PeerLastApiVersionsSummaryKey, PendingClientConfigKey, TransactionFeesKey,
92 apply_migrations_core_client_dbtx, verify_client_db_integrity_dbtx,
93};
94use crate::error::{
95 ApiVersionDiscoveryError, ClientModuleError, ClientSecretError, GlobalRpcError,
96 ModuleLookupError, OperationAlreadyExistsError, OperationNotFoundError, RecoveryError,
97 TransactionSubmitError,
98};
99use crate::meta::MetaService;
100use crate::module_init::{ClientModuleInitRegistry, DynClientModuleInit, IClientModuleInit};
101use crate::oplog::OperationLog;
102use crate::sm::executor::{
103 ActiveModuleOperationStateKeyPrefix, ActiveOperationStateKeyPrefix, Executor,
104 InactiveModuleOperationStateKeyPrefix, InactiveOperationStateKeyPrefix,
105};
106
107pub(crate) mod builder;
108pub(crate) mod event_log;
109pub(crate) mod global_ctx;
110pub(crate) mod handle;
111
112#[cfg(test)]
113mod tests;
114
115const SUPPORTED_CORE_API_VERSIONS: &[fedimint_core::module::ApiVersion] =
119 &[ApiVersion { major: 0, minor: 0 }];
120
121struct FinalizedTransaction {
122 transaction: Transaction,
123 states: Vec<DynState>,
124 change_range: Range<u64>,
125 fees: Amounts,
126}
127
128#[derive(Default)]
130pub(crate) struct PrimaryModuleCandidates {
131 specific: BTreeMap<AmountUnit, Vec<ModuleInstanceId>>,
133 wildcard: Vec<ModuleInstanceId>,
135}
136
137pub(crate) type ModuleRecoveryFuture =
140 Pin<Box<maybe_add_send!(dyn Future<Output = Result<Option<Amount>, ClientModuleError>>)>>;
141
142#[derive(Clone, Debug)]
167pub(crate) enum RecoveryStatus {
168 InProgress(RecoveryProgress),
172 Failed {
176 last_progress: RecoveryProgress,
177 error: Arc<ClientModuleError>,
178 },
179}
180
181impl RecoveryStatus {
182 pub(crate) fn is_successfully_done(&self) -> bool {
187 match self {
188 Self::InProgress(progress) => progress.is_done(),
189 Self::Failed { .. } => false,
190 }
191 }
192
193 pub(crate) fn progress(&self) -> RecoveryProgress {
196 match self {
197 Self::InProgress(progress)
198 | Self::Failed {
199 last_progress: progress,
200 ..
201 } => *progress,
202 }
203 }
204}
205
206pub struct Client {
220 final_client: FinalClientIface,
221 config: tokio::sync::RwLock<ClientConfig>,
222 api_secret: Option<String>,
223 decoders: ModuleDecoderRegistry,
224 connectors: ConnectorRegistry,
225 db: Database,
226 federation_id: FederationId,
227 federation_config_meta: BTreeMap<String, String>,
228 primary_modules: BTreeMap<PrimaryModulePriority, PrimaryModuleCandidates>,
229 pub(crate) modules: ClientModuleRegistry,
230 module_inits: ClientModuleInitRegistry,
231 executor: Executor,
232 pub(crate) api: DynGlobalApi,
233 root_secret: DerivableSecret,
234 operation_log: OperationLog,
235 secp_ctx: Secp256k1<secp256k1::All>,
236 meta_service: Arc<MetaService>,
237
238 task_group: TaskGroup,
239
240 client_span: Span,
244
245 client_recovery_status_receiver: watch::Receiver<BTreeMap<ModuleInstanceId, RecoveryStatus>>,
258
259 log_ordering_wakeup_tx: watch::Sender<()>,
262 log_event_added_rx: watch::Receiver<()>,
264 log_event_added_transient_tx: broadcast::Sender<EventLogEntry>,
265 request_hook: ApiRequestHook,
266 iroh_enable_dht: bool,
267 iroh_enable_next: bool,
268 #[allow(dead_code)]
273 user_bitcoind_rpc: Option<DynBitcoindRpc>,
274 pub(crate) user_bitcoind_rpc_no_chain_id:
279 Option<fedimint_client_module::module::init::BitcoindRpcNoChainIdFactory>,
280}
281
282#[derive(Debug, Serialize, Deserialize)]
283struct ListOperationsParams {
284 limit: Option<usize>,
285 last_seen: Option<ChronologicalOperationLogKey>,
286}
287
288pub const DEFAULT_EVENT_LOG_PAGE_SIZE: u64 = 100;
289pub const MAX_EVENT_LOG_PAGE_SIZE: u64 = 10_000;
290
291#[derive(Debug, Clone, Serialize, Deserialize)]
292struct GetEventLogRequest {
293 pos: Option<EventLogId>,
294 limit: Option<u64>,
295}
296
297#[derive(Debug, Clone, Serialize, Deserialize)]
298pub struct GetOperationIdRequest {
299 operation_id: OperationId,
300}
301
302#[derive(Debug, Clone, Serialize, Deserialize)]
303pub struct GetBalanceChangesRequest {
304 #[serde(default = "AmountUnit::bitcoin")]
305 unit: AmountUnit,
306}
307
308impl Client {
309 pub async fn builder() -> ClientBuilder {
314 ClientBuilder::new()
315 }
316
317 pub fn api(&self) -> &(dyn IGlobalFederationApi + 'static) {
318 self.api.as_ref()
319 }
320
321 pub fn api_clone(&self) -> DynGlobalApi {
322 self.api.clone()
323 }
324
325 pub fn connection_status_stream(&self) -> impl Stream<Item = BTreeMap<PeerId, PeerStatus>> {
328 self.api.connection_status_stream()
329 }
330
331 pub fn federation_reconnect(&self) {
339 let peers: Vec<PeerId> = self.api.all_peers().iter().copied().collect();
340
341 for peer_id in peers {
342 let api = self.api.clone();
343 self.spawn_cancellable(format!("federation-reconnect-once-{peer_id}"), async move {
344 if let Err(e) = api.get_peer_connection(peer_id).await {
345 debug!(
346 target: LOG_CLIENT_NET_API,
347 %peer_id,
348 err = %e.fmt_compact(),
349 "Failed to connect to peer"
350 );
351 }
352 });
353 }
354 }
355
356 pub fn spawn_federation_reconnect(&self) {
378 let peers: Vec<PeerId> = self.api.all_peers().iter().copied().collect();
379
380 for peer_id in peers {
381 let api = self.api.clone();
382 self.spawn_cancellable(format!("federation-reconnect-{peer_id}"), async move {
383 loop {
384 match api.get_peer_connection(peer_id).await {
385 Ok(conn) => {
386 conn.await_disconnection().await;
387 }
388 Err(e) => {
389 debug!(
392 target: LOG_CLIENT_NET_API,
393 %peer_id,
394 err = %e.fmt_compact(),
395 "Failed to connect to peer, will retry"
396 );
397 }
398 }
399 }
400 });
401 }
402 }
403
404 pub fn task_group(&self) -> &TaskGroup {
406 &self.task_group
407 }
408
409 pub(crate) fn make_client_span(federation_id: FederationId) -> Span {
416 tracing::info_span!(
417 target: LOG_CLIENT,
418 parent: None,
419 "client",
420 fed_id = %federation_id.to_prefix(),
421 )
422 }
423
424 pub(crate) fn spawn_cancellable<R>(
427 &self,
428 name: impl Into<String>,
429 future: impl Future<Output = R> + MaybeSend + 'static,
430 ) -> oneshot::Receiver<Result<R, ShuttingDownError>>
431 where
432 R: MaybeSend + 'static,
433 {
434 self.task_group
435 .spawn_cancellable_with_span(self.client_span.clone(), name, future)
436 }
437
438 pub(crate) fn spawn<Fut, R>(
442 &self,
443 name: impl Into<String>,
444 f: impl FnOnce(TaskHandle) -> Fut + MaybeSend + 'static,
445 ) -> oneshot::Receiver<R>
446 where
447 Fut: Future<Output = R> + MaybeSend + 'static,
448 R: MaybeSend + 'static,
449 {
450 self.task_group
451 .spawn_with_span(self.client_span.clone(), name, f)
452 }
453
454 pub fn get_metrics() -> Result<String, fedimint_metrics::prometheus::Error> {
459 fedimint_metrics::get_metrics()
460 }
461
462 #[doc(hidden)]
464 pub fn executor(&self) -> &Executor {
465 &self.executor
466 }
467
468 pub async fn get_config_from_db(db: &Database) -> Option<ClientConfig> {
469 let mut dbtx = db.begin_transaction_nc().await;
470 dbtx.get_value(&ClientConfigKey).await
471 }
472
473 pub async fn get_pending_config_from_db(db: &Database) -> Option<ClientConfig> {
474 let mut dbtx = db.begin_transaction_nc().await;
475 dbtx.get_value(&PendingClientConfigKey).await
476 }
477
478 pub async fn get_api_secret_from_db(db: &Database) -> Option<String> {
479 let mut dbtx = db.begin_transaction_nc().await;
480 dbtx.get_value(&ApiSecretKey).await
481 }
482
483 pub async fn store_encodable_client_secret<T: Encodable>(
484 db: &Database,
485 secret: T,
486 ) -> Result<(), ClientSecretError> {
487 let mut dbtx = db.begin_transaction().await;
488
489 if dbtx.get_value(&EncodedClientSecretKey).await.is_some() {
491 return Err(ClientSecretError::AlreadyExists);
492 }
493
494 let encoded_secret = T::consensus_encode_to_vec(&secret);
495 dbtx.insert_entry(&EncodedClientSecretKey, &encoded_secret)
496 .await;
497 dbtx.commit_tx().await;
498 Ok(())
499 }
500
501 pub async fn load_decodable_client_secret<T: Decodable>(
502 db: &Database,
503 ) -> Result<T, ClientSecretError> {
504 let Some(secret) = Self::load_decodable_client_secret_opt(db).await? else {
505 return Err(ClientSecretError::NotPresent);
506 };
507
508 Ok(secret)
509 }
510
511 pub async fn load_decodable_client_secret_opt<T: Decodable>(
512 db: &Database,
513 ) -> Result<Option<T>, ClientSecretError> {
514 let mut dbtx = db.begin_transaction_nc().await;
515
516 let client_secret = dbtx.get_value(&EncodedClientSecretKey).await;
517
518 Ok(match client_secret {
519 Some(client_secret) => Some(T::consensus_decode_whole(
520 &client_secret,
521 &ModuleRegistry::default(),
522 )?),
523 None => None,
524 })
525 }
526
527 pub async fn load_or_generate_client_secret(db: &Database) -> [u8; 64] {
528 match Self::load_decodable_client_secret::<[u8; 64]>(db).await {
529 Ok(secret) => secret,
530 _ => {
531 let secret = PlainRootSecretStrategy::random(&mut thread_rng());
532 Self::store_encodable_client_secret(db, secret)
533 .await
534 .expect("Storing client secret must work");
535 secret
536 }
537 }
538 }
539
540 pub async fn is_initialized(db: &Database) -> bool {
541 let mut dbtx = db.begin_transaction_nc().await;
542 dbtx.raw_get_bytes(&[ClientConfigKey::DB_PREFIX])
543 .await
544 .expect("Unrecoverable error occurred while reading and entry from the database")
545 .is_some()
546 }
547
548 pub fn start_executor(self: &Arc<Self>) {
549 self.client_span.in_scope(|| {
550 debug!(
551 target: LOG_CLIENT,
552 "Starting fedimint client executor",
553 );
554 });
555 self.executor
556 .start_executor(self.context_gen(), self.client_span.clone());
557 }
558
559 pub fn federation_id(&self) -> FederationId {
560 self.federation_id
561 }
562
563 fn context_gen(self: &Arc<Self>) -> ModuleGlobalContextGen {
564 let client_inner = Arc::downgrade(self);
565 Arc::new(move |module_instance, operation| {
566 ModuleGlobalClientContext {
567 client: client_inner
568 .clone()
569 .upgrade()
570 .expect("ModuleGlobalContextGen called after client was dropped"),
571 module_instance_id: module_instance,
572 operation,
573 }
574 .into()
575 })
576 }
577
578 pub async fn config(&self) -> ClientConfig {
579 self.config.read().await.clone()
580 }
581
582 pub fn api_secret(&self) -> &Option<String> {
584 &self.api_secret
585 }
586
587 pub async fn core_api_version(&self) -> ApiVersion {
593 self.db
596 .begin_transaction_nc()
597 .await
598 .get_value(&CachedApiVersionSetKey)
599 .await
600 .map(|cached: CachedApiVersionSet| cached.0.core)
601 .unwrap_or(ApiVersion { major: 0, minor: 0 })
602 }
603
604 pub async fn chain_id(&self) -> Result<ChainId, FederationError> {
611 if let Some(chain_id) = self
613 .db
614 .begin_transaction_nc()
615 .await
616 .get_value(&ChainIdKey)
617 .await
618 {
619 return Ok(chain_id);
620 }
621
622 let chain_id = self.api.chain_id().await?;
624
625 let mut dbtx = self.db.begin_transaction().await;
627 dbtx.insert_entry(&ChainIdKey, &chain_id).await;
628 dbtx.commit_tx().await;
629
630 Ok(chain_id)
631 }
632
633 pub fn decoders(&self) -> &ModuleDecoderRegistry {
634 &self.decoders
635 }
636
637 fn get_module(&self, instance: ModuleInstanceId) -> &maybe_add_send_sync!(dyn IClientModule) {
639 self.try_get_module(instance)
640 .expect("Module instance not found")
641 }
642
643 fn try_get_module(
644 &self,
645 instance: ModuleInstanceId,
646 ) -> Option<&maybe_add_send_sync!(dyn IClientModule)> {
647 Some(self.modules.get(instance)?.as_ref())
648 }
649
650 pub fn has_module(&self, instance: ModuleInstanceId) -> bool {
651 self.modules.get(instance).is_some()
652 }
653
654 fn transaction_builder_get_balance(
660 &self,
661 builder: &TransactionBuilder,
662 ) -> Result<(Amounts, Amounts), TransactionSubmitError> {
663 let inputs = builder.inputs().map(|input| {
664 let module = self.get_module(input.input.module_instance_id());
665
666 let item_fees = module.input_fee(&input.amounts, &input.input).expect(
667 "We only build transactions with input versions that are supported by the module",
668 );
669
670 (&input.amounts, item_fees)
671 });
672
673 let outputs = builder.outputs().map(|output| {
674 let module = self.get_module(output.output.module_instance_id());
675
676 let item_fees = module.output_fee(&output.amounts, &output.output).expect(
677 "We only build transactions with output versions that are supported by the module",
678 );
679
680 (&output.amounts, item_fees)
681 });
682
683 accumulate_transaction_balance(inputs, outputs)
684 }
685}
686
687fn accumulate_transaction_balance<'a>(
690 inputs: impl IntoIterator<Item = (&'a Amounts, Amounts)>,
691 outputs: impl IntoIterator<Item = (&'a Amounts, Amounts)>,
692) -> Result<(Amounts, Amounts), TransactionSubmitError> {
693 let mut in_amounts = Amounts::ZERO;
694 let mut out_amounts = Amounts::ZERO;
695 let mut fee_amounts = Amounts::ZERO;
696
697 for (amount, item_fees) in inputs {
698 in_amounts
699 .checked_add_mut(amount)
700 .ok_or(TransactionSubmitError::AmountOverflow)?;
701 fee_amounts
702 .checked_add_mut(&item_fees)
703 .ok_or(TransactionSubmitError::AmountOverflow)?;
704 }
705
706 for (amount, item_fees) in outputs {
707 out_amounts
708 .checked_add_mut(amount)
709 .ok_or(TransactionSubmitError::AmountOverflow)?;
710 fee_amounts
711 .checked_add_mut(&item_fees)
712 .ok_or(TransactionSubmitError::AmountOverflow)?;
713 }
714
715 out_amounts
716 .checked_add_mut(&fee_amounts)
717 .ok_or(TransactionSubmitError::AmountOverflow)?;
718 Ok((in_amounts, out_amounts))
719}
720
721impl Client {
722 pub fn get_internal_payment_markers(
723 &self,
724 ) -> Result<(PublicKey, u64), bitcoin::secp256k1::Error> {
725 Ok((self.federation_id().to_fake_ln_pub_key(&self.secp_ctx)?, 0))
726 }
727
728 pub fn get_config_meta(&self, key: &str) -> Option<String> {
730 self.federation_config_meta.get(key).cloned()
731 }
732
733 pub(crate) fn root_secret(&self) -> DerivableSecret {
734 self.root_secret.clone()
735 }
736
737 pub async fn add_state_machines(
738 &self,
739 dbtx: &mut DatabaseTransaction<'_>,
740 states: Vec<DynState>,
741 ) -> AddStateMachinesResult {
742 self.executor.add_state_machines_dbtx(dbtx, states).await
743 }
744
745 pub async fn get_active_operations(&self) -> HashSet<OperationId> {
747 let active_states = self.executor.get_active_states().await;
748 let mut active_operations = HashSet::with_capacity(active_states.len());
749 let mut dbtx = self.db().begin_transaction_nc().await;
750 for (state, _) in active_states {
751 let operation_id = state.operation_id();
752 if dbtx
753 .get_value(&OperationLogKey { operation_id })
754 .await
755 .is_some()
756 {
757 active_operations.insert(operation_id);
758 }
759 }
760 active_operations
761 }
762
763 pub fn operation_log(&self) -> &OperationLog {
764 &self.operation_log
765 }
766
767 pub fn meta_service(&self) -> &Arc<MetaService> {
769 &self.meta_service
770 }
771
772 pub async fn get_meta_expiration_timestamp(&self) -> Option<SystemTime> {
774 let meta_service = self.meta_service();
775 let ts = meta_service
776 .get_field::<u64>(self.db(), "federation_expiry_timestamp")
777 .await
778 .and_then(|v| v.value)?;
779 Some(UNIX_EPOCH + Duration::from_secs(ts))
780 }
781
782 async fn finalize_transaction(
784 &self,
785 dbtx: &mut DatabaseTransaction<'_>,
786 operation_id: OperationId,
787 mut partial_transaction: TransactionBuilder,
788 ) -> Result<FinalizedTransaction, TransactionSubmitError> {
789 let (in_amounts, out_amounts) =
790 self.transaction_builder_get_balance(&partial_transaction)?;
791
792 let mut added_inputs_bundles = vec![];
793 let mut added_outputs_bundles = vec![];
794
795 for unit in in_amounts.units().union(&out_amounts.units()) {
806 let input_amount = in_amounts.get(unit).copied().unwrap_or_default();
807 let output_amount = out_amounts.get(unit).copied().unwrap_or_default();
808 if input_amount == output_amount {
809 continue;
810 }
811
812 let Some((module_id, module)) = self.primary_module_for_unit(*unit) else {
813 return Err(TransactionSubmitError::NoPrimaryModule { unit: *unit });
814 };
815
816 let (added_input_bundle, added_output_bundle) = module
817 .create_final_inputs_and_outputs(
818 module_id,
819 dbtx,
820 operation_id,
821 *unit,
822 input_amount,
823 output_amount,
824 )
825 .await?;
826
827 added_inputs_bundles.push(added_input_bundle);
828 added_outputs_bundles.push(added_output_bundle);
829 }
830
831 let change_range = Range {
835 start: partial_transaction.outputs().count() as u64,
836 end: (partial_transaction.outputs().count() as u64
837 + added_outputs_bundles
838 .iter()
839 .map(|output| output.outputs().len() as u64)
840 .sum::<u64>()),
841 };
842
843 for added_inputs in added_inputs_bundles {
844 partial_transaction = partial_transaction.with_inputs(added_inputs);
845 }
846
847 for added_outputs in added_outputs_bundles {
848 partial_transaction = partial_transaction.with_outputs(added_outputs);
849 }
850
851 let (input_amounts, output_amounts) =
852 self.transaction_builder_get_balance(&partial_transaction)?;
853
854 for (unit, output_amount) in output_amounts {
855 let input_amount = input_amounts.get(&unit).copied().unwrap_or_default();
856
857 assert!(input_amount >= output_amount, "Transaction is underfunded");
858 }
859
860 let fees = {
864 let mut input_total = Amounts::ZERO;
865 for input in partial_transaction.inputs() {
866 input_total
867 .checked_add_mut(&input.amounts)
868 .expect("Own transaction amounts don't overflow");
869 }
870 let mut output_total = Amounts::ZERO;
871 for output in partial_transaction.outputs() {
872 output_total
873 .checked_add_mut(&output.amounts)
874 .expect("Own transaction amounts don't overflow");
875 }
876 input_total
877 .checked_sub(&output_total)
878 .expect("Inputs >= outputs for own transactions")
879 };
880
881 let (transaction, states) = partial_transaction.build(&self.secp_ctx, thread_rng());
882
883 Ok(FinalizedTransaction {
884 transaction,
885 states,
886 change_range,
887 fees,
888 })
889 }
890
891 pub async fn fee_quote(
911 &self,
912 operation_id: OperationId,
913 request: FeeQuoteRequest,
914 ) -> Result<FeeQuote, TransactionSubmitError> {
915 let FeeQuoteRequest {
916 input_amount,
917 output_amount,
918 input_fee,
919 output_fee,
920 } = request;
921
922 let mut gross_input = input_amount.clone();
926 let mut gross_output = output_amount.clone();
927 let mut input_fees = input_fee.clone();
928 let mut output_fees = output_fee.clone();
929
930 let balance_input = input_amount;
936 let balance_output = output_amount
937 .checked_add(&input_fee)
938 .and_then(|amounts| amounts.checked_add(&output_fee))
939 .expect("explicit amounts and fees cannot overflow an Amounts");
940
941 let mut dbtx = self.db.begin_transaction_nc().await;
945
946 for unit in balance_input.units().union(&balance_output.units()) {
949 let balance_input_amount = balance_input.get(unit).copied().unwrap_or_default();
950 let balance_output_amount = balance_output.get(unit).copied().unwrap_or_default();
951 if balance_input_amount == balance_output_amount {
952 continue;
953 }
954
955 let Some((module_id, module)) = self.primary_module_for_unit(*unit) else {
956 return Err(TransactionSubmitError::NoPrimaryModule { unit: *unit });
957 };
958
959 let (change_input, change_output) = module
960 .create_final_inputs_and_outputs(
961 module_id,
962 &mut dbtx.to_ref_nc(),
963 operation_id,
964 *unit,
965 balance_input_amount,
966 balance_output_amount,
967 )
968 .await?;
969
970 for input in change_input.inputs() {
976 let module = self.get_module(input.input.module_instance_id());
977 let fee = module
978 .input_fee(&input.amounts, &input.input)
979 .expect("Primary module must know its own change input fees");
980 gross_input.checked_add_mut(&input.amounts);
981 input_fees.checked_add_mut(&fee);
982 }
983
984 for output in change_output.outputs() {
985 let module = self.get_module(output.output.module_instance_id());
986 let fee = module
987 .output_fee(&output.amounts, &output.output)
988 .expect("Primary module must know its own change output fees");
989 gross_output.checked_add_mut(&output.amounts);
990 output_fees.checked_add_mut(&fee);
991 }
992 }
993
994 dbtx.ignore_uncommitted();
997
998 let mut dust = Amounts::ZERO;
1003 for unit in gross_input.units().union(&gross_output.units()) {
1004 let total = gross_input
1005 .get(unit)
1006 .copied()
1007 .unwrap_or_default()
1008 .saturating_sub(gross_output.get(unit).copied().unwrap_or_default());
1009 let fees = input_fees.get(unit).copied().unwrap_or_default()
1010 + output_fees.get(unit).copied().unwrap_or_default();
1011 dust = dust
1012 .checked_add_unit(total.saturating_sub(fees), *unit)
1013 .expect("dust cannot overflow an Amounts");
1014 }
1015
1016 Ok(FeeQuote {
1017 input: input_fees,
1018 output: output_fees,
1019 dust,
1020 })
1021 }
1022
1023 pub async fn finalize_and_submit_transaction<F, M>(
1059 &self,
1060 operation_id: OperationId,
1061 operation_type: &str,
1062 operation_meta_gen: F,
1063 tx_builder: TransactionBuilder,
1064 ) -> Result<OutPointRange, TransactionSubmitError>
1065 where
1066 F: Fn(OutPointRange) -> M + Clone + MaybeSend + MaybeSync,
1067 M: serde::Serialize + MaybeSend,
1068 {
1069 let operation_type = operation_type.to_owned();
1070
1071 let autocommit_res = self
1072 .db
1073 .autocommit(
1074 |dbtx, _| {
1075 let operation_type = operation_type.clone();
1076 let tx_builder = tx_builder.clone();
1077 let operation_meta_gen = operation_meta_gen.clone();
1078 Box::pin(async move {
1079 self.finalize_and_submit_transaction_dbtx(
1080 dbtx,
1081 operation_id,
1082 &operation_type,
1083 operation_meta_gen,
1084 tx_builder,
1085 )
1086 .await
1087 })
1088 },
1089 Some(100), )
1091 .await;
1092
1093 match autocommit_res {
1094 Ok(txid) => Ok(txid),
1095 Err(AutocommitError::ClosureError { error, .. }) => Err(error),
1096 Err(AutocommitError::CommitFailed { last_error, .. }) => {
1097 Err(TransactionSubmitError::Database(last_error))
1098 }
1099 }
1100 }
1101
1102 pub async fn finalize_and_submit_transaction_dbtx<F, M>(
1111 &self,
1112 dbtx: &mut DatabaseTransaction<'_>,
1113 operation_id: OperationId,
1114 operation_type: &str,
1115 operation_meta_gen: F,
1116 tx_builder: TransactionBuilder,
1117 ) -> Result<OutPointRange, TransactionSubmitError>
1118 where
1119 F: FnOnce(OutPointRange) -> M + MaybeSend,
1120 M: serde::Serialize + MaybeSend,
1121 {
1122 if Client::operation_exists_dbtx(dbtx, operation_id).await {
1123 return Err(OperationAlreadyExistsError { operation_id }.into());
1124 }
1125
1126 let out_point_range = self
1127 .finalize_and_submit_transaction_inner(dbtx, operation_id, tx_builder)
1128 .await?;
1129
1130 self.operation_log()
1131 .add_operation_log_entry_dbtx(
1132 dbtx,
1133 operation_id,
1134 operation_type,
1135 operation_meta_gen(out_point_range),
1136 )
1137 .await;
1138
1139 Ok(out_point_range)
1140 }
1141
1142 async fn finalize_and_submit_transaction_inner(
1143 &self,
1144 dbtx: &mut DatabaseTransaction<'_>,
1145 operation_id: OperationId,
1146 tx_builder: TransactionBuilder,
1147 ) -> Result<OutPointRange, TransactionSubmitError> {
1148 let FinalizedTransaction {
1149 transaction,
1150 mut states,
1151 change_range,
1152 fees,
1153 } = self
1154 .finalize_transaction(&mut dbtx.to_ref_nc(), operation_id, tx_builder)
1155 .await?;
1156
1157 if transaction.consensus_encode_to_vec().len() > Transaction::MAX_TX_SIZE {
1158 let inputs = transaction
1159 .inputs
1160 .iter()
1161 .map(DynInput::module_instance_id)
1162 .collect::<Vec<_>>();
1163 let outputs = transaction
1164 .outputs
1165 .iter()
1166 .map(DynOutput::module_instance_id)
1167 .collect::<Vec<_>>();
1168 warn!(
1169 target: LOG_CLIENT_NET_API,
1170 size=%transaction.consensus_encode_to_vec().len(),
1171 ?inputs,
1172 ?outputs,
1173 "Transaction too large",
1174 );
1175 debug!(target: LOG_CLIENT_NET_API, ?transaction, "transaction details");
1176 return Err(TransactionSubmitError::TransactionTooLarge {
1177 size: transaction.consensus_encode_to_vec().len(),
1178 max: Transaction::MAX_TX_SIZE,
1179 });
1180 }
1181
1182 let txid = transaction.tx_hash();
1183
1184 debug!(
1185 target: LOG_CLIENT_NET_API,
1186 %txid,
1187 operation_id = %operation_id.fmt_short(),
1188 ?transaction,
1189 "Finalized and submitting transaction",
1190 );
1191
1192 let tx_submission_sm = DynState::from_typed(
1193 TRANSACTION_SUBMISSION_MODULE_INSTANCE,
1194 TxSubmissionStatesSM {
1195 operation_id,
1196 state: TxSubmissionStates::Created(transaction),
1197 },
1198 );
1199 states.push(tx_submission_sm);
1200
1201 self.executor.add_state_machines_dbtx(dbtx, states).await?;
1202
1203 dbtx.insert_new_entry(&TransactionFeesKey(txid), &fees)
1204 .await;
1205
1206 self.log_event_dbtx(dbtx, None, TxCreatedEvent { txid, operation_id })
1207 .await;
1208
1209 Ok(OutPointRange::new(txid, IdxRange::from(change_range)))
1210 }
1211
1212 async fn transaction_update_stream(
1213 &self,
1214 operation_id: OperationId,
1215 ) -> BoxStream<'static, TxSubmissionStatesSM> {
1216 self.executor
1217 .notifier()
1218 .module_notifier::<TxSubmissionStatesSM>(
1219 TRANSACTION_SUBMISSION_MODULE_INSTANCE,
1220 self.final_client.clone(),
1221 )
1222 .subscribe(operation_id)
1223 .await
1224 }
1225
1226 pub async fn operation_exists(&self, operation_id: OperationId) -> bool {
1227 let mut dbtx = self.db().begin_transaction_nc().await;
1228
1229 Client::operation_exists_dbtx(&mut dbtx, operation_id).await
1230 }
1231
1232 pub async fn operation_exists_dbtx(
1233 dbtx: &mut DatabaseTransaction<'_>,
1234 operation_id: OperationId,
1235 ) -> bool {
1236 let active_state_exists = dbtx
1237 .find_by_prefix(&ActiveOperationStateKeyPrefix { operation_id })
1238 .await
1239 .next()
1240 .await
1241 .is_some();
1242
1243 let inactive_state_exists = dbtx
1244 .find_by_prefix(&InactiveOperationStateKeyPrefix { operation_id })
1245 .await
1246 .next()
1247 .await
1248 .is_some();
1249
1250 active_state_exists || inactive_state_exists
1251 }
1252
1253 pub async fn has_active_states(&self, operation_id: OperationId) -> bool {
1255 self.executor.has_active_states(operation_id).await
1256 }
1257
1258 pub async fn await_no_active_states(&self, operation_id: OperationId) {
1278 self.executor.await_no_active_states(operation_id).await;
1279 }
1280
1281 pub async fn get_operation_fees(
1300 &self,
1301 operation_id: OperationId,
1302 ) -> Result<Option<Amounts>, OperationNotFoundError> {
1303 if !self.operation_exists(operation_id).await {
1304 return Err(OperationNotFoundError { operation_id });
1305 }
1306
1307 let (active_states, inactive_states) =
1308 self.executor().get_operation_states(operation_id).await;
1309
1310 let states = active_states
1311 .into_iter()
1312 .map(|(state, _)| state)
1313 .chain(inactive_states.into_iter().map(|(state, _)| state));
1314
1315 let accepted_transactions = states
1316 .filter_map(|state| {
1317 let tx_state = state.as_any().downcast_ref::<TxSubmissionStatesSM>()?;
1318
1319 match &tx_state.state {
1320 TxSubmissionStates::Accepted(transaction_id) => Some(*transaction_id),
1321 _ => None,
1322 }
1323 })
1324 .collect::<HashSet<_>>();
1325
1326 let mut dbtx = self.db.begin_transaction_nc().await;
1328 let mut total_fees = Amounts::ZERO;
1329 for txid in &accepted_transactions {
1330 let Some(fees) = dbtx.get_value(&TransactionFeesKey(*txid)).await else {
1331 return Ok(None);
1332 };
1333 total_fees = total_fees
1334 .checked_add(&fees)
1335 .expect("Fee amounts don't overflow in practice");
1336 }
1337
1338 Ok(Some(total_fees))
1339 }
1340
1341 pub async fn await_primary_bitcoin_module_output(
1344 &self,
1345 operation_id: OperationId,
1346 out_point: OutPoint,
1347 ) -> Result<(), TransactionSubmitError> {
1348 self.primary_module_for_unit(AmountUnit::BITCOIN)
1349 .ok_or(TransactionSubmitError::NoPrimaryModule {
1350 unit: AmountUnit::BITCOIN,
1351 })?
1352 .1
1353 .await_primary_module_output(operation_id, out_point)
1354 .await
1355 .map_err(TransactionSubmitError::PrimaryModule)
1356 }
1357
1358 pub fn get_first_module<M: ClientModule>(
1360 &'_ self,
1361 ) -> Result<ClientModuleInstance<'_, M>, ModuleLookupError> {
1362 let module_kind = M::kind();
1363 let id = self.get_first_instance(&module_kind).ok_or_else(|| {
1364 ModuleLookupError::NoModuleOfKind {
1365 kind: module_kind.clone(),
1366 }
1367 })?;
1368 let module: &M = self
1369 .try_get_module(id)
1370 .ok_or(ModuleLookupError::UnknownInstance { instance_id: id })?
1371 .as_any()
1372 .downcast_ref::<M>()
1373 .ok_or(ModuleLookupError::WrongModuleType {
1374 instance_id: id,
1375 expected: std::any::type_name::<M>(),
1376 })?;
1377 let (db, _) = self.db().with_prefix_module_id(id);
1378 Ok(ClientModuleInstance {
1379 id,
1380 db,
1381 api: self.api().with_module(id),
1382 module,
1383 })
1384 }
1385
1386 pub fn get_module_client_dyn(
1387 &self,
1388 instance_id: ModuleInstanceId,
1389 ) -> Result<&maybe_add_send_sync!(dyn IClientModule), ModuleLookupError> {
1390 self.try_get_module(instance_id)
1391 .ok_or(ModuleLookupError::UnknownInstance { instance_id })
1392 }
1393
1394 pub fn db(&self) -> &Database {
1395 &self.db
1396 }
1397
1398 pub fn endpoints(&self) -> &ConnectorRegistry {
1399 &self.connectors
1400 }
1401
1402 pub async fn transaction_updates(&self, operation_id: OperationId) -> TransactionUpdates {
1405 TransactionUpdates {
1406 update_stream: self.transaction_update_stream(operation_id).await,
1407 }
1408 }
1409
1410 pub fn get_first_instance(&self, module_kind: &ModuleKind) -> Option<ModuleInstanceId> {
1412 self.modules
1413 .iter_modules()
1414 .find(|(_, kind, _module)| *kind == module_kind)
1415 .map(|(instance_id, _, _)| instance_id)
1416 }
1417
1418 pub async fn await_primary_bitcoin_module_outputs(
1421 &self,
1422 operation_id: OperationId,
1423 outputs: Vec<OutPoint>,
1424 ) -> Result<(), TransactionSubmitError> {
1425 for out_point in outputs {
1426 self.await_primary_bitcoin_module_output(operation_id, out_point)
1427 .await?;
1428 }
1429
1430 Ok(())
1431 }
1432
1433 pub async fn get_config_json(&self) -> JsonClientConfig {
1439 self.config().await.to_json()
1440 }
1441
1442 #[doc(hidden)]
1445 pub async fn get_balance_for_btc(&self) -> Result<Amount, ModuleLookupError> {
1448 self.get_balance_for_unit(AmountUnit::BITCOIN).await
1449 }
1450
1451 pub async fn get_balance_for_unit(
1452 &self,
1453 unit: AmountUnit,
1454 ) -> Result<Amount, ModuleLookupError> {
1455 let (id, module) = self
1456 .primary_module_for_unit(unit)
1457 .ok_or(ModuleLookupError::NoPrimaryModule { unit })?;
1458 Ok(module
1459 .get_balance(id, &mut self.db().begin_transaction_nc().await, unit)
1460 .await)
1461 }
1462
1463 pub async fn subscribe_balance_changes(&self, unit: AmountUnit) -> BoxStream<'static, Amount> {
1466 let primary_module_things =
1467 if let Some((primary_module_id, primary_module)) = self.primary_module_for_unit(unit) {
1468 let balance_changes = primary_module.subscribe_balance_changes().await;
1469 let initial_balance = self
1470 .get_balance_for_unit(unit)
1471 .await
1472 .expect("Primary is present");
1473
1474 Some((
1475 primary_module_id,
1476 primary_module.clone(),
1477 balance_changes,
1478 initial_balance,
1479 ))
1480 } else {
1481 None
1482 };
1483 let db = self.db().clone();
1484
1485 Box::pin(async_stream::stream! {
1486 let Some((primary_module_id, primary_module, mut balance_changes, initial_balance)) = primary_module_things else {
1487 pending().await
1490 };
1491
1492
1493 yield initial_balance;
1494 let mut prev_balance = initial_balance;
1495 while let Some(()) = balance_changes.next().await {
1496 let mut dbtx = db.begin_transaction_nc().await;
1497 let balance = primary_module
1498 .get_balance(primary_module_id, &mut dbtx, unit)
1499 .await;
1500
1501 if balance != prev_balance {
1503 prev_balance = balance;
1504 yield balance;
1505 }
1506 }
1507 })
1508 }
1509
1510 async fn make_api_version_request(
1515 delay: Duration,
1516 peer_id: PeerId,
1517 api: &DynGlobalApi,
1518 ) -> (
1519 PeerId,
1520 Result<SupportedApiVersionsSummary, fedimint_connectors::error::ServerError>,
1521 ) {
1522 runtime::sleep(delay).await;
1523 (
1524 peer_id,
1525 api.request_single_peer::<SupportedApiVersionsSummary>(
1526 VERSION_ENDPOINT.to_owned(),
1527 ApiRequestErased::default(),
1528 peer_id,
1529 )
1530 .await,
1531 )
1532 }
1533
1534 fn create_api_version_backoff() -> impl Iterator<Item = Duration> {
1540 custom_backoff(Duration::from_millis(200), Duration::from_secs(600), None)
1541 }
1542
1543 pub async fn fetch_common_api_versions_from_all_peers(
1546 num_peers: NumPeers,
1547 api: DynGlobalApi,
1548 db: Database,
1549 num_responses_sender: watch::Sender<usize>,
1550 ) {
1551 let mut backoff = Self::create_api_version_backoff();
1552
1553 let mut requests = FuturesUnordered::new();
1556
1557 for peer_id in num_peers.peer_ids() {
1558 requests.push(Self::make_api_version_request(
1559 Duration::ZERO,
1560 peer_id,
1561 &api,
1562 ));
1563 }
1564
1565 let mut num_responses = 0;
1566
1567 while let Some((peer_id, response)) = requests.next().await {
1568 let retry = match response {
1569 Err(err) => {
1570 let has_previous_response = db
1571 .begin_transaction_nc()
1572 .await
1573 .get_value(&PeerLastApiVersionsSummaryKey(peer_id))
1574 .await
1575 .is_some();
1576 debug!(
1577 target: LOG_CLIENT,
1578 %peer_id,
1579 err = %err.fmt_compact(),
1580 %has_previous_response,
1581 "Failed to refresh API versions of a peer"
1582 );
1583
1584 !has_previous_response
1585 }
1586 Ok(o) => {
1587 let mut dbtx = db.begin_transaction().await;
1590 dbtx.insert_entry(
1591 &PeerLastApiVersionsSummaryKey(peer_id),
1592 &PeerLastApiVersionsSummary(o),
1593 )
1594 .await;
1595 dbtx.commit_tx().await;
1596 false
1597 }
1598 };
1599
1600 if retry {
1601 requests.push(Self::make_api_version_request(
1602 backoff.next().expect("Keeps retrying"),
1603 peer_id,
1604 &api,
1605 ));
1606 } else {
1607 num_responses += 1;
1608 num_responses_sender.send_replace(num_responses);
1609 }
1610 }
1611 }
1612
1613 pub async fn fetch_peers_api_versions_from_threshold_of_peers(
1617 num_peers: NumPeers,
1618 api: DynGlobalApi,
1619 ) -> BTreeMap<PeerId, SupportedApiVersionsSummary> {
1620 let mut backoff = Self::create_api_version_backoff();
1621
1622 let mut requests = FuturesUnordered::new();
1625
1626 for peer_id in num_peers.peer_ids() {
1627 requests.push(Self::make_api_version_request(
1628 Duration::ZERO,
1629 peer_id,
1630 &api,
1631 ));
1632 }
1633
1634 let mut successful_responses = BTreeMap::new();
1635
1636 while successful_responses.len() < num_peers.threshold()
1637 && let Some((peer_id, response)) = requests.next().await
1638 {
1639 let retry = match response {
1640 Err(err) => {
1641 debug!(
1642 target: LOG_CLIENT,
1643 %peer_id,
1644 err = %err.fmt_compact(),
1645 "Failed to fetch API versions from peer"
1646 );
1647 true
1648 }
1649 Ok(response) => {
1650 successful_responses.insert(peer_id, response);
1651 false
1652 }
1653 };
1654
1655 if retry {
1656 requests.push(Self::make_api_version_request(
1657 backoff.next().expect("Keeps retrying"),
1658 peer_id,
1659 &api,
1660 ));
1661 }
1662 }
1663
1664 successful_responses
1665 }
1666
1667 pub async fn fetch_common_api_versions(
1669 config: &ClientConfig,
1670 api: &DynGlobalApi,
1671 ) -> BTreeMap<PeerId, SupportedApiVersionsSummary> {
1672 debug!(
1673 target: LOG_CLIENT,
1674 "Fetching common api versions"
1675 );
1676
1677 let num_peers = NumPeers::from(config.global.api_endpoints.len());
1678
1679 Self::fetch_peers_api_versions_from_threshold_of_peers(num_peers, api.clone()).await
1680 }
1681
1682 pub async fn write_api_version_cache(
1686 dbtx: &mut DatabaseTransaction<'_>,
1687 api_version_set: ApiVersionSet,
1688 ) {
1689 debug!(
1690 target: LOG_CLIENT,
1691 value = ?api_version_set,
1692 "Writing API version set to cache"
1693 );
1694
1695 dbtx.insert_entry(
1696 &CachedApiVersionSetKey,
1697 &CachedApiVersionSet(api_version_set),
1698 )
1699 .await;
1700 }
1701
1702 pub async fn store_prefetched_api_versions(
1707 db: &Database,
1708 config: &ClientConfig,
1709 client_module_init: &ClientModuleInitRegistry,
1710 peer_api_versions: &BTreeMap<PeerId, SupportedApiVersionsSummary>,
1711 ) {
1712 debug!(
1713 target: LOG_CLIENT,
1714 "Storing {} prefetched peer API version responses and calculating common version set",
1715 peer_api_versions.len()
1716 );
1717
1718 let mut dbtx = db.begin_transaction().await;
1719 let client_supported_versions =
1721 Self::supported_api_versions_summary_static(config, client_module_init);
1722 match fedimint_client_module::api_version_discovery::discover_common_api_versions_set(
1723 &client_supported_versions,
1724 peer_api_versions,
1725 ) {
1726 Ok(common_api_versions) => {
1727 Self::write_api_version_cache(&mut dbtx.to_ref_nc(), common_api_versions).await;
1729 debug!(target: LOG_CLIENT, "Calculated and stored common API version set");
1730 }
1731 Err(err) => {
1732 debug!(target: LOG_CLIENT, err = %err.fmt_compact(), "Failed to calculate common API versions from prefetched data");
1733 }
1734 }
1735
1736 for (peer_id, peer_api_versions) in peer_api_versions {
1738 dbtx.insert_entry(
1739 &PeerLastApiVersionsSummaryKey(*peer_id),
1740 &PeerLastApiVersionsSummary(peer_api_versions.clone()),
1741 )
1742 .await;
1743 }
1744 dbtx.commit_tx().await;
1745 debug!(target: LOG_CLIENT, "Stored individual peer API version responses");
1746 }
1747
1748 pub fn supported_api_versions_summary_static(
1750 config: &ClientConfig,
1751 client_module_init: &ClientModuleInitRegistry,
1752 ) -> SupportedApiVersionsSummary {
1753 SupportedApiVersionsSummary {
1754 core: SupportedCoreApiVersions {
1755 core_consensus: config.global.consensus_version,
1756 api: MultiApiVersion::try_from_iter(SUPPORTED_CORE_API_VERSIONS.to_owned())
1757 .expect("must not have conflicting versions"),
1758 },
1759 modules: config
1760 .modules
1761 .iter()
1762 .filter_map(|(&module_instance_id, module_config)| {
1763 client_module_init
1764 .get(module_config.kind())
1765 .map(|module_init| {
1766 (
1767 module_instance_id,
1768 SupportedModuleApiVersions {
1769 core_consensus: config.global.consensus_version,
1770 module_consensus: module_config.version,
1771 api: module_init.supported_api_versions(),
1772 },
1773 )
1774 })
1775 })
1776 .collect(),
1777 }
1778 }
1779
1780 pub async fn load_and_refresh_common_api_version(
1781 &self,
1782 ) -> Result<ApiVersionSet, ApiVersionDiscoveryError> {
1783 Self::load_and_refresh_common_api_version_static(
1784 &self.config().await,
1785 &self.module_inits,
1786 self.connectors.clone(),
1787 &self.api,
1788 &self.db,
1789 &self.task_group,
1790 &self.client_span,
1791 )
1792 .await
1793 }
1794
1795 pub async fn refresh_api_versions(&self) -> Result<ApiVersionSet, ApiVersionDiscoveryError> {
1801 Self::refresh_common_api_version_static(
1802 &self.config().await,
1803 &self.module_inits,
1804 &self.api,
1805 &self.db,
1806 self.task_group.clone(),
1807 &self.client_span,
1808 true,
1809 )
1810 .await
1811 }
1812
1813 pub(crate) async fn load_and_refresh_common_api_version_static(
1819 config: &ClientConfig,
1820 module_init: &ClientModuleInitRegistry,
1821 connectors: ConnectorRegistry,
1822 api: &DynGlobalApi,
1823 db: &Database,
1824 task_group: &TaskGroup,
1825 client_span: &Span,
1826 ) -> Result<ApiVersionSet, ApiVersionDiscoveryError> {
1827 if let Some(v) = db
1828 .begin_transaction_nc()
1829 .await
1830 .get_value(&CachedApiVersionSetKey)
1831 .await
1832 {
1833 client_span.in_scope(|| {
1834 debug!(
1835 target: LOG_CLIENT,
1836 "Found existing cached common api versions"
1837 );
1838 });
1839 let config = config.clone();
1840 let client_module_init = module_init.clone();
1841 let api = api.clone();
1842 let db = db.clone();
1843 let task_group = task_group.clone();
1844 let client_span_owned = client_span.clone();
1845 task_group.clone().spawn_cancellable_with_span(
1848 client_span.clone(),
1849 "refresh_common_api_version_static",
1850 async move {
1851 connectors.wait_for_initialized_connections().await;
1852
1853 if let Err(error) = Self::refresh_common_api_version_static(
1854 &config,
1855 &client_module_init,
1856 &api,
1857 &db,
1858 task_group,
1859 &client_span_owned,
1860 false,
1861 )
1862 .await
1863 {
1864 warn!(
1865 target: LOG_CLIENT,
1866 err = %error.fmt_compact(), "Failed to discover common api versions"
1867 );
1868 }
1869 },
1870 );
1871
1872 return Ok(v.0);
1873 }
1874
1875 info!(
1876 target: LOG_CLIENT,
1877 "Fetching initial API versions "
1878 );
1879 Self::refresh_common_api_version_static(
1880 config,
1881 module_init,
1882 api,
1883 db,
1884 task_group.clone(),
1885 client_span,
1886 true,
1887 )
1888 .await
1889 }
1890
1891 async fn refresh_common_api_version_static(
1892 config: &ClientConfig,
1893 client_module_init: &ClientModuleInitRegistry,
1894 api: &DynGlobalApi,
1895 db: &Database,
1896 task_group: TaskGroup,
1897 client_span: &Span,
1898 block_until_ok: bool,
1899 ) -> Result<ApiVersionSet, ApiVersionDiscoveryError> {
1900 debug!(
1901 target: LOG_CLIENT,
1902 "Refreshing common api versions"
1903 );
1904
1905 let (num_responses_sender, mut num_responses_receiver) = tokio::sync::watch::channel(0);
1906 let num_peers = NumPeers::from(config.global.api_endpoints.len());
1907
1908 task_group.spawn_cancellable_with_span(
1909 client_span.clone(),
1910 "refresh peers api versions",
1911 Client::fetch_common_api_versions_from_all_peers(
1912 num_peers,
1913 api.clone(),
1914 db.clone(),
1915 num_responses_sender,
1916 ),
1917 );
1918
1919 let common_api_versions = loop {
1920 let _: Result<_, Elapsed> = runtime::timeout(
1928 Duration::from_secs(30),
1929 num_responses_receiver.wait_for(|num| num_peers.threshold() <= *num),
1930 )
1931 .await;
1932
1933 let peer_api_version_sets = Self::load_peers_last_api_versions(db, num_peers).await;
1934
1935 match fedimint_client_module::api_version_discovery::discover_common_api_versions_set(
1936 &Self::supported_api_versions_summary_static(config, client_module_init),
1937 &peer_api_version_sets,
1938 ) {
1939 Ok(o) => break o,
1940 Err(err) if block_until_ok => {
1941 warn!(
1942 target: LOG_CLIENT,
1943 err = %err.fmt_compact(),
1944 "Failed to discover API version to use. Retrying..."
1945 );
1946 continue;
1947 }
1948 Err(e) => return Err(e),
1949 }
1950 };
1951
1952 debug!(
1953 target: LOG_CLIENT,
1954 value = ?common_api_versions,
1955 "Updating the cached common api versions"
1956 );
1957 let mut dbtx = db.begin_transaction().await;
1958 let _ = dbtx
1959 .insert_entry(
1960 &CachedApiVersionSetKey,
1961 &CachedApiVersionSet(common_api_versions.clone()),
1962 )
1963 .await;
1964
1965 dbtx.commit_tx().await;
1966
1967 Ok(common_api_versions)
1968 }
1969
1970 pub async fn get_metadata(&self) -> Metadata {
1972 self.db
1973 .begin_transaction_nc()
1974 .await
1975 .get_value(&ClientMetadataKey)
1976 .await
1977 .unwrap_or_else(|| {
1978 warn!(
1979 target: LOG_CLIENT,
1980 "Missing existing metadata. This key should have been set on Client init"
1981 );
1982 Metadata::empty()
1983 })
1984 }
1985
1986 pub async fn set_metadata(&self, metadata: &Metadata) {
1988 self.db
1989 .autocommit::<_, _, Infallible>(
1990 |dbtx, _| {
1991 Box::pin(async {
1992 Self::set_metadata_dbtx(dbtx, metadata).await;
1993 Ok(())
1994 })
1995 },
1996 None,
1997 )
1998 .await
1999 .expect("Failed to autocommit metadata");
2000 }
2001
2002 pub fn has_pending_recoveries(&self) -> bool {
2003 !self
2004 .client_recovery_status_receiver
2005 .borrow()
2006 .values()
2007 .all(RecoveryStatus::is_successfully_done)
2008 }
2009
2010 pub fn all_modules_usable(&self) -> bool {
2021 self.client_recovery_status_receiver
2022 .borrow()
2023 .keys()
2024 .all(|module_instance_id| self.modules.get(*module_instance_id).is_some())
2025 }
2026
2027 pub async fn wait_for_all_recoveries(&self) -> Result<(), RecoveryError> {
2041 Self::wait_for_recoveries(
2042 self.client_recovery_status_receiver.clone(),
2043 |_module_instance_id| true,
2044 )
2045 .await
2046 }
2047
2048 async fn wait_for_recoveries(
2057 mut status_receiver: watch::Receiver<BTreeMap<ModuleInstanceId, RecoveryStatus>>,
2058 module_filter: impl Fn(ModuleInstanceId) -> bool,
2059 ) -> Result<(), RecoveryError> {
2060 let failure = status_receiver
2061 .wait_for(|statuses| {
2062 let matching = || {
2063 statuses
2064 .iter()
2065 .filter(|(module_instance_id, _status)| module_filter(**module_instance_id))
2066 .map(|(_module_instance_id, status)| status)
2067 };
2068
2069 matching().any(|status| matches!(status, RecoveryStatus::Failed { .. }))
2073 || matching().all(RecoveryStatus::is_successfully_done)
2074 })
2075 .await
2076 .map_err(|_closed| RecoveryError::ClientStopped)?
2077 .iter()
2080 .find_map(|(module_instance_id, status)| match status {
2081 RecoveryStatus::Failed { error, .. } if module_filter(*module_instance_id) => {
2082 Some((*module_instance_id, error.clone()))
2083 }
2084 _ => None,
2085 });
2086
2087 match failure {
2088 Some((module_instance_id, source)) => Err(RecoveryError::Failed {
2089 module_instance_id,
2090 source,
2091 }),
2092 None => Ok(()),
2093 }
2094 }
2095
2096 pub fn subscribe_to_recovery_progress(
2106 &self,
2107 ) -> impl Stream<Item = (ModuleInstanceId, RecoveryProgress)> + use<> {
2108 WatchStream::new(self.client_recovery_status_receiver.clone()).flat_map(|statuses| {
2109 futures::stream::iter(
2110 statuses
2111 .into_iter()
2112 .map(|(module_instance_id, status)| (module_instance_id, status.progress())),
2113 )
2114 })
2115 }
2116
2117 pub async fn wait_for_module_kind_recovery(
2126 &self,
2127 module_kind: ModuleKind,
2128 ) -> Result<(), RecoveryError> {
2129 let config = self.config().await;
2130 Self::wait_for_recoveries(
2131 self.client_recovery_status_receiver.clone(),
2132 move |module_instance_id| {
2133 config
2134 .modules
2135 .get(&module_instance_id)
2136 .is_some_and(|module| module.kind == module_kind)
2137 },
2138 )
2139 .await
2140 }
2141
2142 pub async fn wait_for_all_active_state_machines(&self) {
2143 loop {
2144 if self.executor.get_active_states().await.is_empty() {
2145 break;
2146 }
2147 sleep(Duration::from_millis(100)).await;
2148 }
2149 }
2150
2151 pub async fn set_metadata_dbtx(dbtx: &mut DatabaseTransaction<'_>, metadata: &Metadata) {
2153 dbtx.insert_new_entry(&ClientMetadataKey, metadata).await;
2154 }
2155
2156 fn spawn_module_recoveries_task(
2157 &self,
2158 recovery_sender: watch::Sender<BTreeMap<ModuleInstanceId, RecoveryStatus>>,
2159 module_recoveries: BTreeMap<ModuleInstanceId, ModuleRecoveryFuture>,
2160 module_recovery_progress_receivers: BTreeMap<
2161 ModuleInstanceId,
2162 watch::Receiver<RecoveryProgress>,
2163 >,
2164 module_kinds: BTreeMap<ModuleInstanceId, ModuleKind>,
2167 ) {
2168 let db = self.db.clone();
2169 let log_ordering_wakeup_tx = self.log_ordering_wakeup_tx.clone();
2170 self.spawn("module recoveries", |_task_handle| async {
2175 Self::run_module_recoveries_task(
2176 db,
2177 log_ordering_wakeup_tx,
2178 recovery_sender,
2179 module_recoveries,
2180 module_recovery_progress_receivers,
2181 module_kinds,
2182 )
2183 .await;
2184 });
2185 }
2186
2187 async fn run_module_recoveries_task(
2188 db: Database,
2189 log_ordering_wakeup_tx: watch::Sender<()>,
2190 recovery_sender: watch::Sender<BTreeMap<ModuleInstanceId, RecoveryStatus>>,
2191 module_recoveries: BTreeMap<ModuleInstanceId, ModuleRecoveryFuture>,
2192 module_recovery_progress_receivers: BTreeMap<
2193 ModuleInstanceId,
2194 watch::Receiver<RecoveryProgress>,
2195 >,
2196 module_kinds: BTreeMap<ModuleInstanceId, ModuleKind>,
2197 ) {
2198 debug!(target: LOG_CLIENT_RECOVERY, num_modules=%module_recovery_progress_receivers.len(), "Staring module recoveries");
2199
2200 enum RecoveryUpdate {
2204 Progress(RecoveryProgress),
2205 Completed(Option<Amount>),
2206 }
2207
2208 let mut completed_stream = Vec::new();
2209 let progress_stream = futures::stream::FuturesUnordered::new();
2210
2211 for (module_instance_id, f) in module_recoveries {
2212 let recovery_sender = recovery_sender.clone();
2213 completed_stream.push(futures::stream::once(Box::pin(async move {
2214 match f.await {
2215 Ok(amount) => (module_instance_id, RecoveryUpdate::Completed(amount)),
2216 Err(err) => {
2217 warn!(
2218 target: LOG_CLIENT,
2219 err = %err.fmt_compact(), module_instance_id, "Module recovery failed"
2220 );
2221 let error = Arc::new(err);
2222 recovery_sender.send_modify(|statuses| {
2229 let last_progress = statuses
2230 .get(&module_instance_id)
2231 .expect("existing status must be present")
2232 .progress();
2233 statuses.insert(
2234 module_instance_id,
2235 RecoveryStatus::Failed {
2236 last_progress,
2237 error,
2238 },
2239 );
2240 });
2241 futures::future::pending::<()>().await;
2251 unreachable!()
2252 }
2253 }
2254 })));
2255 }
2256
2257 for (module_instance_id, rx) in module_recovery_progress_receivers {
2258 progress_stream.push(
2259 tokio_stream::wrappers::WatchStream::new(rx)
2260 .fuse()
2261 .map(move |progress| (module_instance_id, RecoveryUpdate::Progress(progress))),
2262 );
2263 }
2264
2265 let mut futures = futures::stream::select(
2266 futures::stream::select_all(progress_stream),
2267 futures::stream::select_all(completed_stream),
2268 );
2269
2270 while let Some((module_instance_id, update)) = futures.next().await {
2271 let prev_status = recovery_sender
2275 .borrow()
2276 .get(&module_instance_id)
2277 .expect("existing status must be present")
2278 .clone();
2279
2280 if matches!(prev_status, RecoveryStatus::Failed { .. }) {
2290 debug!(
2291 target: LOG_CLIENT_RECOVERY,
2292 module_instance_id,
2293 "Ignoring a recovery update of a module whose recovery already failed"
2294 );
2295 continue;
2296 }
2297
2298 let prev_progress = prev_status.progress();
2299
2300 if let RecoveryUpdate::Progress(progress) = &update {
2306 if progress.is_done() {
2307 warn!(
2308 target: LOG_CLIENT_RECOVERY,
2309 module_instance_id,
2310 "Module bypassed the sanctioned recovery progress reporting API and reported a completed recovery progress. Ignoring"
2311 );
2312 continue;
2313 }
2314
2315 if progress.is_none() && !prev_progress.is_none() && !prev_progress.is_done() {
2322 warn!(
2323 target: LOG_CLIENT_RECOVERY,
2324 module_instance_id,
2325 "Module bypassed the sanctioned recovery progress reporting API and reported a none recovery progress, regressing its previous one. Ignoring"
2326 );
2327 continue;
2328 }
2329 }
2330
2331 let mut dbtx = db.begin_transaction().await;
2332
2333 let (progress, recovered_amount) = if prev_progress.is_done() {
2339 (prev_progress, None)
2341 } else {
2342 match update {
2343 RecoveryUpdate::Progress(progress) => (progress, None),
2344 RecoveryUpdate::Completed(amount) => (prev_progress.to_complete(), amount),
2345 }
2346 };
2347
2348 if !prev_progress.is_done() && progress.is_done() {
2349 info!(
2350 target: LOG_CLIENT,
2351 module_instance_id,
2352 progress = format!("{}/{}", progress.complete, progress.total),
2353 amount = ?recovered_amount,
2354 "Recovery complete"
2355 );
2356 dbtx.log_event(
2357 log_ordering_wakeup_tx.clone(),
2358 None,
2359 ModuleRecoveryCompleted {
2360 module_id: module_instance_id,
2361 kind: module_kinds.get(&module_instance_id).cloned(),
2362 amount: recovered_amount,
2363 },
2364 )
2365 .await;
2366 } else {
2367 info!(
2368 target: LOG_CLIENT,
2369 module_instance_id,
2370 kind = ?module_kinds.get(&module_instance_id),
2371 progress = format!("{}/{}", progress.complete, progress.total),
2372 "Recovery progress"
2373 );
2374 }
2375
2376 dbtx.insert_entry(
2377 &ClientModuleRecovery { module_instance_id },
2378 &ClientModuleRecoveryState { progress },
2379 )
2380 .await;
2381 dbtx.commit_tx().await;
2382
2383 recovery_sender.send_modify(|statuses| {
2384 statuses.insert(module_instance_id, RecoveryStatus::InProgress(progress));
2385 });
2386 }
2387 debug!(target: LOG_CLIENT_RECOVERY, "Recovery executor stopped");
2388 }
2389
2390 async fn load_peers_last_api_versions(
2391 db: &Database,
2392 num_peers: NumPeers,
2393 ) -> BTreeMap<PeerId, SupportedApiVersionsSummary> {
2394 let mut peer_api_version_sets = BTreeMap::new();
2395
2396 let mut dbtx = db.begin_transaction_nc().await;
2397 for peer_id in num_peers.peer_ids() {
2398 if let Some(v) = dbtx
2399 .get_value(&PeerLastApiVersionsSummaryKey(peer_id))
2400 .await
2401 {
2402 peer_api_version_sets.insert(peer_id, v.0);
2403 }
2404 }
2405 drop(dbtx);
2406 peer_api_version_sets
2407 }
2408
2409 pub async fn get_peer_url_announcements(&self) -> BTreeMap<PeerId, SignedApiAnnouncement> {
2412 self.db()
2413 .begin_transaction_nc()
2414 .await
2415 .find_by_prefix(&ApiAnnouncementPrefix)
2416 .await
2417 .map(|(announcement_key, announcement)| (announcement_key.0, announcement))
2418 .collect()
2419 .await
2420 }
2421
2422 pub async fn get_guardian_metadata(
2424 &self,
2425 ) -> BTreeMap<PeerId, fedimint_core::net::guardian_metadata::SignedGuardianMetadata> {
2426 self.db()
2427 .begin_transaction_nc()
2428 .await
2429 .find_by_prefix(&crate::guardian_metadata::GuardianMetadataPrefix)
2430 .await
2431 .map(|(key, metadata)| (key.0, metadata))
2432 .collect()
2433 .await
2434 }
2435
2436 pub async fn peer_availability(&self) -> MergedPeerAvailability {
2440 let reports =
2441 futures::future::join_all(self.api.all_peers().iter().map(|peer| async move {
2442 let report =
2443 runtime::timeout(Duration::from_secs(10), self.api.peer_availability(*peer))
2444 .await;
2445 (*peer, report)
2446 }))
2447 .await
2448 .into_iter()
2449 .filter_map(|(peer, report)| Some((peer, report.ok()?.ok()?)))
2450 .collect();
2451
2452 merge_peer_availability(
2453 &reports,
2454 fedimint_core::NumPeersExt::to_num_peers(self.api.all_peers()),
2455 )
2456 }
2457
2458 pub async fn get_peer_urls(&self) -> BTreeMap<PeerId, SafeUrl> {
2460 get_api_urls(&self.db, &self.config().await, self.iroh_enable_next).await
2461 }
2462
2463 pub async fn invite_code(&self, peer: PeerId) -> Option<InviteCode> {
2466 self.get_peer_urls()
2467 .await
2468 .into_iter()
2469 .find_map(|(peer_id, url)| (peer == peer_id).then_some(url))
2470 .map(|peer_url| {
2471 InviteCode::new(
2472 peer_url.clone(),
2473 peer,
2474 self.federation_id(),
2475 self.api_secret.clone(),
2476 )
2477 })
2478 }
2479
2480 pub async fn get_guardian_public_keys_blocking(
2484 &self,
2485 ) -> BTreeMap<PeerId, fedimint_core::secp256k1::PublicKey> {
2486 self.db
2487 .autocommit(
2488 |dbtx, _| {
2489 Box::pin(async move {
2490 let config = self.config().await;
2491
2492 let guardian_pub_keys = self
2493 .get_or_backfill_broadcast_public_keys(dbtx, config)
2494 .await;
2495
2496 Result::<_, ()>::Ok(guardian_pub_keys)
2497 })
2498 },
2499 None,
2500 )
2501 .await
2502 .expect("Will retry forever")
2503 }
2504
2505 async fn get_or_backfill_broadcast_public_keys(
2506 &self,
2507 dbtx: &mut DatabaseTransaction<'_>,
2508 config: ClientConfig,
2509 ) -> BTreeMap<PeerId, PublicKey> {
2510 match config.global.broadcast_public_keys {
2511 Some(guardian_pub_keys) => guardian_pub_keys,
2512 _ => {
2513 let (guardian_pub_keys, new_config) = self.fetch_and_update_config(config).await;
2514
2515 dbtx.insert_entry(&ClientConfigKey, &new_config).await;
2516 *(self.config.write().await) = new_config;
2517 guardian_pub_keys
2518 }
2519 }
2520 }
2521
2522 pub async fn fetch_session_count(&self) -> FederationResult<u64> {
2523 self.api.session_count().await
2524 }
2525
2526 async fn fetch_and_update_config(
2527 &self,
2528 config: ClientConfig,
2529 ) -> (BTreeMap<PeerId, PublicKey>, ClientConfig) {
2530 let fetched_config = retry(
2531 "Fetching guardian public keys",
2532 backoff_util::background_backoff(),
2533 || async {
2534 self.api
2535 .request_current_consensus::<ClientConfig>(
2536 CLIENT_CONFIG_ENDPOINT.to_owned(),
2537 ApiRequestErased::default(),
2538 )
2539 .await
2540 },
2541 )
2542 .await
2543 .expect("Will never return on error");
2544
2545 let Some(guardian_pub_keys) = fetched_config.global.broadcast_public_keys else {
2546 warn!(
2547 target: LOG_CLIENT,
2548 "Guardian public keys not found in fetched config, server not updated to 0.4 yet"
2549 );
2550 pending::<()>().await;
2551 unreachable!("Pending will never return");
2552 };
2553
2554 let new_config = ClientConfig {
2555 global: GlobalClientConfig {
2556 broadcast_public_keys: Some(guardian_pub_keys.clone()),
2557 ..config.global
2558 },
2559 modules: config.modules,
2560 };
2561 (guardian_pub_keys, new_config)
2562 }
2563
2564 pub fn handle_global_rpc(
2571 &self,
2572 method: String,
2573 params: serde_json::Value,
2574 ) -> BoxStream<'_, Result<serde_json::Value, GlobalRpcError>> {
2575 Box::pin(try_stream! {
2576 match method.as_str() {
2577 "get_balance" => {
2578 let balance = self.get_balance_for_btc().await.unwrap_or_default();
2579 yield serde_json::to_value(balance)?;
2580 }
2581 "subscribe_balance_changes" => {
2582 let req: GetBalanceChangesRequest= serde_json::from_value(params)?;
2583 let mut stream = self.subscribe_balance_changes(req.unit).await;
2584 while let Some(balance) = stream.next().await {
2585 yield serde_json::to_value(balance)?;
2586 }
2587 }
2588 "get_config" => {
2589 let config = self.config().await;
2590 yield serde_json::to_value(config)?;
2591 }
2592 "get_federation_id" => {
2593 let federation_id = self.federation_id();
2594 yield serde_json::to_value(federation_id)?;
2595 }
2596 "get_invite_code" => {
2597 let req: GetInviteCodeRequest = serde_json::from_value(params)?;
2598 let invite_code = self.invite_code(req.peer).await;
2599 yield serde_json::to_value(invite_code)?;
2600 }
2601 "get_operation" => {
2602 let req: GetOperationIdRequest = serde_json::from_value(params)?;
2603 let operation = self.operation_log().get_operation(req.operation_id).await;
2604 yield serde_json::to_value(operation)?;
2605 }
2606 "list_operations" => {
2607 let req: ListOperationsParams = serde_json::from_value(params)?;
2608 let limit = if req.limit.is_none() && req.last_seen.is_none() {
2609 usize::MAX
2610 } else {
2611 req.limit.unwrap_or(usize::MAX)
2612 };
2613 let operations = self.operation_log()
2614 .paginate_operations_rev(limit, req.last_seen)
2615 .await;
2616 yield serde_json::to_value(operations)?;
2617 }
2618 "get_event_log" => {
2619 let req: GetEventLogRequest = serde_json::from_value(params)?;
2620 let limit = req
2621 .limit
2622 .unwrap_or(DEFAULT_EVENT_LOG_PAGE_SIZE)
2623 .min(MAX_EVENT_LOG_PAGE_SIZE);
2624 let events = self.get_event_log(req.pos, limit).await;
2625 yield serde_json::to_value(events)?;
2626 }
2627 "session_count" => {
2628 let count = self.fetch_session_count().await?;
2629 yield serde_json::to_value(count)?;
2630 }
2631 "has_pending_recoveries" => {
2632 let has_pending = self.has_pending_recoveries();
2633 yield serde_json::to_value(has_pending)?;
2634 }
2635 "wait_for_all_recoveries" => {
2636 self.wait_for_all_recoveries().await?;
2637 yield serde_json::Value::Null;
2638 }
2639 "subscribe_to_recovery_progress" => {
2640 let mut stream = self.subscribe_to_recovery_progress();
2641 while let Some((module_id, progress)) = stream.next().await {
2642 yield serde_json::json!({
2643 "module_id": module_id,
2644 "progress": progress
2645 });
2646 }
2647 }
2648 #[allow(deprecated)]
2649 "backup_to_federation" => {
2650 let metadata = if params.is_null() {
2651 Metadata::from_json_serialized(serde_json::json!({}))
2652 } else {
2653 Metadata::from_json_serialized(params)
2654 };
2655 self.backup_to_federation(metadata).await?;
2656 yield serde_json::Value::Null;
2657 }
2658 _ => {
2659 Err(GlobalRpcError::UnknownMethod { method: method.clone() })?;
2660 unreachable!()
2661 },
2662 }
2663 })
2664 }
2665
2666 pub async fn log_event<E>(&self, module_id: Option<ModuleInstanceId>, event: E)
2667 where
2668 E: Event + Send,
2669 {
2670 let mut dbtx = self.db.begin_transaction().await;
2671 self.log_event_dbtx(&mut dbtx, module_id, event).await;
2672 dbtx.commit_tx().await;
2673 }
2674
2675 pub async fn log_event_dbtx<E, Cap>(
2676 &self,
2677 dbtx: &mut DatabaseTransaction<'_, Cap>,
2678 module_id: Option<ModuleInstanceId>,
2679 event: E,
2680 ) where
2681 E: Event + Send,
2682 Cap: Send,
2683 {
2684 dbtx.log_event(self.log_ordering_wakeup_tx.clone(), module_id, event)
2685 .await;
2686 }
2687
2688 pub async fn log_event_raw_dbtx<Cap>(
2689 &self,
2690 dbtx: &mut DatabaseTransaction<'_, Cap>,
2691 kind: EventKind,
2692 module: Option<(ModuleKind, ModuleInstanceId)>,
2693 payload: Vec<u8>,
2694 persist: EventPersistence,
2695 ) where
2696 Cap: Send,
2697 {
2698 let module_id = module.as_ref().map(|m| m.1);
2699 let module_kind = module.map(|m| m.0);
2700 dbtx.log_event_raw(
2701 self.log_ordering_wakeup_tx.clone(),
2702 kind,
2703 module_kind,
2704 module_id,
2705 payload,
2706 persist,
2707 )
2708 .await;
2709 }
2710
2711 pub fn built_in_application_event_log_tracker(&self) -> DynEventLogTrimableTracker {
2723 struct BuiltInApplicationEventLogTracker;
2724
2725 #[apply(async_trait_maybe_send!)]
2726 impl EventLogTrimableTracker for BuiltInApplicationEventLogTracker {
2727 async fn store(
2729 &mut self,
2730 dbtx: &mut DatabaseTransaction<NonCommittable>,
2731 pos: EventLogTrimableId,
2732 ) -> Result<(), EventLogTrackerError> {
2733 dbtx.insert_entry(&DefaultApplicationEventLogKey, &pos)
2734 .await;
2735 Ok(())
2736 }
2737
2738 async fn load(
2740 &mut self,
2741 dbtx: &mut DatabaseTransaction<NonCommittable>,
2742 ) -> Result<Option<EventLogTrimableId>, EventLogTrackerError> {
2743 Ok(dbtx.get_value(&DefaultApplicationEventLogKey).await)
2744 }
2745 }
2746 Box::new(BuiltInApplicationEventLogTracker)
2747 }
2748
2749 pub async fn handle_historical_events<F, R, E>(
2757 &self,
2758 tracker: fedimint_eventlog::DynEventLogTracker,
2759 handler_fn: F,
2760 ) -> Result<(), EventHandlerError<E>>
2761 where
2762 F: Fn(&mut DatabaseTransaction<NonCommittable>, EventLogEntry) -> R,
2763 R: Future<Output = Result<(), E>>,
2764 E: std::error::Error + 'static,
2765 {
2766 fedimint_eventlog::handle_events(
2767 self.db.clone(),
2768 tracker,
2769 self.log_event_added_rx.clone(),
2770 handler_fn,
2771 )
2772 .await
2773 }
2774
2775 pub async fn handle_events<F, R, E>(
2794 &self,
2795 tracker: fedimint_eventlog::DynEventLogTrimableTracker,
2796 handler_fn: F,
2797 ) -> Result<(), EventHandlerError<E>>
2798 where
2799 F: Fn(&mut DatabaseTransaction<NonCommittable>, EventLogEntry) -> R,
2800 R: Future<Output = Result<(), E>>,
2801 E: std::error::Error + 'static,
2802 {
2803 fedimint_eventlog::handle_trimable_events(
2804 self.db.clone(),
2805 tracker,
2806 self.log_event_added_rx.clone(),
2807 handler_fn,
2808 )
2809 .await
2810 }
2811
2812 pub async fn get_event_log(
2813 &self,
2814 pos: Option<EventLogId>,
2815 limit: u64,
2816 ) -> Vec<PersistedLogEntry> {
2817 self.get_event_log_dbtx(&mut self.db.begin_transaction_nc().await, pos, limit)
2818 .await
2819 }
2820
2821 pub async fn get_next_event_log_id(&self) -> EventLogId {
2824 self.db
2825 .begin_transaction_nc()
2826 .await
2827 .get_next_event_log_id()
2828 .await
2829 }
2830
2831 pub async fn get_event_log_trimable(
2832 &self,
2833 pos: Option<EventLogTrimableId>,
2834 limit: u64,
2835 ) -> Vec<PersistedLogEntry> {
2836 self.get_event_log_trimable_dbtx(&mut self.db.begin_transaction_nc().await, pos, limit)
2837 .await
2838 }
2839
2840 pub async fn get_event_log_dbtx<Cap>(
2841 &self,
2842 dbtx: &mut DatabaseTransaction<'_, Cap>,
2843 pos: Option<EventLogId>,
2844 limit: u64,
2845 ) -> Vec<PersistedLogEntry>
2846 where
2847 Cap: Send,
2848 {
2849 dbtx.get_event_log(pos, limit).await
2850 }
2851
2852 pub async fn get_event_log_trimable_dbtx<Cap>(
2853 &self,
2854 dbtx: &mut DatabaseTransaction<'_, Cap>,
2855 pos: Option<EventLogTrimableId>,
2856 limit: u64,
2857 ) -> Vec<PersistedLogEntry>
2858 where
2859 Cap: Send,
2860 {
2861 dbtx.get_event_log_trimable(pos, limit).await
2862 }
2863
2864 pub fn get_event_log_transient_receiver(&self) -> broadcast::Receiver<EventLogEntry> {
2866 self.log_event_added_transient_tx.subscribe()
2867 }
2868
2869 pub fn log_event_added_rx(&self) -> watch::Receiver<()> {
2871 self.log_event_added_rx.clone()
2872 }
2873
2874 pub fn iroh_enable_dht(&self) -> bool {
2875 self.iroh_enable_dht
2876 }
2877
2878 pub fn iroh_enable_next(&self) -> bool {
2881 self.iroh_enable_next
2882 }
2883
2884 pub(crate) async fn run_core_migrations(
2885 db_no_decoders: &Database,
2886 ) -> Result<(), DbMigrationError> {
2887 let mut dbtx = db_no_decoders.begin_transaction().await;
2888 apply_migrations_core_client_dbtx(&mut dbtx.to_ref_nc(), "fedimint-client".to_string())
2889 .await?;
2890 if is_running_in_test_env() {
2891 verify_client_db_integrity_dbtx(&mut dbtx.to_ref_nc()).await;
2892 }
2893 dbtx.commit_tx_result().await?;
2894 Ok(())
2895 }
2896
2897 fn primary_modules_for_unit(
2899 &self,
2900 unit: AmountUnit,
2901 ) -> impl Iterator<Item = (ModuleInstanceId, &DynClientModule)> {
2902 self.primary_modules
2903 .iter()
2904 .flat_map(move |(_prio, candidates)| {
2905 candidates
2906 .specific
2907 .get(&unit)
2908 .into_iter()
2909 .flatten()
2910 .copied()
2911 .chain(candidates.wildcard.iter().copied())
2913 })
2914 .map(|id| (id, self.modules.get_expect(id)))
2915 }
2916
2917 pub fn primary_module_for_unit(
2921 &self,
2922 unit: AmountUnit,
2923 ) -> Option<(ModuleInstanceId, &DynClientModule)> {
2924 self.primary_modules_for_unit(unit).next()
2925 }
2926
2927 pub fn primary_module_for_btc(&self) -> (ModuleInstanceId, &DynClientModule) {
2929 self.primary_module_for_unit(AmountUnit::BITCOIN)
2930 .expect("No primary module for Bitcoin")
2931 }
2932
2933 pub fn get_primary_module_for_unit<M: ClientModule>(
2946 &self,
2947 unit: AmountUnit,
2948 ) -> Result<&M, ModuleLookupError> {
2949 let mut modules = self.primary_modules_for_unit(unit).peekable();
2950 if modules.peek().is_none() {
2951 return Err(ModuleLookupError::NoPrimaryModule { unit });
2952 }
2953 modules
2954 .find_map(|(_, module)| module.as_any().downcast_ref::<M>())
2955 .ok_or_else(|| ModuleLookupError::NoPrimaryModuleOfKind {
2956 kind: M::kind(),
2957 unit,
2958 })
2959 }
2960}
2961
2962#[apply(async_trait_maybe_send!)]
2963impl ClientContextIface for Client {
2964 fn get_module(&self, instance: ModuleInstanceId) -> &maybe_add_send_sync!(dyn IClientModule) {
2965 Client::get_module(self, instance)
2966 }
2967
2968 fn api_clone(&self) -> DynGlobalApi {
2969 Client::api_clone(self)
2970 }
2971 fn decoders(&self) -> &ModuleDecoderRegistry {
2972 Client::decoders(self)
2973 }
2974
2975 async fn finalize_and_submit_transaction(
2976 &self,
2977 operation_id: OperationId,
2978 operation_type: &str,
2979 operation_meta_gen: Box<maybe_add_send_sync!(dyn Fn(OutPointRange) -> serde_json::Value)>,
2980 tx_builder: TransactionBuilder,
2981 ) -> Result<OutPointRange, TransactionSubmitError> {
2982 Client::finalize_and_submit_transaction(
2983 self,
2984 operation_id,
2985 operation_type,
2986 &operation_meta_gen,
2988 tx_builder,
2989 )
2990 .await
2991 }
2992
2993 async fn finalize_and_submit_transaction_dbtx(
2994 &self,
2995 dbtx: &mut DatabaseTransaction<'_>,
2996 operation_id: OperationId,
2997 operation_type: &str,
2998 operation_meta_gen: Box<maybe_add_send_sync!(dyn Fn(OutPointRange) -> serde_json::Value)>,
2999 tx_builder: TransactionBuilder,
3000 ) -> Result<OutPointRange, TransactionSubmitError> {
3001 Client::finalize_and_submit_transaction_dbtx(
3002 self,
3003 dbtx,
3004 operation_id,
3005 operation_type,
3006 &operation_meta_gen,
3007 tx_builder,
3008 )
3009 .await
3010 }
3011
3012 async fn finalize_and_submit_transaction_inner(
3013 &self,
3014 dbtx: &mut DatabaseTransaction<'_>,
3015 operation_id: OperationId,
3016 tx_builder: TransactionBuilder,
3017 ) -> Result<OutPointRange, TransactionSubmitError> {
3018 Client::finalize_and_submit_transaction_inner(self, dbtx, operation_id, tx_builder).await
3019 }
3020
3021 async fn fee_quote(
3022 &self,
3023 operation_id: OperationId,
3024 request: FeeQuoteRequest,
3025 ) -> Result<FeeQuote, TransactionSubmitError> {
3026 Client::fee_quote(self, operation_id, request).await
3027 }
3028
3029 async fn get_balance_for_unit(&self, unit: AmountUnit) -> Result<Amount, ModuleLookupError> {
3030 Client::get_balance_for_unit(self, unit).await
3031 }
3032
3033 async fn transaction_updates(&self, operation_id: OperationId) -> TransactionUpdates {
3034 Client::transaction_updates(self, operation_id).await
3035 }
3036
3037 async fn await_primary_module_outputs(
3038 &self,
3039 operation_id: OperationId,
3040 outputs: Vec<OutPoint>,
3042 ) -> Result<(), TransactionSubmitError> {
3043 Client::await_primary_bitcoin_module_outputs(self, operation_id, outputs).await
3044 }
3045
3046 fn operation_log(&self) -> &dyn IOperationLog {
3047 Client::operation_log(self)
3048 }
3049
3050 async fn has_active_states(&self, operation_id: OperationId) -> bool {
3051 Client::has_active_states(self, operation_id).await
3052 }
3053
3054 async fn await_no_active_states(&self, operation_id: OperationId) {
3055 Client::await_no_active_states(self, operation_id).await;
3056 }
3057
3058 async fn operation_exists(&self, operation_id: OperationId) -> bool {
3059 Client::operation_exists(self, operation_id).await
3060 }
3061
3062 async fn config(&self) -> ClientConfig {
3063 Client::config(self).await
3064 }
3065
3066 fn db(&self) -> &Database {
3067 Client::db(self)
3068 }
3069
3070 fn executor(&self) -> &(maybe_add_send_sync!(dyn IExecutor + 'static)) {
3071 Client::executor(self)
3072 }
3073
3074 async fn invite_code(&self, peer: PeerId) -> Option<InviteCode> {
3075 Client::invite_code(self, peer).await
3076 }
3077
3078 fn get_internal_payment_markers(&self) -> Result<(PublicKey, u64), bitcoin::secp256k1::Error> {
3079 Client::get_internal_payment_markers(self)
3080 }
3081
3082 async fn log_event_json(
3083 &self,
3084 dbtx: &mut DatabaseTransaction<'_, NonCommittable>,
3085 module_kind: Option<ModuleKind>,
3086 module_id: ModuleInstanceId,
3087 kind: EventKind,
3088 payload: serde_json::Value,
3089 persist: EventPersistence,
3090 ) {
3091 dbtx.ensure_global()
3092 .expect("Must be called with global dbtx");
3093 self.log_event_raw_dbtx(
3094 dbtx,
3095 kind,
3096 module_kind.map(|kind| (kind, module_id)),
3097 serde_json::to_vec(&payload).expect("Serialization can't fail"),
3098 persist,
3099 )
3100 .await;
3101 }
3102
3103 async fn read_operation_active_states<'dbtx>(
3104 &self,
3105 operation_id: OperationId,
3106 module_id: ModuleInstanceId,
3107 dbtx: &'dbtx mut DatabaseTransaction<'_>,
3108 ) -> Pin<Box<maybe_add_send!(dyn Stream<Item = (ActiveStateKey, ActiveStateMeta)> + 'dbtx)>>
3109 {
3110 Box::pin(
3111 dbtx.find_by_prefix(&ActiveModuleOperationStateKeyPrefix {
3112 operation_id,
3113 module_instance: module_id,
3114 })
3115 .await
3116 .map(move |(k, v)| (k.0, v)),
3117 )
3118 }
3119 async fn read_operation_inactive_states<'dbtx>(
3120 &self,
3121 operation_id: OperationId,
3122 module_id: ModuleInstanceId,
3123 dbtx: &'dbtx mut DatabaseTransaction<'_>,
3124 ) -> Pin<Box<maybe_add_send!(dyn Stream<Item = (InactiveStateKey, InactiveStateMeta)> + 'dbtx)>>
3125 {
3126 Box::pin(
3127 dbtx.find_by_prefix(&InactiveModuleOperationStateKeyPrefix {
3128 operation_id,
3129 module_instance: module_id,
3130 })
3131 .await
3132 .map(move |(k, v)| (k.0, v)),
3133 )
3134 }
3135}
3136
3137impl fmt::Debug for Client {
3139 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3140 write!(f, "Client")
3141 }
3142}
3143
3144pub fn client_decoders<'a>(
3145 registry: &ModuleInitRegistry<DynClientModuleInit>,
3146 module_kinds: impl Iterator<Item = (ModuleInstanceId, &'a ModuleKind)>,
3147) -> ModuleDecoderRegistry {
3148 let mut modules = BTreeMap::new();
3149 for (id, kind) in module_kinds {
3150 let Some(init) = registry.get(kind) else {
3151 debug!("Detected configuration for unsupported module id: {id}, kind: {kind}");
3152 continue;
3153 };
3154
3155 modules.insert(
3156 id,
3157 (
3158 kind.clone(),
3159 IClientModuleInit::decoder(AsRef::<dyn IClientModuleInit + 'static>::as_ref(init)),
3160 ),
3161 );
3162 }
3163 ModuleDecoderRegistry::from(modules)
3164}