1use std::collections::{BTreeMap, HashSet};
2use std::fmt::{self, Formatter};
3use std::future::{Future, pending};
4use std::ops::Range;
5use std::pin::Pin;
6use std::sync::Arc;
7use std::time::{Duration, SystemTime, UNIX_EPOCH};
8
9use anyhow::{Context as _, anyhow, bail, format_err};
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 _, FederationResult, IGlobalFederationApi,
17};
18use fedimint_bitcoind::DynBitcoindRpc;
19use fedimint_client_module::module::recovery::RecoveryProgress;
20use fedimint_client_module::module::{
21 ClientContextIface, ClientModule, ClientModuleRegistry, DynClientModule, FinalClientIface,
22 IClientModule, IdxRange, OutPointRange, PrimaryModulePriority,
23};
24use fedimint_client_module::oplog::IOperationLog;
25use fedimint_client_module::secret::{PlainRootSecretStrategy, RootSecretStrategy as _};
26use fedimint_client_module::sm::executor::{ActiveStateKey, IExecutor, InactiveStateKey};
27use fedimint_client_module::sm::{ActiveStateMeta, DynState, InactiveStateMeta};
28use fedimint_client_module::transaction::{
29 FeeQuote, FeeQuoteRequest, TRANSACTION_SUBMISSION_MODULE_INSTANCE, TransactionBuilder,
30 TxSubmissionStates, TxSubmissionStatesSM,
31};
32use fedimint_client_module::{
33 AddStateMachinesResult, ClientModuleInstance, GetInviteCodeRequest, ModuleGlobalContextGen,
34 ModuleRecoveryCompleted, TransactionUpdates, TxCreatedEvent,
35};
36use fedimint_connectors::{ConnectorRegistry, PeerStatus};
37use fedimint_core::config::{
38 ClientConfig, FederationId, GlobalClientConfig, JsonClientConfig, ModuleInitRegistry,
39};
40use fedimint_core::core::{DynInput, DynOutput, ModuleInstanceId, ModuleKind, OperationId};
41use fedimint_core::db::{
42 AutocommitError, Database, DatabaseRecord, DatabaseTransaction,
43 IDatabaseTransactionOpsCore as _, IDatabaseTransactionOpsCoreTyped as _, NonCommittable,
44};
45use fedimint_core::encoding::{Decodable, Encodable};
46use fedimint_core::endpoint_constants::{CLIENT_CONFIG_ENDPOINT, VERSION_ENDPOINT};
47use fedimint_core::envs::is_running_in_test_env;
48use fedimint_core::invite_code::InviteCode;
49use fedimint_core::module::registry::{ModuleDecoderRegistry, ModuleRegistry};
50use fedimint_core::module::{
51 AmountUnit, Amounts, ApiRequestErased, ApiVersion, MultiApiVersion,
52 SupportedApiVersionsSummary, SupportedCoreApiVersions, SupportedModuleApiVersions,
53};
54use fedimint_core::net::api_announcement::SignedApiAnnouncement;
55use fedimint_core::runtime::sleep;
56use fedimint_core::task::{
57 Elapsed, MaybeSend, MaybeSync, ShuttingDownError, TaskGroup, TaskHandle,
58};
59use fedimint_core::transaction::Transaction;
60use fedimint_core::util::backoff_util::custom_backoff;
61use fedimint_core::util::{
62 BoxStream, FmtCompact as _, FmtCompactAnyhow as _, SafeUrl, backoff_util, retry,
63};
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, EventKind, EventLogEntry,
71 EventLogId, EventLogTrimableId, EventLogTrimableTracker, EventPersistence, PersistedLogEntry,
72};
73use fedimint_logging::{LOG_CLIENT, LOG_CLIENT_NET_API, LOG_CLIENT_RECOVERY};
74use futures::stream::FuturesUnordered;
75use futures::{Stream, StreamExt as _};
76use global_ctx::ModuleGlobalClientContext;
77use serde::{Deserialize, Serialize};
78use tokio::sync::{broadcast, oneshot, watch};
79use tokio_stream::wrappers::WatchStream;
80use tracing::{Span, debug, info, warn};
81
82use crate::ClientBuilder;
83use crate::api_announcements::{ApiAnnouncementPrefix, get_api_urls};
84use crate::backup::Metadata;
85use crate::client::event_log::DefaultApplicationEventLogKey;
86use crate::db::{
87 ApiSecretKey, CachedApiVersionSet, CachedApiVersionSetKey, ChainIdKey,
88 ChronologicalOperationLogKey, ClientConfigKey, ClientMetadataKey, ClientModuleRecovery,
89 ClientModuleRecoveryState, EncodedClientSecretKey, OperationLogKey, PeerLastApiVersionsSummary,
90 PeerLastApiVersionsSummaryKey, PendingClientConfigKey, TransactionFeesKey,
91 apply_migrations_core_client_dbtx, get_decoded_client_secret, verify_client_db_integrity_dbtx,
92};
93use crate::meta::MetaService;
94use crate::module_init::{ClientModuleInitRegistry, DynClientModuleInit, IClientModuleInit};
95use crate::oplog::OperationLog;
96use crate::sm::executor::{
97 ActiveModuleOperationStateKeyPrefix, ActiveOperationStateKeyPrefix, Executor,
98 InactiveModuleOperationStateKeyPrefix, InactiveOperationStateKeyPrefix,
99};
100
101pub(crate) mod builder;
102pub(crate) mod event_log;
103pub(crate) mod global_ctx;
104pub(crate) mod handle;
105
106#[cfg(test)]
107mod tests;
108
109const SUPPORTED_CORE_API_VERSIONS: &[fedimint_core::module::ApiVersion] =
113 &[ApiVersion { major: 0, minor: 0 }];
114
115struct FinalizedTransaction {
116 transaction: Transaction,
117 states: Vec<DynState>,
118 change_range: Range<u64>,
119 fees: Amounts,
120}
121
122#[derive(Default)]
124pub(crate) struct PrimaryModuleCandidates {
125 specific: BTreeMap<AmountUnit, Vec<ModuleInstanceId>>,
127 wildcard: Vec<ModuleInstanceId>,
129}
130
131pub(crate) type ModuleRecoveryFuture =
134 Pin<Box<maybe_add_send!(dyn Future<Output = anyhow::Result<Option<Amount>>>)>>;
135
136#[derive(Clone, Debug)]
161pub(crate) enum RecoveryStatus {
162 InProgress(RecoveryProgress),
166 Failed {
169 last_progress: RecoveryProgress,
170 error: String,
171 },
172}
173
174impl RecoveryStatus {
175 pub(crate) fn is_successfully_done(&self) -> bool {
180 match self {
181 Self::InProgress(progress) => progress.is_done(),
182 Self::Failed { .. } => false,
183 }
184 }
185
186 pub(crate) fn progress(&self) -> RecoveryProgress {
189 match self {
190 Self::InProgress(progress)
191 | Self::Failed {
192 last_progress: progress,
193 ..
194 } => *progress,
195 }
196 }
197}
198
199pub struct Client {
213 final_client: FinalClientIface,
214 config: tokio::sync::RwLock<ClientConfig>,
215 api_secret: Option<String>,
216 decoders: ModuleDecoderRegistry,
217 connectors: ConnectorRegistry,
218 db: Database,
219 federation_id: FederationId,
220 federation_config_meta: BTreeMap<String, String>,
221 primary_modules: BTreeMap<PrimaryModulePriority, PrimaryModuleCandidates>,
222 pub(crate) modules: ClientModuleRegistry,
223 module_inits: ClientModuleInitRegistry,
224 executor: Executor,
225 pub(crate) api: DynGlobalApi,
226 root_secret: DerivableSecret,
227 operation_log: OperationLog,
228 secp_ctx: Secp256k1<secp256k1::All>,
229 meta_service: Arc<MetaService>,
230
231 task_group: TaskGroup,
232
233 client_span: Span,
237
238 client_recovery_status_receiver: watch::Receiver<BTreeMap<ModuleInstanceId, RecoveryStatus>>,
251
252 log_ordering_wakeup_tx: watch::Sender<()>,
255 log_event_added_rx: watch::Receiver<()>,
257 log_event_added_transient_tx: broadcast::Sender<EventLogEntry>,
258 request_hook: ApiRequestHook,
259 iroh_enable_dht: bool,
260 iroh_enable_next: bool,
261 #[allow(dead_code)]
266 user_bitcoind_rpc: Option<DynBitcoindRpc>,
267 pub(crate) user_bitcoind_rpc_no_chain_id:
272 Option<fedimint_client_module::module::init::BitcoindRpcNoChainIdFactory>,
273}
274
275#[derive(Debug, Serialize, Deserialize)]
276struct ListOperationsParams {
277 limit: Option<usize>,
278 last_seen: Option<ChronologicalOperationLogKey>,
279}
280
281pub const DEFAULT_EVENT_LOG_PAGE_SIZE: u64 = 100;
282pub const MAX_EVENT_LOG_PAGE_SIZE: u64 = 10_000;
283
284#[derive(Debug, Clone, Serialize, Deserialize)]
285struct GetEventLogRequest {
286 pos: Option<EventLogId>,
287 limit: Option<u64>,
288}
289
290#[derive(Debug, Clone, Serialize, Deserialize)]
291pub struct GetOperationIdRequest {
292 operation_id: OperationId,
293}
294
295#[derive(Debug, Clone, Serialize, Deserialize)]
296pub struct GetBalanceChangesRequest {
297 #[serde(default = "AmountUnit::bitcoin")]
298 unit: AmountUnit,
299}
300
301impl Client {
302 pub async fn builder() -> anyhow::Result<ClientBuilder> {
305 Ok(ClientBuilder::new())
306 }
307
308 pub fn api(&self) -> &(dyn IGlobalFederationApi + 'static) {
309 self.api.as_ref()
310 }
311
312 pub fn api_clone(&self) -> DynGlobalApi {
313 self.api.clone()
314 }
315
316 pub fn connection_status_stream(&self) -> impl Stream<Item = BTreeMap<PeerId, PeerStatus>> {
319 self.api.connection_status_stream()
320 }
321
322 pub fn federation_reconnect(&self) {
330 let peers: Vec<PeerId> = self.api.all_peers().iter().copied().collect();
331
332 for peer_id in peers {
333 let api = self.api.clone();
334 self.spawn_cancellable(format!("federation-reconnect-once-{peer_id}"), async move {
335 if let Err(e) = api.get_peer_connection(peer_id).await {
336 debug!(
337 target: LOG_CLIENT_NET_API,
338 %peer_id,
339 err = %e.fmt_compact(),
340 "Failed to connect to peer"
341 );
342 }
343 });
344 }
345 }
346
347 pub fn spawn_federation_reconnect(&self) {
369 let peers: Vec<PeerId> = self.api.all_peers().iter().copied().collect();
370
371 for peer_id in peers {
372 let api = self.api.clone();
373 self.spawn_cancellable(format!("federation-reconnect-{peer_id}"), async move {
374 loop {
375 match api.get_peer_connection(peer_id).await {
376 Ok(conn) => {
377 conn.await_disconnection().await;
378 }
379 Err(e) => {
380 debug!(
383 target: LOG_CLIENT_NET_API,
384 %peer_id,
385 err = %e.fmt_compact(),
386 "Failed to connect to peer, will retry"
387 );
388 }
389 }
390 }
391 });
392 }
393 }
394
395 pub fn task_group(&self) -> &TaskGroup {
397 &self.task_group
398 }
399
400 pub(crate) fn make_client_span(federation_id: FederationId) -> Span {
407 tracing::info_span!(
408 target: LOG_CLIENT,
409 parent: None,
410 "client",
411 fed_id = %federation_id.to_prefix(),
412 )
413 }
414
415 pub(crate) fn spawn_cancellable<R>(
418 &self,
419 name: impl Into<String>,
420 future: impl Future<Output = R> + MaybeSend + 'static,
421 ) -> oneshot::Receiver<Result<R, ShuttingDownError>>
422 where
423 R: MaybeSend + 'static,
424 {
425 self.task_group
426 .spawn_cancellable_with_span(self.client_span.clone(), name, future)
427 }
428
429 pub(crate) fn spawn<Fut, R>(
433 &self,
434 name: impl Into<String>,
435 f: impl FnOnce(TaskHandle) -> Fut + MaybeSend + 'static,
436 ) -> oneshot::Receiver<R>
437 where
438 Fut: Future<Output = R> + MaybeSend + 'static,
439 R: MaybeSend + 'static,
440 {
441 self.task_group
442 .spawn_with_span(self.client_span.clone(), name, f)
443 }
444
445 pub fn get_metrics() -> anyhow::Result<String> {
450 fedimint_metrics::get_metrics()
451 }
452
453 #[doc(hidden)]
455 pub fn executor(&self) -> &Executor {
456 &self.executor
457 }
458
459 pub async fn get_config_from_db(db: &Database) -> Option<ClientConfig> {
460 let mut dbtx = db.begin_transaction_nc().await;
461 dbtx.get_value(&ClientConfigKey).await
462 }
463
464 pub async fn get_pending_config_from_db(db: &Database) -> Option<ClientConfig> {
465 let mut dbtx = db.begin_transaction_nc().await;
466 dbtx.get_value(&PendingClientConfigKey).await
467 }
468
469 pub async fn get_api_secret_from_db(db: &Database) -> Option<String> {
470 let mut dbtx = db.begin_transaction_nc().await;
471 dbtx.get_value(&ApiSecretKey).await
472 }
473
474 pub async fn store_encodable_client_secret<T: Encodable>(
475 db: &Database,
476 secret: T,
477 ) -> anyhow::Result<()> {
478 let mut dbtx = db.begin_transaction().await;
479
480 if dbtx.get_value(&EncodedClientSecretKey).await.is_some() {
482 bail!("Encoded client secret already exists, cannot overwrite")
483 }
484
485 let encoded_secret = T::consensus_encode_to_vec(&secret);
486 dbtx.insert_entry(&EncodedClientSecretKey, &encoded_secret)
487 .await;
488 dbtx.commit_tx().await;
489 Ok(())
490 }
491
492 pub async fn load_decodable_client_secret<T: Decodable>(db: &Database) -> anyhow::Result<T> {
493 let Some(secret) = Self::load_decodable_client_secret_opt(db).await? else {
494 bail!("Encoded client secret not present in DB")
495 };
496
497 Ok(secret)
498 }
499 pub async fn load_decodable_client_secret_opt<T: Decodable>(
500 db: &Database,
501 ) -> anyhow::Result<Option<T>> {
502 let mut dbtx = db.begin_transaction_nc().await;
503
504 let client_secret = dbtx.get_value(&EncodedClientSecretKey).await;
505
506 Ok(match client_secret {
507 Some(client_secret) => Some(
508 T::consensus_decode_whole(&client_secret, &ModuleRegistry::default())
509 .context("Decoding failed")?,
510 ),
511 None => None,
512 })
513 }
514
515 pub async fn load_or_generate_client_secret(db: &Database) -> anyhow::Result<[u8; 64]> {
516 let client_secret = match Self::load_decodable_client_secret::<[u8; 64]>(db).await {
517 Ok(secret) => secret,
518 _ => {
519 let secret = PlainRootSecretStrategy::random(&mut thread_rng());
520 Self::store_encodable_client_secret(db, secret)
521 .await
522 .expect("Storing client secret must work");
523 secret
524 }
525 };
526 Ok(client_secret)
527 }
528
529 pub async fn is_initialized(db: &Database) -> bool {
530 let mut dbtx = db.begin_transaction_nc().await;
531 dbtx.raw_get_bytes(&[ClientConfigKey::DB_PREFIX])
532 .await
533 .expect("Unrecoverable error occurred while reading and entry from the database")
534 .is_some()
535 }
536
537 pub fn start_executor(self: &Arc<Self>) {
538 self.client_span.in_scope(|| {
539 debug!(
540 target: LOG_CLIENT,
541 "Starting fedimint client executor",
542 );
543 });
544 self.executor
545 .start_executor(self.context_gen(), self.client_span.clone());
546 }
547
548 pub fn federation_id(&self) -> FederationId {
549 self.federation_id
550 }
551
552 fn context_gen(self: &Arc<Self>) -> ModuleGlobalContextGen {
553 let client_inner = Arc::downgrade(self);
554 Arc::new(move |module_instance, operation| {
555 ModuleGlobalClientContext {
556 client: client_inner
557 .clone()
558 .upgrade()
559 .expect("ModuleGlobalContextGen called after client was dropped"),
560 module_instance_id: module_instance,
561 operation,
562 }
563 .into()
564 })
565 }
566
567 pub async fn config(&self) -> ClientConfig {
568 self.config.read().await.clone()
569 }
570
571 pub fn api_secret(&self) -> &Option<String> {
573 &self.api_secret
574 }
575
576 pub async fn core_api_version(&self) -> ApiVersion {
582 self.db
585 .begin_transaction_nc()
586 .await
587 .get_value(&CachedApiVersionSetKey)
588 .await
589 .map(|cached: CachedApiVersionSet| cached.0.core)
590 .unwrap_or(ApiVersion { major: 0, minor: 0 })
591 }
592
593 pub async fn chain_id(&self) -> anyhow::Result<ChainId> {
600 if let Some(chain_id) = self
602 .db
603 .begin_transaction_nc()
604 .await
605 .get_value(&ChainIdKey)
606 .await
607 {
608 return Ok(chain_id);
609 }
610
611 let chain_id = self.api.chain_id().await?;
613
614 let mut dbtx = self.db.begin_transaction().await;
616 dbtx.insert_entry(&ChainIdKey, &chain_id).await;
617 dbtx.commit_tx().await;
618
619 Ok(chain_id)
620 }
621
622 pub fn decoders(&self) -> &ModuleDecoderRegistry {
623 &self.decoders
624 }
625
626 fn get_module(&self, instance: ModuleInstanceId) -> &maybe_add_send_sync!(dyn IClientModule) {
628 self.try_get_module(instance)
629 .expect("Module instance not found")
630 }
631
632 fn try_get_module(
633 &self,
634 instance: ModuleInstanceId,
635 ) -> Option<&maybe_add_send_sync!(dyn IClientModule)> {
636 Some(self.modules.get(instance)?.as_ref())
637 }
638
639 pub fn has_module(&self, instance: ModuleInstanceId) -> bool {
640 self.modules.get(instance).is_some()
641 }
642
643 fn transaction_builder_get_balance(&self, builder: &TransactionBuilder) -> (Amounts, Amounts) {
649 let mut in_amounts = Amounts::ZERO;
651 let mut out_amounts = Amounts::ZERO;
652 let mut fee_amounts = Amounts::ZERO;
653
654 for input in builder.inputs() {
655 let module = self.get_module(input.input.module_instance_id());
656
657 let item_fees = module.input_fee(&input.amounts, &input.input).expect(
658 "We only build transactions with input versions that are supported by the module",
659 );
660
661 in_amounts.checked_add_mut(&input.amounts);
662 fee_amounts.checked_add_mut(&item_fees);
663 }
664
665 for output in builder.outputs() {
666 let module = self.get_module(output.output.module_instance_id());
667
668 let item_fees = module.output_fee(&output.amounts, &output.output).expect(
669 "We only build transactions with output versions that are supported by the module",
670 );
671
672 out_amounts.checked_add_mut(&output.amounts);
673 fee_amounts.checked_add_mut(&item_fees);
674 }
675
676 out_amounts.checked_add_mut(&fee_amounts);
677 (in_amounts, out_amounts)
678 }
679
680 pub fn get_internal_payment_markers(&self) -> anyhow::Result<(PublicKey, u64)> {
681 Ok((self.federation_id().to_fake_ln_pub_key(&self.secp_ctx)?, 0))
682 }
683
684 pub fn get_config_meta(&self, key: &str) -> Option<String> {
686 self.federation_config_meta.get(key).cloned()
687 }
688
689 pub(crate) fn root_secret(&self) -> DerivableSecret {
690 self.root_secret.clone()
691 }
692
693 pub async fn add_state_machines(
694 &self,
695 dbtx: &mut DatabaseTransaction<'_>,
696 states: Vec<DynState>,
697 ) -> AddStateMachinesResult {
698 self.executor.add_state_machines_dbtx(dbtx, states).await
699 }
700
701 pub async fn get_active_operations(&self) -> HashSet<OperationId> {
703 let active_states = self.executor.get_active_states().await;
704 let mut active_operations = HashSet::with_capacity(active_states.len());
705 let mut dbtx = self.db().begin_transaction_nc().await;
706 for (state, _) in active_states {
707 let operation_id = state.operation_id();
708 if dbtx
709 .get_value(&OperationLogKey { operation_id })
710 .await
711 .is_some()
712 {
713 active_operations.insert(operation_id);
714 }
715 }
716 active_operations
717 }
718
719 pub fn operation_log(&self) -> &OperationLog {
720 &self.operation_log
721 }
722
723 pub fn meta_service(&self) -> &Arc<MetaService> {
725 &self.meta_service
726 }
727
728 pub async fn get_meta_expiration_timestamp(&self) -> Option<SystemTime> {
730 let meta_service = self.meta_service();
731 let ts = meta_service
732 .get_field::<u64>(self.db(), "federation_expiry_timestamp")
733 .await
734 .and_then(|v| v.value)?;
735 Some(UNIX_EPOCH + Duration::from_secs(ts))
736 }
737
738 async fn finalize_transaction(
740 &self,
741 dbtx: &mut DatabaseTransaction<'_>,
742 operation_id: OperationId,
743 mut partial_transaction: TransactionBuilder,
744 ) -> anyhow::Result<FinalizedTransaction> {
745 let (in_amounts, out_amounts) = self.transaction_builder_get_balance(&partial_transaction);
746
747 let mut added_inputs_bundles = vec![];
748 let mut added_outputs_bundles = vec![];
749
750 for unit in in_amounts.units().union(&out_amounts.units()) {
761 let input_amount = in_amounts.get(unit).copied().unwrap_or_default();
762 let output_amount = out_amounts.get(unit).copied().unwrap_or_default();
763 if input_amount == output_amount {
764 continue;
765 }
766
767 let Some((module_id, module)) = self.primary_module_for_unit(*unit) else {
768 bail!("No module to balance a partial transaction (affected unit: {unit:?}");
769 };
770
771 let (added_input_bundle, added_output_bundle) = module
772 .create_final_inputs_and_outputs(
773 module_id,
774 dbtx,
775 operation_id,
776 *unit,
777 input_amount,
778 output_amount,
779 )
780 .await?;
781
782 added_inputs_bundles.push(added_input_bundle);
783 added_outputs_bundles.push(added_output_bundle);
784 }
785
786 let change_range = Range {
790 start: partial_transaction.outputs().count() as u64,
791 end: (partial_transaction.outputs().count() as u64
792 + added_outputs_bundles
793 .iter()
794 .map(|output| output.outputs().len() as u64)
795 .sum::<u64>()),
796 };
797
798 for added_inputs in added_inputs_bundles {
799 partial_transaction = partial_transaction.with_inputs(added_inputs);
800 }
801
802 for added_outputs in added_outputs_bundles {
803 partial_transaction = partial_transaction.with_outputs(added_outputs);
804 }
805
806 let (input_amounts, output_amounts) =
807 self.transaction_builder_get_balance(&partial_transaction);
808
809 for (unit, output_amount) in output_amounts {
810 let input_amount = input_amounts.get(&unit).copied().unwrap_or_default();
811
812 assert!(input_amount >= output_amount, "Transaction is underfunded");
813 }
814
815 let fees = {
819 let mut input_total = Amounts::ZERO;
820 for input in partial_transaction.inputs() {
821 input_total
822 .checked_add_mut(&input.amounts)
823 .expect("Own transaction amounts don't overflow");
824 }
825 let mut output_total = Amounts::ZERO;
826 for output in partial_transaction.outputs() {
827 output_total
828 .checked_add_mut(&output.amounts)
829 .expect("Own transaction amounts don't overflow");
830 }
831 input_total
832 .checked_sub(&output_total)
833 .expect("Inputs >= outputs for own transactions")
834 };
835
836 let (transaction, states) = partial_transaction.build(&self.secp_ctx, thread_rng());
837
838 Ok(FinalizedTransaction {
839 transaction,
840 states,
841 change_range,
842 fees,
843 })
844 }
845
846 pub async fn fee_quote(
866 &self,
867 operation_id: OperationId,
868 request: FeeQuoteRequest,
869 ) -> anyhow::Result<FeeQuote> {
870 let FeeQuoteRequest {
871 input_amount,
872 output_amount,
873 input_fee,
874 output_fee,
875 } = request;
876
877 let mut gross_input = input_amount.clone();
881 let mut gross_output = output_amount.clone();
882 let mut input_fees = input_fee.clone();
883 let mut output_fees = output_fee.clone();
884
885 let balance_input = input_amount;
891 let balance_output = output_amount
892 .checked_add(&input_fee)
893 .and_then(|amounts| amounts.checked_add(&output_fee))
894 .expect("explicit amounts and fees cannot overflow an Amounts");
895
896 let mut dbtx = self.db.begin_transaction_nc().await;
900
901 for unit in balance_input.units().union(&balance_output.units()) {
904 let balance_input_amount = balance_input.get(unit).copied().unwrap_or_default();
905 let balance_output_amount = balance_output.get(unit).copied().unwrap_or_default();
906 if balance_input_amount == balance_output_amount {
907 continue;
908 }
909
910 let Some((module_id, module)) = self.primary_module_for_unit(*unit) else {
911 bail!("No module to balance a partial transaction (affected unit: {unit:?}");
912 };
913
914 let (change_input, change_output) = module
915 .create_final_inputs_and_outputs(
916 module_id,
917 &mut dbtx.to_ref_nc(),
918 operation_id,
919 *unit,
920 balance_input_amount,
921 balance_output_amount,
922 )
923 .await?;
924
925 for input in change_input.inputs() {
931 let module = self.get_module(input.input.module_instance_id());
932 let fee = module
933 .input_fee(&input.amounts, &input.input)
934 .expect("Primary module must know its own change input fees");
935 gross_input.checked_add_mut(&input.amounts);
936 input_fees.checked_add_mut(&fee);
937 }
938
939 for output in change_output.outputs() {
940 let module = self.get_module(output.output.module_instance_id());
941 let fee = module
942 .output_fee(&output.amounts, &output.output)
943 .expect("Primary module must know its own change output fees");
944 gross_output.checked_add_mut(&output.amounts);
945 output_fees.checked_add_mut(&fee);
946 }
947 }
948
949 dbtx.ignore_uncommitted();
952
953 let mut dust = Amounts::ZERO;
958 for unit in gross_input.units().union(&gross_output.units()) {
959 let total = gross_input
960 .get(unit)
961 .copied()
962 .unwrap_or_default()
963 .saturating_sub(gross_output.get(unit).copied().unwrap_or_default());
964 let fees = input_fees.get(unit).copied().unwrap_or_default()
965 + output_fees.get(unit).copied().unwrap_or_default();
966 dust = dust
967 .checked_add_unit(total.saturating_sub(fees), *unit)
968 .expect("dust cannot overflow an Amounts");
969 }
970
971 Ok(FeeQuote {
972 input: input_fees,
973 output: output_fees,
974 dust,
975 })
976 }
977
978 pub async fn finalize_and_submit_transaction<F, M>(
999 &self,
1000 operation_id: OperationId,
1001 operation_type: &str,
1002 operation_meta_gen: F,
1003 tx_builder: TransactionBuilder,
1004 ) -> anyhow::Result<OutPointRange>
1005 where
1006 F: Fn(OutPointRange) -> M + Clone + MaybeSend + MaybeSync,
1007 M: serde::Serialize + MaybeSend,
1008 {
1009 let operation_type = operation_type.to_owned();
1010
1011 let autocommit_res = self
1012 .db
1013 .autocommit(
1014 |dbtx, _| {
1015 let operation_type = operation_type.clone();
1016 let tx_builder = tx_builder.clone();
1017 let operation_meta_gen = operation_meta_gen.clone();
1018 Box::pin(async move {
1019 self.finalize_and_submit_transaction_dbtx(
1020 dbtx,
1021 operation_id,
1022 &operation_type,
1023 operation_meta_gen,
1024 tx_builder,
1025 )
1026 .await
1027 })
1028 },
1029 Some(100), )
1031 .await;
1032
1033 match autocommit_res {
1034 Ok(txid) => Ok(txid),
1035 Err(AutocommitError::ClosureError { error, .. }) => Err(error),
1036 Err(AutocommitError::CommitFailed {
1037 attempts,
1038 last_error,
1039 }) => panic!(
1040 "Failed to commit tx submission dbtx after {attempts} attempts: {last_error}"
1041 ),
1042 }
1043 }
1044
1045 pub async fn finalize_and_submit_transaction_dbtx<F, M>(
1054 &self,
1055 dbtx: &mut DatabaseTransaction<'_>,
1056 operation_id: OperationId,
1057 operation_type: &str,
1058 operation_meta_gen: F,
1059 tx_builder: TransactionBuilder,
1060 ) -> anyhow::Result<OutPointRange>
1061 where
1062 F: FnOnce(OutPointRange) -> M + MaybeSend,
1063 M: serde::Serialize + MaybeSend,
1064 {
1065 if Client::operation_exists_dbtx(dbtx, operation_id).await {
1066 bail!("There already exists an operation with id {operation_id:?}")
1067 }
1068
1069 let out_point_range = self
1070 .finalize_and_submit_transaction_inner(dbtx, operation_id, tx_builder)
1071 .await?;
1072
1073 self.operation_log()
1074 .add_operation_log_entry_dbtx(
1075 dbtx,
1076 operation_id,
1077 operation_type,
1078 operation_meta_gen(out_point_range),
1079 )
1080 .await;
1081
1082 Ok(out_point_range)
1083 }
1084
1085 async fn finalize_and_submit_transaction_inner(
1086 &self,
1087 dbtx: &mut DatabaseTransaction<'_>,
1088 operation_id: OperationId,
1089 tx_builder: TransactionBuilder,
1090 ) -> anyhow::Result<OutPointRange> {
1091 let FinalizedTransaction {
1092 transaction,
1093 mut states,
1094 change_range,
1095 fees,
1096 } = self
1097 .finalize_transaction(&mut dbtx.to_ref_nc(), operation_id, tx_builder)
1098 .await?;
1099
1100 if transaction.consensus_encode_to_vec().len() > Transaction::MAX_TX_SIZE {
1101 let inputs = transaction
1102 .inputs
1103 .iter()
1104 .map(DynInput::module_instance_id)
1105 .collect::<Vec<_>>();
1106 let outputs = transaction
1107 .outputs
1108 .iter()
1109 .map(DynOutput::module_instance_id)
1110 .collect::<Vec<_>>();
1111 warn!(
1112 target: LOG_CLIENT_NET_API,
1113 size=%transaction.consensus_encode_to_vec().len(),
1114 ?inputs,
1115 ?outputs,
1116 "Transaction too large",
1117 );
1118 debug!(target: LOG_CLIENT_NET_API, ?transaction, "transaction details");
1119 bail!(
1120 "The generated transaction would be rejected by the federation for being too large."
1121 );
1122 }
1123
1124 let txid = transaction.tx_hash();
1125
1126 debug!(
1127 target: LOG_CLIENT_NET_API,
1128 %txid,
1129 operation_id = %operation_id.fmt_short(),
1130 ?transaction,
1131 "Finalized and submitting transaction",
1132 );
1133
1134 let tx_submission_sm = DynState::from_typed(
1135 TRANSACTION_SUBMISSION_MODULE_INSTANCE,
1136 TxSubmissionStatesSM {
1137 operation_id,
1138 state: TxSubmissionStates::Created(transaction),
1139 },
1140 );
1141 states.push(tx_submission_sm);
1142
1143 self.executor.add_state_machines_dbtx(dbtx, states).await?;
1144
1145 dbtx.insert_new_entry(&TransactionFeesKey(txid), &fees)
1146 .await;
1147
1148 self.log_event_dbtx(dbtx, None, TxCreatedEvent { txid, operation_id })
1149 .await;
1150
1151 Ok(OutPointRange::new(txid, IdxRange::from(change_range)))
1152 }
1153
1154 async fn transaction_update_stream(
1155 &self,
1156 operation_id: OperationId,
1157 ) -> BoxStream<'static, TxSubmissionStatesSM> {
1158 self.executor
1159 .notifier()
1160 .module_notifier::<TxSubmissionStatesSM>(
1161 TRANSACTION_SUBMISSION_MODULE_INSTANCE,
1162 self.final_client.clone(),
1163 )
1164 .subscribe(operation_id)
1165 .await
1166 }
1167
1168 pub async fn operation_exists(&self, operation_id: OperationId) -> bool {
1169 let mut dbtx = self.db().begin_transaction_nc().await;
1170
1171 Client::operation_exists_dbtx(&mut dbtx, operation_id).await
1172 }
1173
1174 pub async fn operation_exists_dbtx(
1175 dbtx: &mut DatabaseTransaction<'_>,
1176 operation_id: OperationId,
1177 ) -> bool {
1178 let active_state_exists = dbtx
1179 .find_by_prefix(&ActiveOperationStateKeyPrefix { operation_id })
1180 .await
1181 .next()
1182 .await
1183 .is_some();
1184
1185 let inactive_state_exists = dbtx
1186 .find_by_prefix(&InactiveOperationStateKeyPrefix { operation_id })
1187 .await
1188 .next()
1189 .await
1190 .is_some();
1191
1192 active_state_exists || inactive_state_exists
1193 }
1194
1195 pub async fn has_active_states(&self, operation_id: OperationId) -> bool {
1196 self.db
1197 .begin_transaction_nc()
1198 .await
1199 .find_by_prefix(&ActiveOperationStateKeyPrefix { operation_id })
1200 .await
1201 .next()
1202 .await
1203 .is_some()
1204 }
1205
1206 pub async fn get_operation_fees(
1225 &self,
1226 operation_id: OperationId,
1227 ) -> anyhow::Result<Option<Amounts>> {
1228 if !self.operation_exists(operation_id).await {
1229 bail!("Operation does not exist");
1230 }
1231
1232 let (active_states, inactive_states) =
1233 self.executor().get_operation_states(operation_id).await;
1234
1235 let states = active_states
1236 .into_iter()
1237 .map(|(state, _)| state)
1238 .chain(inactive_states.into_iter().map(|(state, _)| state));
1239
1240 let accepted_transactions = states
1241 .filter_map(|state| {
1242 let tx_state = state.as_any().downcast_ref::<TxSubmissionStatesSM>()?;
1243
1244 match &tx_state.state {
1245 TxSubmissionStates::Accepted(transaction_id) => Some(*transaction_id),
1246 _ => None,
1247 }
1248 })
1249 .collect::<HashSet<_>>();
1250
1251 let mut dbtx = self.db.begin_transaction_nc().await;
1253 let mut total_fees = Amounts::ZERO;
1254 for txid in &accepted_transactions {
1255 let Some(fees) = dbtx.get_value(&TransactionFeesKey(*txid)).await else {
1256 return Ok(None);
1257 };
1258 total_fees = total_fees
1259 .checked_add(&fees)
1260 .expect("Fee amounts don't overflow in practice");
1261 }
1262
1263 Ok(Some(total_fees))
1264 }
1265
1266 pub async fn await_primary_bitcoin_module_output(
1269 &self,
1270 operation_id: OperationId,
1271 out_point: OutPoint,
1272 ) -> anyhow::Result<()> {
1273 self.primary_module_for_unit(AmountUnit::BITCOIN)
1274 .ok_or_else(|| anyhow!("No primary module available"))?
1275 .1
1276 .await_primary_module_output(operation_id, out_point)
1277 .await
1278 }
1279
1280 pub fn get_first_module<M: ClientModule>(
1282 &'_ self,
1283 ) -> anyhow::Result<ClientModuleInstance<'_, M>> {
1284 let module_kind = M::kind();
1285 let id = self
1286 .get_first_instance(&module_kind)
1287 .ok_or_else(|| format_err!("No modules found of kind {module_kind}"))?;
1288 let module: &M = self
1289 .try_get_module(id)
1290 .ok_or_else(|| format_err!("Unknown module instance {id}"))?
1291 .as_any()
1292 .downcast_ref::<M>()
1293 .ok_or_else(|| format_err!("Module is not of type {}", std::any::type_name::<M>()))?;
1294 let (db, _) = self.db().with_prefix_module_id(id);
1295 Ok(ClientModuleInstance {
1296 id,
1297 db,
1298 api: self.api().with_module(id),
1299 module,
1300 })
1301 }
1302
1303 #[cfg(not(target_family = "wasm"))]
1308 pub fn get_first_module_arc<M: ClientModule>(&self) -> anyhow::Result<Arc<M>> {
1309 let module_kind = M::kind();
1310 let id = self
1311 .get_first_instance(&module_kind)
1312 .ok_or_else(|| format_err!("No modules found of kind {module_kind}"))?;
1313 let dyn_module = self
1314 .modules
1315 .get(id)
1316 .ok_or_else(|| format_err!("Unknown module instance {id}"))?;
1317 dyn_module
1318 .as_any_arc()
1319 .downcast::<M>()
1320 .map_err(|_| format_err!("Module is not of type {}", std::any::type_name::<M>()))
1321 }
1322
1323 pub fn get_module_client_dyn(
1324 &self,
1325 instance_id: ModuleInstanceId,
1326 ) -> anyhow::Result<&maybe_add_send_sync!(dyn IClientModule)> {
1327 self.try_get_module(instance_id)
1328 .ok_or(anyhow!("Unknown module instance {}", instance_id))
1329 }
1330
1331 pub fn db(&self) -> &Database {
1332 &self.db
1333 }
1334
1335 pub fn endpoints(&self) -> &ConnectorRegistry {
1336 &self.connectors
1337 }
1338
1339 pub async fn transaction_updates(&self, operation_id: OperationId) -> TransactionUpdates {
1342 TransactionUpdates {
1343 update_stream: self.transaction_update_stream(operation_id).await,
1344 }
1345 }
1346
1347 pub fn get_first_instance(&self, module_kind: &ModuleKind) -> Option<ModuleInstanceId> {
1349 self.modules
1350 .iter_modules()
1351 .find(|(_, kind, _module)| *kind == module_kind)
1352 .map(|(instance_id, _, _)| instance_id)
1353 }
1354
1355 pub async fn root_secret_encoding<T: Decodable>(&self) -> anyhow::Result<T> {
1358 get_decoded_client_secret::<T>(self.db()).await
1359 }
1360
1361 pub async fn await_primary_bitcoin_module_outputs(
1364 &self,
1365 operation_id: OperationId,
1366 outputs: Vec<OutPoint>,
1367 ) -> anyhow::Result<()> {
1368 for out_point in outputs {
1369 self.await_primary_bitcoin_module_output(operation_id, out_point)
1370 .await?;
1371 }
1372
1373 Ok(())
1374 }
1375
1376 pub async fn get_config_json(&self) -> JsonClientConfig {
1382 self.config().await.to_json()
1383 }
1384
1385 #[doc(hidden)]
1388 pub async fn get_balance_for_btc(&self) -> anyhow::Result<Amount> {
1391 self.get_balance_for_unit(AmountUnit::BITCOIN).await
1392 }
1393
1394 pub async fn get_balance_for_unit(&self, unit: AmountUnit) -> anyhow::Result<Amount> {
1395 let (id, module) = self
1396 .primary_module_for_unit(unit)
1397 .ok_or_else(|| anyhow!("Primary module not available"))?;
1398 Ok(module
1399 .get_balance(id, &mut self.db().begin_transaction_nc().await, unit)
1400 .await)
1401 }
1402
1403 pub async fn subscribe_balance_changes(&self, unit: AmountUnit) -> BoxStream<'static, Amount> {
1406 let primary_module_things =
1407 if let Some((primary_module_id, primary_module)) = self.primary_module_for_unit(unit) {
1408 let balance_changes = primary_module.subscribe_balance_changes().await;
1409 let initial_balance = self
1410 .get_balance_for_unit(unit)
1411 .await
1412 .expect("Primary is present");
1413
1414 Some((
1415 primary_module_id,
1416 primary_module.clone(),
1417 balance_changes,
1418 initial_balance,
1419 ))
1420 } else {
1421 None
1422 };
1423 let db = self.db().clone();
1424
1425 Box::pin(async_stream::stream! {
1426 let Some((primary_module_id, primary_module, mut balance_changes, initial_balance)) = primary_module_things else {
1427 pending().await
1430 };
1431
1432
1433 yield initial_balance;
1434 let mut prev_balance = initial_balance;
1435 while let Some(()) = balance_changes.next().await {
1436 let mut dbtx = db.begin_transaction_nc().await;
1437 let balance = primary_module
1438 .get_balance(primary_module_id, &mut dbtx, unit)
1439 .await;
1440
1441 if balance != prev_balance {
1443 prev_balance = balance;
1444 yield balance;
1445 }
1446 }
1447 })
1448 }
1449
1450 async fn make_api_version_request(
1455 delay: Duration,
1456 peer_id: PeerId,
1457 api: &DynGlobalApi,
1458 ) -> (
1459 PeerId,
1460 Result<SupportedApiVersionsSummary, fedimint_connectors::error::ServerError>,
1461 ) {
1462 runtime::sleep(delay).await;
1463 (
1464 peer_id,
1465 api.request_single_peer::<SupportedApiVersionsSummary>(
1466 VERSION_ENDPOINT.to_owned(),
1467 ApiRequestErased::default(),
1468 peer_id,
1469 )
1470 .await,
1471 )
1472 }
1473
1474 fn create_api_version_backoff() -> impl Iterator<Item = Duration> {
1480 custom_backoff(Duration::from_millis(200), Duration::from_secs(600), None)
1481 }
1482
1483 pub async fn fetch_common_api_versions_from_all_peers(
1486 num_peers: NumPeers,
1487 api: DynGlobalApi,
1488 db: Database,
1489 num_responses_sender: watch::Sender<usize>,
1490 ) {
1491 let mut backoff = Self::create_api_version_backoff();
1492
1493 let mut requests = FuturesUnordered::new();
1496
1497 for peer_id in num_peers.peer_ids() {
1498 requests.push(Self::make_api_version_request(
1499 Duration::ZERO,
1500 peer_id,
1501 &api,
1502 ));
1503 }
1504
1505 let mut num_responses = 0;
1506
1507 while let Some((peer_id, response)) = requests.next().await {
1508 let retry = match response {
1509 Err(err) => {
1510 let has_previous_response = db
1511 .begin_transaction_nc()
1512 .await
1513 .get_value(&PeerLastApiVersionsSummaryKey(peer_id))
1514 .await
1515 .is_some();
1516 debug!(
1517 target: LOG_CLIENT,
1518 %peer_id,
1519 err = %err.fmt_compact(),
1520 %has_previous_response,
1521 "Failed to refresh API versions of a peer"
1522 );
1523
1524 !has_previous_response
1525 }
1526 Ok(o) => {
1527 let mut dbtx = db.begin_transaction().await;
1530 dbtx.insert_entry(
1531 &PeerLastApiVersionsSummaryKey(peer_id),
1532 &PeerLastApiVersionsSummary(o),
1533 )
1534 .await;
1535 dbtx.commit_tx().await;
1536 false
1537 }
1538 };
1539
1540 if retry {
1541 requests.push(Self::make_api_version_request(
1542 backoff.next().expect("Keeps retrying"),
1543 peer_id,
1544 &api,
1545 ));
1546 } else {
1547 num_responses += 1;
1548 num_responses_sender.send_replace(num_responses);
1549 }
1550 }
1551 }
1552
1553 pub async fn fetch_peers_api_versions_from_threshold_of_peers(
1557 num_peers: NumPeers,
1558 api: DynGlobalApi,
1559 ) -> BTreeMap<PeerId, SupportedApiVersionsSummary> {
1560 let mut backoff = Self::create_api_version_backoff();
1561
1562 let mut requests = FuturesUnordered::new();
1565
1566 for peer_id in num_peers.peer_ids() {
1567 requests.push(Self::make_api_version_request(
1568 Duration::ZERO,
1569 peer_id,
1570 &api,
1571 ));
1572 }
1573
1574 let mut successful_responses = BTreeMap::new();
1575
1576 while successful_responses.len() < num_peers.threshold()
1577 && let Some((peer_id, response)) = requests.next().await
1578 {
1579 let retry = match response {
1580 Err(err) => {
1581 debug!(
1582 target: LOG_CLIENT,
1583 %peer_id,
1584 err = %err.fmt_compact(),
1585 "Failed to fetch API versions from peer"
1586 );
1587 true
1588 }
1589 Ok(response) => {
1590 successful_responses.insert(peer_id, response);
1591 false
1592 }
1593 };
1594
1595 if retry {
1596 requests.push(Self::make_api_version_request(
1597 backoff.next().expect("Keeps retrying"),
1598 peer_id,
1599 &api,
1600 ));
1601 }
1602 }
1603
1604 successful_responses
1605 }
1606
1607 pub async fn fetch_common_api_versions(
1609 config: &ClientConfig,
1610 api: &DynGlobalApi,
1611 ) -> anyhow::Result<BTreeMap<PeerId, SupportedApiVersionsSummary>> {
1612 debug!(
1613 target: LOG_CLIENT,
1614 "Fetching common api versions"
1615 );
1616
1617 let num_peers = NumPeers::from(config.global.api_endpoints.len());
1618
1619 let peer_api_version_sets =
1620 Self::fetch_peers_api_versions_from_threshold_of_peers(num_peers, api.clone()).await;
1621
1622 Ok(peer_api_version_sets)
1623 }
1624
1625 pub async fn write_api_version_cache(
1629 dbtx: &mut DatabaseTransaction<'_>,
1630 api_version_set: ApiVersionSet,
1631 ) {
1632 debug!(
1633 target: LOG_CLIENT,
1634 value = ?api_version_set,
1635 "Writing API version set to cache"
1636 );
1637
1638 dbtx.insert_entry(
1639 &CachedApiVersionSetKey,
1640 &CachedApiVersionSet(api_version_set),
1641 )
1642 .await;
1643 }
1644
1645 pub async fn store_prefetched_api_versions(
1650 db: &Database,
1651 config: &ClientConfig,
1652 client_module_init: &ClientModuleInitRegistry,
1653 peer_api_versions: &BTreeMap<PeerId, SupportedApiVersionsSummary>,
1654 ) {
1655 debug!(
1656 target: LOG_CLIENT,
1657 "Storing {} prefetched peer API version responses and calculating common version set",
1658 peer_api_versions.len()
1659 );
1660
1661 let mut dbtx = db.begin_transaction().await;
1662 let client_supported_versions =
1664 Self::supported_api_versions_summary_static(config, client_module_init);
1665 match fedimint_client_module::api_version_discovery::discover_common_api_versions_set(
1666 &client_supported_versions,
1667 peer_api_versions,
1668 ) {
1669 Ok(common_api_versions) => {
1670 Self::write_api_version_cache(&mut dbtx.to_ref_nc(), common_api_versions).await;
1672 debug!(target: LOG_CLIENT, "Calculated and stored common API version set");
1673 }
1674 Err(err) => {
1675 debug!(target: LOG_CLIENT, err = %err.fmt_compact_anyhow(), "Failed to calculate common API versions from prefetched data");
1676 }
1677 }
1678
1679 for (peer_id, peer_api_versions) in peer_api_versions {
1681 dbtx.insert_entry(
1682 &PeerLastApiVersionsSummaryKey(*peer_id),
1683 &PeerLastApiVersionsSummary(peer_api_versions.clone()),
1684 )
1685 .await;
1686 }
1687 dbtx.commit_tx().await;
1688 debug!(target: LOG_CLIENT, "Stored individual peer API version responses");
1689 }
1690
1691 pub fn supported_api_versions_summary_static(
1693 config: &ClientConfig,
1694 client_module_init: &ClientModuleInitRegistry,
1695 ) -> SupportedApiVersionsSummary {
1696 SupportedApiVersionsSummary {
1697 core: SupportedCoreApiVersions {
1698 core_consensus: config.global.consensus_version,
1699 api: MultiApiVersion::try_from_iter(SUPPORTED_CORE_API_VERSIONS.to_owned())
1700 .expect("must not have conflicting versions"),
1701 },
1702 modules: config
1703 .modules
1704 .iter()
1705 .filter_map(|(&module_instance_id, module_config)| {
1706 client_module_init
1707 .get(module_config.kind())
1708 .map(|module_init| {
1709 (
1710 module_instance_id,
1711 SupportedModuleApiVersions {
1712 core_consensus: config.global.consensus_version,
1713 module_consensus: module_config.version,
1714 api: module_init.supported_api_versions(),
1715 },
1716 )
1717 })
1718 })
1719 .collect(),
1720 }
1721 }
1722
1723 pub async fn load_and_refresh_common_api_version(&self) -> anyhow::Result<ApiVersionSet> {
1724 Self::load_and_refresh_common_api_version_static(
1725 &self.config().await,
1726 &self.module_inits,
1727 self.connectors.clone(),
1728 &self.api,
1729 &self.db,
1730 &self.task_group,
1731 &self.client_span,
1732 )
1733 .await
1734 }
1735
1736 pub async fn refresh_api_versions(&self) -> anyhow::Result<ApiVersionSet> {
1742 Self::refresh_common_api_version_static(
1743 &self.config().await,
1744 &self.module_inits,
1745 &self.api,
1746 &self.db,
1747 self.task_group.clone(),
1748 &self.client_span,
1749 true,
1750 )
1751 .await
1752 }
1753
1754 pub(crate) async fn load_and_refresh_common_api_version_static(
1760 config: &ClientConfig,
1761 module_init: &ClientModuleInitRegistry,
1762 connectors: ConnectorRegistry,
1763 api: &DynGlobalApi,
1764 db: &Database,
1765 task_group: &TaskGroup,
1766 client_span: &Span,
1767 ) -> anyhow::Result<ApiVersionSet> {
1768 if let Some(v) = db
1769 .begin_transaction_nc()
1770 .await
1771 .get_value(&CachedApiVersionSetKey)
1772 .await
1773 {
1774 client_span.in_scope(|| {
1775 debug!(
1776 target: LOG_CLIENT,
1777 "Found existing cached common api versions"
1778 );
1779 });
1780 let config = config.clone();
1781 let client_module_init = module_init.clone();
1782 let api = api.clone();
1783 let db = db.clone();
1784 let task_group = task_group.clone();
1785 let client_span_owned = client_span.clone();
1786 task_group.clone().spawn_cancellable_with_span(
1789 client_span.clone(),
1790 "refresh_common_api_version_static",
1791 async move {
1792 connectors.wait_for_initialized_connections().await;
1793
1794 if let Err(error) = Self::refresh_common_api_version_static(
1795 &config,
1796 &client_module_init,
1797 &api,
1798 &db,
1799 task_group,
1800 &client_span_owned,
1801 false,
1802 )
1803 .await
1804 {
1805 warn!(
1806 target: LOG_CLIENT,
1807 err = %error.fmt_compact_anyhow(), "Failed to discover common api versions"
1808 );
1809 }
1810 },
1811 );
1812
1813 return Ok(v.0);
1814 }
1815
1816 info!(
1817 target: LOG_CLIENT,
1818 "Fetching initial API versions "
1819 );
1820 Self::refresh_common_api_version_static(
1821 config,
1822 module_init,
1823 api,
1824 db,
1825 task_group.clone(),
1826 client_span,
1827 true,
1828 )
1829 .await
1830 }
1831
1832 async fn refresh_common_api_version_static(
1833 config: &ClientConfig,
1834 client_module_init: &ClientModuleInitRegistry,
1835 api: &DynGlobalApi,
1836 db: &Database,
1837 task_group: TaskGroup,
1838 client_span: &Span,
1839 block_until_ok: bool,
1840 ) -> anyhow::Result<ApiVersionSet> {
1841 debug!(
1842 target: LOG_CLIENT,
1843 "Refreshing common api versions"
1844 );
1845
1846 let (num_responses_sender, mut num_responses_receiver) = tokio::sync::watch::channel(0);
1847 let num_peers = NumPeers::from(config.global.api_endpoints.len());
1848
1849 task_group.spawn_cancellable_with_span(
1850 client_span.clone(),
1851 "refresh peers api versions",
1852 Client::fetch_common_api_versions_from_all_peers(
1853 num_peers,
1854 api.clone(),
1855 db.clone(),
1856 num_responses_sender,
1857 ),
1858 );
1859
1860 let common_api_versions = loop {
1861 let _: Result<_, Elapsed> = runtime::timeout(
1869 Duration::from_secs(30),
1870 num_responses_receiver.wait_for(|num| num_peers.threshold() <= *num),
1871 )
1872 .await;
1873
1874 let peer_api_version_sets = Self::load_peers_last_api_versions(db, num_peers).await;
1875
1876 match fedimint_client_module::api_version_discovery::discover_common_api_versions_set(
1877 &Self::supported_api_versions_summary_static(config, client_module_init),
1878 &peer_api_version_sets,
1879 ) {
1880 Ok(o) => break o,
1881 Err(err) if block_until_ok => {
1882 warn!(
1883 target: LOG_CLIENT,
1884 err = %err.fmt_compact_anyhow(),
1885 "Failed to discover API version to use. Retrying..."
1886 );
1887 continue;
1888 }
1889 Err(e) => return Err(e),
1890 }
1891 };
1892
1893 debug!(
1894 target: LOG_CLIENT,
1895 value = ?common_api_versions,
1896 "Updating the cached common api versions"
1897 );
1898 let mut dbtx = db.begin_transaction().await;
1899 let _ = dbtx
1900 .insert_entry(
1901 &CachedApiVersionSetKey,
1902 &CachedApiVersionSet(common_api_versions.clone()),
1903 )
1904 .await;
1905
1906 dbtx.commit_tx().await;
1907
1908 Ok(common_api_versions)
1909 }
1910
1911 pub async fn get_metadata(&self) -> Metadata {
1913 self.db
1914 .begin_transaction_nc()
1915 .await
1916 .get_value(&ClientMetadataKey)
1917 .await
1918 .unwrap_or_else(|| {
1919 warn!(
1920 target: LOG_CLIENT,
1921 "Missing existing metadata. This key should have been set on Client init"
1922 );
1923 Metadata::empty()
1924 })
1925 }
1926
1927 pub async fn set_metadata(&self, metadata: &Metadata) {
1929 self.db
1930 .autocommit::<_, _, anyhow::Error>(
1931 |dbtx, _| {
1932 Box::pin(async {
1933 Self::set_metadata_dbtx(dbtx, metadata).await;
1934 Ok(())
1935 })
1936 },
1937 None,
1938 )
1939 .await
1940 .expect("Failed to autocommit metadata");
1941 }
1942
1943 pub fn has_pending_recoveries(&self) -> bool {
1944 !self
1945 .client_recovery_status_receiver
1946 .borrow()
1947 .values()
1948 .all(RecoveryStatus::is_successfully_done)
1949 }
1950
1951 pub fn all_modules_usable(&self) -> bool {
1962 self.client_recovery_status_receiver
1963 .borrow()
1964 .keys()
1965 .all(|module_instance_id| self.modules.get(*module_instance_id).is_some())
1966 }
1967
1968 pub async fn wait_for_all_recoveries(&self) -> anyhow::Result<()> {
1982 Self::wait_for_recoveries(
1983 self.client_recovery_status_receiver.clone(),
1984 |_module_instance_id| true,
1985 "Recovery task completed and update receiver disconnected, but some modules failed to recover",
1986 )
1987 .await
1988 }
1989
1990 async fn wait_for_recoveries(
1999 mut status_receiver: watch::Receiver<BTreeMap<ModuleInstanceId, RecoveryStatus>>,
2000 module_filter: impl Fn(ModuleInstanceId) -> bool,
2001 disconnected_context: &'static str,
2002 ) -> anyhow::Result<()> {
2003 let failure = status_receiver
2004 .wait_for(|statuses| {
2005 let matching = || {
2006 statuses
2007 .iter()
2008 .filter(|(module_instance_id, _status)| module_filter(**module_instance_id))
2009 .map(|(_module_instance_id, status)| status)
2010 };
2011
2012 matching().any(|status| matches!(status, RecoveryStatus::Failed { .. }))
2016 || matching().all(RecoveryStatus::is_successfully_done)
2017 })
2018 .await
2019 .context(disconnected_context)?
2020 .iter()
2023 .find_map(|(module_instance_id, status)| match status {
2024 RecoveryStatus::Failed { error, .. } if module_filter(*module_instance_id) => {
2025 Some((*module_instance_id, error.clone()))
2026 }
2027 _ => None,
2028 });
2029
2030 match failure {
2031 Some((module_instance_id, error)) => Err(anyhow!(
2032 "Module recovery failed: module_instance_id={module_instance_id}, error={error}"
2033 )),
2034 None => Ok(()),
2035 }
2036 }
2037
2038 pub fn subscribe_to_recovery_progress(
2048 &self,
2049 ) -> impl Stream<Item = (ModuleInstanceId, RecoveryProgress)> + use<> {
2050 WatchStream::new(self.client_recovery_status_receiver.clone()).flat_map(|statuses| {
2051 futures::stream::iter(
2052 statuses
2053 .into_iter()
2054 .map(|(module_instance_id, status)| (module_instance_id, status.progress())),
2055 )
2056 })
2057 }
2058
2059 pub async fn wait_for_module_kind_recovery(
2068 &self,
2069 module_kind: ModuleKind,
2070 ) -> anyhow::Result<()> {
2071 let config = self.config().await;
2072 Self::wait_for_recoveries(
2073 self.client_recovery_status_receiver.clone(),
2074 move |module_instance_id| {
2075 config
2076 .modules
2077 .get(&module_instance_id)
2078 .is_some_and(|module| module.kind == module_kind)
2079 },
2080 "Recovery task completed and update receiver disconnected, but the desired modules are still unavailable or failed to recover",
2081 )
2082 .await
2083 }
2084
2085 pub async fn wait_for_all_active_state_machines(&self) -> anyhow::Result<()> {
2086 loop {
2087 if self.executor.get_active_states().await.is_empty() {
2088 break;
2089 }
2090 sleep(Duration::from_millis(100)).await;
2091 }
2092 Ok(())
2093 }
2094
2095 pub async fn set_metadata_dbtx(dbtx: &mut DatabaseTransaction<'_>, metadata: &Metadata) {
2097 dbtx.insert_new_entry(&ClientMetadataKey, metadata).await;
2098 }
2099
2100 fn spawn_module_recoveries_task(
2101 &self,
2102 recovery_sender: watch::Sender<BTreeMap<ModuleInstanceId, RecoveryStatus>>,
2103 module_recoveries: BTreeMap<ModuleInstanceId, ModuleRecoveryFuture>,
2104 module_recovery_progress_receivers: BTreeMap<
2105 ModuleInstanceId,
2106 watch::Receiver<RecoveryProgress>,
2107 >,
2108 module_kinds: BTreeMap<ModuleInstanceId, ModuleKind>,
2111 ) {
2112 let db = self.db.clone();
2113 let log_ordering_wakeup_tx = self.log_ordering_wakeup_tx.clone();
2114 self.spawn("module recoveries", |_task_handle| async {
2119 Self::run_module_recoveries_task(
2120 db,
2121 log_ordering_wakeup_tx,
2122 recovery_sender,
2123 module_recoveries,
2124 module_recovery_progress_receivers,
2125 module_kinds,
2126 )
2127 .await;
2128 });
2129 }
2130
2131 async fn run_module_recoveries_task(
2132 db: Database,
2133 log_ordering_wakeup_tx: watch::Sender<()>,
2134 recovery_sender: watch::Sender<BTreeMap<ModuleInstanceId, RecoveryStatus>>,
2135 module_recoveries: BTreeMap<ModuleInstanceId, ModuleRecoveryFuture>,
2136 module_recovery_progress_receivers: BTreeMap<
2137 ModuleInstanceId,
2138 watch::Receiver<RecoveryProgress>,
2139 >,
2140 module_kinds: BTreeMap<ModuleInstanceId, ModuleKind>,
2141 ) {
2142 debug!(target: LOG_CLIENT_RECOVERY, num_modules=%module_recovery_progress_receivers.len(), "Staring module recoveries");
2143
2144 enum RecoveryUpdate {
2148 Progress(RecoveryProgress),
2149 Completed(Option<Amount>),
2150 }
2151
2152 let mut completed_stream = Vec::new();
2153 let progress_stream = futures::stream::FuturesUnordered::new();
2154
2155 for (module_instance_id, f) in module_recoveries {
2156 let recovery_sender = recovery_sender.clone();
2157 completed_stream.push(futures::stream::once(Box::pin(async move {
2158 match f.await {
2159 Ok(amount) => (module_instance_id, RecoveryUpdate::Completed(amount)),
2160 Err(err) => {
2161 let error = err.fmt_compact_anyhow().to_string();
2162 warn!(
2163 target: LOG_CLIENT,
2164 err = %error.as_str(), module_instance_id, "Module recovery failed"
2165 );
2166 recovery_sender.send_modify(|statuses| {
2173 let last_progress = statuses
2174 .get(&module_instance_id)
2175 .expect("existing status must be present")
2176 .progress();
2177 statuses.insert(
2178 module_instance_id,
2179 RecoveryStatus::Failed {
2180 last_progress,
2181 error,
2182 },
2183 );
2184 });
2185 futures::future::pending::<()>().await;
2195 unreachable!()
2196 }
2197 }
2198 })));
2199 }
2200
2201 for (module_instance_id, rx) in module_recovery_progress_receivers {
2202 progress_stream.push(
2203 tokio_stream::wrappers::WatchStream::new(rx)
2204 .fuse()
2205 .map(move |progress| (module_instance_id, RecoveryUpdate::Progress(progress))),
2206 );
2207 }
2208
2209 let mut futures = futures::stream::select(
2210 futures::stream::select_all(progress_stream),
2211 futures::stream::select_all(completed_stream),
2212 );
2213
2214 while let Some((module_instance_id, update)) = futures.next().await {
2215 let prev_status = recovery_sender
2219 .borrow()
2220 .get(&module_instance_id)
2221 .expect("existing status must be present")
2222 .clone();
2223
2224 if matches!(prev_status, RecoveryStatus::Failed { .. }) {
2234 debug!(
2235 target: LOG_CLIENT_RECOVERY,
2236 module_instance_id,
2237 "Ignoring a recovery update of a module whose recovery already failed"
2238 );
2239 continue;
2240 }
2241
2242 let prev_progress = prev_status.progress();
2243
2244 if let RecoveryUpdate::Progress(progress) = &update {
2250 if progress.is_done() {
2251 warn!(
2252 target: LOG_CLIENT_RECOVERY,
2253 module_instance_id,
2254 "Module bypassed the sanctioned recovery progress reporting API and reported a completed recovery progress. Ignoring"
2255 );
2256 continue;
2257 }
2258
2259 if progress.is_none() && !prev_progress.is_none() && !prev_progress.is_done() {
2266 warn!(
2267 target: LOG_CLIENT_RECOVERY,
2268 module_instance_id,
2269 "Module bypassed the sanctioned recovery progress reporting API and reported a none recovery progress, regressing its previous one. Ignoring"
2270 );
2271 continue;
2272 }
2273 }
2274
2275 let mut dbtx = db.begin_transaction().await;
2276
2277 let (progress, recovered_amount) = if prev_progress.is_done() {
2283 (prev_progress, None)
2285 } else {
2286 match update {
2287 RecoveryUpdate::Progress(progress) => (progress, None),
2288 RecoveryUpdate::Completed(amount) => (prev_progress.to_complete(), amount),
2289 }
2290 };
2291
2292 if !prev_progress.is_done() && progress.is_done() {
2293 info!(
2294 target: LOG_CLIENT,
2295 module_instance_id,
2296 progress = format!("{}/{}", progress.complete, progress.total),
2297 amount = ?recovered_amount,
2298 "Recovery complete"
2299 );
2300 dbtx.log_event(
2301 log_ordering_wakeup_tx.clone(),
2302 None,
2303 ModuleRecoveryCompleted {
2304 module_id: module_instance_id,
2305 kind: module_kinds.get(&module_instance_id).cloned(),
2306 amount: recovered_amount,
2307 },
2308 )
2309 .await;
2310 } else {
2311 info!(
2312 target: LOG_CLIENT,
2313 module_instance_id,
2314 kind = ?module_kinds.get(&module_instance_id),
2315 progress = format!("{}/{}", progress.complete, progress.total),
2316 "Recovery progress"
2317 );
2318 }
2319
2320 dbtx.insert_entry(
2321 &ClientModuleRecovery { module_instance_id },
2322 &ClientModuleRecoveryState { progress },
2323 )
2324 .await;
2325 dbtx.commit_tx().await;
2326
2327 recovery_sender.send_modify(|statuses| {
2328 statuses.insert(module_instance_id, RecoveryStatus::InProgress(progress));
2329 });
2330 }
2331 debug!(target: LOG_CLIENT_RECOVERY, "Recovery executor stopped");
2332 }
2333
2334 async fn load_peers_last_api_versions(
2335 db: &Database,
2336 num_peers: NumPeers,
2337 ) -> BTreeMap<PeerId, SupportedApiVersionsSummary> {
2338 let mut peer_api_version_sets = BTreeMap::new();
2339
2340 let mut dbtx = db.begin_transaction_nc().await;
2341 for peer_id in num_peers.peer_ids() {
2342 if let Some(v) = dbtx
2343 .get_value(&PeerLastApiVersionsSummaryKey(peer_id))
2344 .await
2345 {
2346 peer_api_version_sets.insert(peer_id, v.0);
2347 }
2348 }
2349 drop(dbtx);
2350 peer_api_version_sets
2351 }
2352
2353 pub async fn get_peer_url_announcements(&self) -> BTreeMap<PeerId, SignedApiAnnouncement> {
2356 self.db()
2357 .begin_transaction_nc()
2358 .await
2359 .find_by_prefix(&ApiAnnouncementPrefix)
2360 .await
2361 .map(|(announcement_key, announcement)| (announcement_key.0, announcement))
2362 .collect()
2363 .await
2364 }
2365
2366 pub async fn get_guardian_metadata(
2368 &self,
2369 ) -> BTreeMap<PeerId, fedimint_core::net::guardian_metadata::SignedGuardianMetadata> {
2370 self.db()
2371 .begin_transaction_nc()
2372 .await
2373 .find_by_prefix(&crate::guardian_metadata::GuardianMetadataPrefix)
2374 .await
2375 .map(|(key, metadata)| (key.0, metadata))
2376 .collect()
2377 .await
2378 }
2379
2380 pub async fn get_peer_urls(&self) -> BTreeMap<PeerId, SafeUrl> {
2382 get_api_urls(&self.db, &self.config().await, self.iroh_enable_next).await
2383 }
2384
2385 pub async fn invite_code(&self, peer: PeerId) -> Option<InviteCode> {
2388 self.get_peer_urls()
2389 .await
2390 .into_iter()
2391 .find_map(|(peer_id, url)| (peer == peer_id).then_some(url))
2392 .map(|peer_url| {
2393 InviteCode::new(
2394 peer_url.clone(),
2395 peer,
2396 self.federation_id(),
2397 self.api_secret.clone(),
2398 )
2399 })
2400 }
2401
2402 pub async fn get_guardian_public_keys_blocking(
2406 &self,
2407 ) -> BTreeMap<PeerId, fedimint_core::secp256k1::PublicKey> {
2408 self.db
2409 .autocommit(
2410 |dbtx, _| {
2411 Box::pin(async move {
2412 let config = self.config().await;
2413
2414 let guardian_pub_keys = self
2415 .get_or_backfill_broadcast_public_keys(dbtx, config)
2416 .await;
2417
2418 Result::<_, ()>::Ok(guardian_pub_keys)
2419 })
2420 },
2421 None,
2422 )
2423 .await
2424 .expect("Will retry forever")
2425 }
2426
2427 async fn get_or_backfill_broadcast_public_keys(
2428 &self,
2429 dbtx: &mut DatabaseTransaction<'_>,
2430 config: ClientConfig,
2431 ) -> BTreeMap<PeerId, PublicKey> {
2432 match config.global.broadcast_public_keys {
2433 Some(guardian_pub_keys) => guardian_pub_keys,
2434 _ => {
2435 let (guardian_pub_keys, new_config) = self.fetch_and_update_config(config).await;
2436
2437 dbtx.insert_entry(&ClientConfigKey, &new_config).await;
2438 *(self.config.write().await) = new_config;
2439 guardian_pub_keys
2440 }
2441 }
2442 }
2443
2444 pub async fn fetch_session_count(&self) -> FederationResult<u64> {
2445 self.api.session_count().await
2446 }
2447
2448 async fn fetch_and_update_config(
2449 &self,
2450 config: ClientConfig,
2451 ) -> (BTreeMap<PeerId, PublicKey>, ClientConfig) {
2452 let fetched_config = retry(
2453 "Fetching guardian public keys",
2454 backoff_util::background_backoff(),
2455 || async {
2456 anyhow::Ok(
2457 self.api
2458 .request_current_consensus::<ClientConfig>(
2459 CLIENT_CONFIG_ENDPOINT.to_owned(),
2460 ApiRequestErased::default(),
2461 )
2462 .await?,
2463 )
2464 },
2465 )
2466 .await
2467 .expect("Will never return on error");
2468
2469 let Some(guardian_pub_keys) = fetched_config.global.broadcast_public_keys else {
2470 warn!(
2471 target: LOG_CLIENT,
2472 "Guardian public keys not found in fetched config, server not updated to 0.4 yet"
2473 );
2474 pending::<()>().await;
2475 unreachable!("Pending will never return");
2476 };
2477
2478 let new_config = ClientConfig {
2479 global: GlobalClientConfig {
2480 broadcast_public_keys: Some(guardian_pub_keys.clone()),
2481 ..config.global
2482 },
2483 modules: config.modules,
2484 };
2485 (guardian_pub_keys, new_config)
2486 }
2487
2488 pub fn handle_global_rpc(
2489 &self,
2490 method: String,
2491 params: serde_json::Value,
2492 ) -> BoxStream<'_, anyhow::Result<serde_json::Value>> {
2493 Box::pin(try_stream! {
2494 match method.as_str() {
2495 "get_balance" => {
2496 let balance = self.get_balance_for_btc().await.unwrap_or_default();
2497 yield serde_json::to_value(balance)?;
2498 }
2499 "subscribe_balance_changes" => {
2500 let req: GetBalanceChangesRequest= serde_json::from_value(params)?;
2501 let mut stream = self.subscribe_balance_changes(req.unit).await;
2502 while let Some(balance) = stream.next().await {
2503 yield serde_json::to_value(balance)?;
2504 }
2505 }
2506 "get_config" => {
2507 let config = self.config().await;
2508 yield serde_json::to_value(config)?;
2509 }
2510 "get_federation_id" => {
2511 let federation_id = self.federation_id();
2512 yield serde_json::to_value(federation_id)?;
2513 }
2514 "get_invite_code" => {
2515 let req: GetInviteCodeRequest = serde_json::from_value(params)?;
2516 let invite_code = self.invite_code(req.peer).await;
2517 yield serde_json::to_value(invite_code)?;
2518 }
2519 "get_operation" => {
2520 let req: GetOperationIdRequest = serde_json::from_value(params)?;
2521 let operation = self.operation_log().get_operation(req.operation_id).await;
2522 yield serde_json::to_value(operation)?;
2523 }
2524 "list_operations" => {
2525 let req: ListOperationsParams = serde_json::from_value(params)?;
2526 let limit = if req.limit.is_none() && req.last_seen.is_none() {
2527 usize::MAX
2528 } else {
2529 req.limit.unwrap_or(usize::MAX)
2530 };
2531 let operations = self.operation_log()
2532 .paginate_operations_rev(limit, req.last_seen)
2533 .await;
2534 yield serde_json::to_value(operations)?;
2535 }
2536 "get_event_log" => {
2537 let req: GetEventLogRequest = serde_json::from_value(params)?;
2538 let limit = req
2539 .limit
2540 .unwrap_or(DEFAULT_EVENT_LOG_PAGE_SIZE)
2541 .min(MAX_EVENT_LOG_PAGE_SIZE);
2542 let events = self.get_event_log(req.pos, limit).await;
2543 yield serde_json::to_value(events)?;
2544 }
2545 "session_count" => {
2546 let count = self.fetch_session_count().await?;
2547 yield serde_json::to_value(count)?;
2548 }
2549 "has_pending_recoveries" => {
2550 let has_pending = self.has_pending_recoveries();
2551 yield serde_json::to_value(has_pending)?;
2552 }
2553 "wait_for_all_recoveries" => {
2554 self.wait_for_all_recoveries().await?;
2555 yield serde_json::Value::Null;
2556 }
2557 "subscribe_to_recovery_progress" => {
2558 let mut stream = self.subscribe_to_recovery_progress();
2559 while let Some((module_id, progress)) = stream.next().await {
2560 yield serde_json::json!({
2561 "module_id": module_id,
2562 "progress": progress
2563 });
2564 }
2565 }
2566 #[allow(deprecated)]
2567 "backup_to_federation" => {
2568 let metadata = if params.is_null() {
2569 Metadata::from_json_serialized(serde_json::json!({}))
2570 } else {
2571 Metadata::from_json_serialized(params)
2572 };
2573 self.backup_to_federation(metadata).await?;
2574 yield serde_json::Value::Null;
2575 }
2576 _ => {
2577 Err(anyhow::format_err!("Unknown method: {}", method))?;
2578 unreachable!()
2579 },
2580 }
2581 })
2582 }
2583
2584 pub async fn log_event<E>(&self, module_id: Option<ModuleInstanceId>, event: E)
2585 where
2586 E: Event + Send,
2587 {
2588 let mut dbtx = self.db.begin_transaction().await;
2589 self.log_event_dbtx(&mut dbtx, module_id, event).await;
2590 dbtx.commit_tx().await;
2591 }
2592
2593 pub async fn log_event_dbtx<E, Cap>(
2594 &self,
2595 dbtx: &mut DatabaseTransaction<'_, Cap>,
2596 module_id: Option<ModuleInstanceId>,
2597 event: E,
2598 ) where
2599 E: Event + Send,
2600 Cap: Send,
2601 {
2602 dbtx.log_event(self.log_ordering_wakeup_tx.clone(), module_id, event)
2603 .await;
2604 }
2605
2606 pub async fn log_event_raw_dbtx<Cap>(
2607 &self,
2608 dbtx: &mut DatabaseTransaction<'_, Cap>,
2609 kind: EventKind,
2610 module: Option<(ModuleKind, ModuleInstanceId)>,
2611 payload: Vec<u8>,
2612 persist: EventPersistence,
2613 ) where
2614 Cap: Send,
2615 {
2616 let module_id = module.as_ref().map(|m| m.1);
2617 let module_kind = module.map(|m| m.0);
2618 dbtx.log_event_raw(
2619 self.log_ordering_wakeup_tx.clone(),
2620 kind,
2621 module_kind,
2622 module_id,
2623 payload,
2624 persist,
2625 )
2626 .await;
2627 }
2628
2629 pub fn built_in_application_event_log_tracker(&self) -> DynEventLogTrimableTracker {
2641 struct BuiltInApplicationEventLogTracker;
2642
2643 #[apply(async_trait_maybe_send!)]
2644 impl EventLogTrimableTracker for BuiltInApplicationEventLogTracker {
2645 async fn store(
2647 &mut self,
2648 dbtx: &mut DatabaseTransaction<NonCommittable>,
2649 pos: EventLogTrimableId,
2650 ) -> anyhow::Result<()> {
2651 dbtx.insert_entry(&DefaultApplicationEventLogKey, &pos)
2652 .await;
2653 Ok(())
2654 }
2655
2656 async fn load(
2658 &mut self,
2659 dbtx: &mut DatabaseTransaction<NonCommittable>,
2660 ) -> anyhow::Result<Option<EventLogTrimableId>> {
2661 Ok(dbtx.get_value(&DefaultApplicationEventLogKey).await)
2662 }
2663 }
2664 Box::new(BuiltInApplicationEventLogTracker)
2665 }
2666
2667 pub async fn handle_historical_events<F, R>(
2675 &self,
2676 tracker: fedimint_eventlog::DynEventLogTracker,
2677 handler_fn: F,
2678 ) -> anyhow::Result<()>
2679 where
2680 F: Fn(&mut DatabaseTransaction<NonCommittable>, EventLogEntry) -> R,
2681 R: Future<Output = anyhow::Result<()>>,
2682 {
2683 fedimint_eventlog::handle_events(
2684 self.db.clone(),
2685 tracker,
2686 self.log_event_added_rx.clone(),
2687 handler_fn,
2688 )
2689 .await
2690 }
2691
2692 pub async fn handle_events<F, R>(
2711 &self,
2712 tracker: fedimint_eventlog::DynEventLogTrimableTracker,
2713 handler_fn: F,
2714 ) -> anyhow::Result<()>
2715 where
2716 F: Fn(&mut DatabaseTransaction<NonCommittable>, EventLogEntry) -> R,
2717 R: Future<Output = anyhow::Result<()>>,
2718 {
2719 fedimint_eventlog::handle_trimable_events(
2720 self.db.clone(),
2721 tracker,
2722 self.log_event_added_rx.clone(),
2723 handler_fn,
2724 )
2725 .await
2726 }
2727
2728 pub async fn get_event_log(
2729 &self,
2730 pos: Option<EventLogId>,
2731 limit: u64,
2732 ) -> Vec<PersistedLogEntry> {
2733 self.get_event_log_dbtx(&mut self.db.begin_transaction_nc().await, pos, limit)
2734 .await
2735 }
2736
2737 pub async fn get_next_event_log_id(&self) -> EventLogId {
2740 self.db
2741 .begin_transaction_nc()
2742 .await
2743 .get_next_event_log_id()
2744 .await
2745 }
2746
2747 pub async fn get_event_log_trimable(
2748 &self,
2749 pos: Option<EventLogTrimableId>,
2750 limit: u64,
2751 ) -> Vec<PersistedLogEntry> {
2752 self.get_event_log_trimable_dbtx(&mut self.db.begin_transaction_nc().await, pos, limit)
2753 .await
2754 }
2755
2756 pub async fn get_event_log_dbtx<Cap>(
2757 &self,
2758 dbtx: &mut DatabaseTransaction<'_, Cap>,
2759 pos: Option<EventLogId>,
2760 limit: u64,
2761 ) -> Vec<PersistedLogEntry>
2762 where
2763 Cap: Send,
2764 {
2765 dbtx.get_event_log(pos, limit).await
2766 }
2767
2768 pub async fn get_event_log_trimable_dbtx<Cap>(
2769 &self,
2770 dbtx: &mut DatabaseTransaction<'_, Cap>,
2771 pos: Option<EventLogTrimableId>,
2772 limit: u64,
2773 ) -> Vec<PersistedLogEntry>
2774 where
2775 Cap: Send,
2776 {
2777 dbtx.get_event_log_trimable(pos, limit).await
2778 }
2779
2780 pub fn get_event_log_transient_receiver(&self) -> broadcast::Receiver<EventLogEntry> {
2782 self.log_event_added_transient_tx.subscribe()
2783 }
2784
2785 pub fn log_event_added_rx(&self) -> watch::Receiver<()> {
2787 self.log_event_added_rx.clone()
2788 }
2789
2790 pub fn iroh_enable_dht(&self) -> bool {
2791 self.iroh_enable_dht
2792 }
2793
2794 pub fn iroh_enable_next(&self) -> bool {
2797 self.iroh_enable_next
2798 }
2799
2800 pub(crate) async fn run_core_migrations(
2801 db_no_decoders: &Database,
2802 ) -> Result<(), anyhow::Error> {
2803 let mut dbtx = db_no_decoders.begin_transaction().await;
2804 apply_migrations_core_client_dbtx(&mut dbtx.to_ref_nc(), "fedimint-client".to_string())
2805 .await?;
2806 if is_running_in_test_env() {
2807 verify_client_db_integrity_dbtx(&mut dbtx.to_ref_nc()).await;
2808 }
2809 dbtx.commit_tx_result().await?;
2810 Ok(())
2811 }
2812
2813 fn primary_modules_for_unit(
2815 &self,
2816 unit: AmountUnit,
2817 ) -> impl Iterator<Item = (ModuleInstanceId, &DynClientModule)> {
2818 self.primary_modules
2819 .iter()
2820 .flat_map(move |(_prio, candidates)| {
2821 candidates
2822 .specific
2823 .get(&unit)
2824 .into_iter()
2825 .flatten()
2826 .copied()
2827 .chain(candidates.wildcard.iter().copied())
2829 })
2830 .map(|id| (id, self.modules.get_expect(id)))
2831 }
2832
2833 pub fn primary_module_for_unit(
2837 &self,
2838 unit: AmountUnit,
2839 ) -> Option<(ModuleInstanceId, &DynClientModule)> {
2840 self.primary_modules_for_unit(unit).next()
2841 }
2842
2843 pub fn primary_module_for_btc(&self) -> (ModuleInstanceId, &DynClientModule) {
2845 self.primary_module_for_unit(AmountUnit::BITCOIN)
2846 .expect("No primary module for Bitcoin")
2847 }
2848}
2849
2850#[apply(async_trait_maybe_send!)]
2851impl ClientContextIface for Client {
2852 fn get_module(&self, instance: ModuleInstanceId) -> &maybe_add_send_sync!(dyn IClientModule) {
2853 Client::get_module(self, instance)
2854 }
2855
2856 fn api_clone(&self) -> DynGlobalApi {
2857 Client::api_clone(self)
2858 }
2859 fn decoders(&self) -> &ModuleDecoderRegistry {
2860 Client::decoders(self)
2861 }
2862
2863 async fn finalize_and_submit_transaction(
2864 &self,
2865 operation_id: OperationId,
2866 operation_type: &str,
2867 operation_meta_gen: Box<maybe_add_send_sync!(dyn Fn(OutPointRange) -> serde_json::Value)>,
2868 tx_builder: TransactionBuilder,
2869 ) -> anyhow::Result<OutPointRange> {
2870 Client::finalize_and_submit_transaction(
2871 self,
2872 operation_id,
2873 operation_type,
2874 &operation_meta_gen,
2876 tx_builder,
2877 )
2878 .await
2879 }
2880
2881 async fn finalize_and_submit_transaction_dbtx(
2882 &self,
2883 dbtx: &mut DatabaseTransaction<'_>,
2884 operation_id: OperationId,
2885 operation_type: &str,
2886 operation_meta_gen: Box<maybe_add_send_sync!(dyn Fn(OutPointRange) -> serde_json::Value)>,
2887 tx_builder: TransactionBuilder,
2888 ) -> anyhow::Result<OutPointRange> {
2889 Client::finalize_and_submit_transaction_dbtx(
2890 self,
2891 dbtx,
2892 operation_id,
2893 operation_type,
2894 &operation_meta_gen,
2895 tx_builder,
2896 )
2897 .await
2898 }
2899
2900 async fn finalize_and_submit_transaction_inner(
2901 &self,
2902 dbtx: &mut DatabaseTransaction<'_>,
2903 operation_id: OperationId,
2904 tx_builder: TransactionBuilder,
2905 ) -> anyhow::Result<OutPointRange> {
2906 Client::finalize_and_submit_transaction_inner(self, dbtx, operation_id, tx_builder).await
2907 }
2908
2909 async fn fee_quote(
2910 &self,
2911 operation_id: OperationId,
2912 request: FeeQuoteRequest,
2913 ) -> anyhow::Result<FeeQuote> {
2914 Client::fee_quote(self, operation_id, request).await
2915 }
2916
2917 async fn get_balance_for_unit(&self, unit: AmountUnit) -> anyhow::Result<Amount> {
2918 Client::get_balance_for_unit(self, unit).await
2919 }
2920
2921 async fn transaction_updates(&self, operation_id: OperationId) -> TransactionUpdates {
2922 Client::transaction_updates(self, operation_id).await
2923 }
2924
2925 async fn await_primary_module_outputs(
2926 &self,
2927 operation_id: OperationId,
2928 outputs: Vec<OutPoint>,
2930 ) -> anyhow::Result<()> {
2931 Client::await_primary_bitcoin_module_outputs(self, operation_id, outputs).await
2932 }
2933
2934 fn operation_log(&self) -> &dyn IOperationLog {
2935 Client::operation_log(self)
2936 }
2937
2938 async fn has_active_states(&self, operation_id: OperationId) -> bool {
2939 Client::has_active_states(self, operation_id).await
2940 }
2941
2942 async fn operation_exists(&self, operation_id: OperationId) -> bool {
2943 Client::operation_exists(self, operation_id).await
2944 }
2945
2946 async fn config(&self) -> ClientConfig {
2947 Client::config(self).await
2948 }
2949
2950 fn db(&self) -> &Database {
2951 Client::db(self)
2952 }
2953
2954 fn executor(&self) -> &(maybe_add_send_sync!(dyn IExecutor + 'static)) {
2955 Client::executor(self)
2956 }
2957
2958 async fn invite_code(&self, peer: PeerId) -> Option<InviteCode> {
2959 Client::invite_code(self, peer).await
2960 }
2961
2962 fn get_internal_payment_markers(&self) -> anyhow::Result<(PublicKey, u64)> {
2963 Client::get_internal_payment_markers(self)
2964 }
2965
2966 async fn log_event_json(
2967 &self,
2968 dbtx: &mut DatabaseTransaction<'_, NonCommittable>,
2969 module_kind: Option<ModuleKind>,
2970 module_id: ModuleInstanceId,
2971 kind: EventKind,
2972 payload: serde_json::Value,
2973 persist: EventPersistence,
2974 ) {
2975 dbtx.ensure_global()
2976 .expect("Must be called with global dbtx");
2977 self.log_event_raw_dbtx(
2978 dbtx,
2979 kind,
2980 module_kind.map(|kind| (kind, module_id)),
2981 serde_json::to_vec(&payload).expect("Serialization can't fail"),
2982 persist,
2983 )
2984 .await;
2985 }
2986
2987 async fn read_operation_active_states<'dbtx>(
2988 &self,
2989 operation_id: OperationId,
2990 module_id: ModuleInstanceId,
2991 dbtx: &'dbtx mut DatabaseTransaction<'_>,
2992 ) -> Pin<Box<maybe_add_send!(dyn Stream<Item = (ActiveStateKey, ActiveStateMeta)> + 'dbtx)>>
2993 {
2994 Box::pin(
2995 dbtx.find_by_prefix(&ActiveModuleOperationStateKeyPrefix {
2996 operation_id,
2997 module_instance: module_id,
2998 })
2999 .await
3000 .map(move |(k, v)| (k.0, v)),
3001 )
3002 }
3003 async fn read_operation_inactive_states<'dbtx>(
3004 &self,
3005 operation_id: OperationId,
3006 module_id: ModuleInstanceId,
3007 dbtx: &'dbtx mut DatabaseTransaction<'_>,
3008 ) -> Pin<Box<maybe_add_send!(dyn Stream<Item = (InactiveStateKey, InactiveStateMeta)> + 'dbtx)>>
3009 {
3010 Box::pin(
3011 dbtx.find_by_prefix(&InactiveModuleOperationStateKeyPrefix {
3012 operation_id,
3013 module_instance: module_id,
3014 })
3015 .await
3016 .map(move |(k, v)| (k.0, v)),
3017 )
3018 }
3019}
3020
3021impl fmt::Debug for Client {
3023 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3024 write!(f, "Client")
3025 }
3026}
3027
3028pub fn client_decoders<'a>(
3029 registry: &ModuleInitRegistry<DynClientModuleInit>,
3030 module_kinds: impl Iterator<Item = (ModuleInstanceId, &'a ModuleKind)>,
3031) -> ModuleDecoderRegistry {
3032 let mut modules = BTreeMap::new();
3033 for (id, kind) in module_kinds {
3034 let Some(init) = registry.get(kind) else {
3035 debug!("Detected configuration for unsupported module id: {id}, kind: {kind}");
3036 continue;
3037 };
3038
3039 modules.insert(
3040 id,
3041 (
3042 kind.clone(),
3043 IClientModuleInit::decoder(AsRef::<dyn IClientModuleInit + 'static>::as_ref(init)),
3044 ),
3045 );
3046 }
3047 ModuleDecoderRegistry::from(modules)
3048}