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_client_module::module::recovery::RecoveryProgress;
19use fedimint_client_module::module::{
20 ClientContextIface, ClientModule, ClientModuleRegistry, DynClientModule, FinalClientIface,
21 IClientModule, IdxRange, OutPointRange, PrimaryModulePriority,
22};
23use fedimint_client_module::oplog::IOperationLog;
24use fedimint_client_module::secret::{PlainRootSecretStrategy, RootSecretStrategy as _};
25use fedimint_client_module::sm::executor::{ActiveStateKey, IExecutor, InactiveStateKey};
26use fedimint_client_module::sm::{ActiveStateMeta, DynState, InactiveStateMeta};
27use fedimint_client_module::transaction::{
28 TRANSACTION_SUBMISSION_MODULE_INSTANCE, TransactionBuilder, TxSubmissionStates,
29 TxSubmissionStatesSM,
30};
31use fedimint_client_module::{
32 AddStateMachinesResult, ClientModuleInstance, GetInviteCodeRequest, ModuleGlobalContextGen,
33 ModuleRecoveryCompleted, TransactionUpdates, TxCreatedEvent,
34};
35use fedimint_connectors::ConnectorRegistry;
36use fedimint_core::config::{
37 ClientConfig, FederationId, GlobalClientConfig, JsonClientConfig, ModuleInitRegistry,
38};
39use fedimint_core::core::{DynInput, DynOutput, ModuleInstanceId, ModuleKind, OperationId};
40use fedimint_core::db::{
41 AutocommitError, Database, DatabaseRecord, DatabaseTransaction,
42 IDatabaseTransactionOpsCore as _, IDatabaseTransactionOpsCoreTyped as _, NonCommittable,
43};
44use fedimint_core::encoding::{Decodable, Encodable};
45use fedimint_core::endpoint_constants::{CLIENT_CONFIG_ENDPOINT, VERSION_ENDPOINT};
46use fedimint_core::envs::is_running_in_test_env;
47use fedimint_core::invite_code::InviteCode;
48use fedimint_core::module::registry::{ModuleDecoderRegistry, ModuleRegistry};
49use fedimint_core::module::{
50 AmountUnit, Amounts, ApiRequestErased, ApiVersion, MultiApiVersion,
51 SupportedApiVersionsSummary, SupportedCoreApiVersions, SupportedModuleApiVersions,
52};
53use fedimint_core::net::api_announcement::SignedApiAnnouncement;
54use fedimint_core::runtime::sleep;
55use fedimint_core::task::{Elapsed, MaybeSend, MaybeSync, TaskGroup};
56use fedimint_core::transaction::Transaction;
57use fedimint_core::util::backoff_util::custom_backoff;
58use fedimint_core::util::{
59 BoxStream, FmtCompact as _, FmtCompactAnyhow as _, SafeUrl, backoff_util, retry,
60};
61use fedimint_core::{
62 Amount, NumPeers, OutPoint, PeerId, apply, async_trait_maybe_send, maybe_add_send,
63 maybe_add_send_sync, runtime,
64};
65use fedimint_derive_secret::DerivableSecret;
66use fedimint_eventlog::{
67 DBTransactionEventLogExt as _, DynEventLogTrimableTracker, Event, EventKind, EventLogEntry,
68 EventLogId, EventLogTrimableId, EventLogTrimableTracker, EventPersistence, PersistedLogEntry,
69};
70use fedimint_logging::{LOG_CLIENT, LOG_CLIENT_NET_API, LOG_CLIENT_RECOVERY};
71use futures::stream::FuturesUnordered;
72use futures::{Stream, StreamExt as _};
73use global_ctx::ModuleGlobalClientContext;
74use serde::{Deserialize, Serialize};
75use tokio::sync::{broadcast, watch};
76use tokio_stream::wrappers::WatchStream;
77use tracing::{debug, info, warn};
78
79use crate::ClientBuilder;
80use crate::api_announcements::{ApiAnnouncementPrefix, get_api_urls};
81use crate::backup::Metadata;
82use crate::client::event_log::DefaultApplicationEventLogKey;
83use crate::db::{
84 ApiSecretKey, CachedApiVersionSet, CachedApiVersionSetKey, ChronologicalOperationLogKey,
85 ClientConfigKey, ClientMetadataKey, ClientModuleRecovery, ClientModuleRecoveryState,
86 EncodedClientSecretKey, OperationLogKey, PeerLastApiVersionsSummary,
87 PeerLastApiVersionsSummaryKey, PendingClientConfigKey, apply_migrations_core_client_dbtx,
88 get_decoded_client_secret, verify_client_db_integrity_dbtx,
89};
90use crate::meta::MetaService;
91use crate::module_init::{ClientModuleInitRegistry, DynClientModuleInit, IClientModuleInit};
92use crate::oplog::OperationLog;
93use crate::sm::executor::{
94 ActiveModuleOperationStateKeyPrefix, ActiveOperationStateKeyPrefix, Executor,
95 InactiveModuleOperationStateKeyPrefix, InactiveOperationStateKeyPrefix,
96};
97
98pub(crate) mod builder;
99pub(crate) mod event_log;
100pub(crate) mod global_ctx;
101pub(crate) mod handle;
102
103const SUPPORTED_CORE_API_VERSIONS: &[fedimint_core::module::ApiVersion] =
107 &[ApiVersion { major: 0, minor: 0 }];
108
109#[derive(Default)]
111pub(crate) struct PrimaryModuleCandidates {
112 specific: BTreeMap<AmountUnit, Vec<ModuleInstanceId>>,
114 wildcard: Vec<ModuleInstanceId>,
116}
117
118pub struct Client {
132 final_client: FinalClientIface,
133 config: tokio::sync::RwLock<ClientConfig>,
134 api_secret: Option<String>,
135 decoders: ModuleDecoderRegistry,
136 connectors: ConnectorRegistry,
137 db: Database,
138 federation_id: FederationId,
139 federation_config_meta: BTreeMap<String, String>,
140 primary_modules: BTreeMap<PrimaryModulePriority, PrimaryModuleCandidates>,
141 pub(crate) modules: ClientModuleRegistry,
142 module_inits: ClientModuleInitRegistry,
143 executor: Executor,
144 pub(crate) api: DynGlobalApi,
145 root_secret: DerivableSecret,
146 operation_log: OperationLog,
147 secp_ctx: Secp256k1<secp256k1::All>,
148 meta_service: Arc<MetaService>,
149
150 task_group: TaskGroup,
151
152 client_recovery_progress_receiver:
154 watch::Receiver<BTreeMap<ModuleInstanceId, RecoveryProgress>>,
155
156 log_ordering_wakeup_tx: watch::Sender<()>,
159 log_event_added_rx: watch::Receiver<()>,
161 log_event_added_transient_tx: broadcast::Sender<EventLogEntry>,
162 request_hook: ApiRequestHook,
163 iroh_enable_dht: bool,
164 iroh_enable_next: bool,
165}
166
167#[derive(Debug, Serialize, Deserialize)]
168struct ListOperationsParams {
169 limit: Option<usize>,
170 last_seen: Option<ChronologicalOperationLogKey>,
171}
172
173#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct GetOperationIdRequest {
175 operation_id: OperationId,
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize)]
179pub struct GetBalanceChangesRequest {
180 #[serde(default = "AmountUnit::bitcoin")]
181 unit: AmountUnit,
182}
183
184impl Client {
185 pub async fn builder() -> anyhow::Result<ClientBuilder> {
188 Ok(ClientBuilder::new())
189 }
190
191 pub fn api(&self) -> &(dyn IGlobalFederationApi + 'static) {
192 self.api.as_ref()
193 }
194
195 pub fn api_clone(&self) -> DynGlobalApi {
196 self.api.clone()
197 }
198
199 pub fn connection_status_stream(&self) -> impl Stream<Item = BTreeMap<PeerId, bool>> {
202 self.api.connection_status_stream()
203 }
204
205 pub fn task_group(&self) -> &TaskGroup {
207 &self.task_group
208 }
209
210 pub fn get_metrics() -> anyhow::Result<String> {
215 fedimint_metrics::get_metrics()
216 }
217
218 #[doc(hidden)]
220 pub fn executor(&self) -> &Executor {
221 &self.executor
222 }
223
224 pub async fn get_config_from_db(db: &Database) -> Option<ClientConfig> {
225 let mut dbtx = db.begin_transaction_nc().await;
226 dbtx.get_value(&ClientConfigKey).await
227 }
228
229 pub async fn get_pending_config_from_db(db: &Database) -> Option<ClientConfig> {
230 let mut dbtx = db.begin_transaction_nc().await;
231 dbtx.get_value(&PendingClientConfigKey).await
232 }
233
234 pub async fn get_api_secret_from_db(db: &Database) -> Option<String> {
235 let mut dbtx = db.begin_transaction_nc().await;
236 dbtx.get_value(&ApiSecretKey).await
237 }
238
239 pub async fn store_encodable_client_secret<T: Encodable>(
240 db: &Database,
241 secret: T,
242 ) -> anyhow::Result<()> {
243 let mut dbtx = db.begin_transaction().await;
244
245 if dbtx.get_value(&EncodedClientSecretKey).await.is_some() {
247 bail!("Encoded client secret already exists, cannot overwrite")
248 }
249
250 let encoded_secret = T::consensus_encode_to_vec(&secret);
251 dbtx.insert_entry(&EncodedClientSecretKey, &encoded_secret)
252 .await;
253 dbtx.commit_tx().await;
254 Ok(())
255 }
256
257 pub async fn load_decodable_client_secret<T: Decodable>(db: &Database) -> anyhow::Result<T> {
258 let Some(secret) = Self::load_decodable_client_secret_opt(db).await? else {
259 bail!("Encoded client secret not present in DB")
260 };
261
262 Ok(secret)
263 }
264 pub async fn load_decodable_client_secret_opt<T: Decodable>(
265 db: &Database,
266 ) -> anyhow::Result<Option<T>> {
267 let mut dbtx = db.begin_transaction_nc().await;
268
269 let client_secret = dbtx.get_value(&EncodedClientSecretKey).await;
270
271 Ok(match client_secret {
272 Some(client_secret) => Some(
273 T::consensus_decode_whole(&client_secret, &ModuleRegistry::default())
274 .map_err(|e| anyhow!("Decoding failed: {e}"))?,
275 ),
276 None => None,
277 })
278 }
279
280 pub async fn load_or_generate_client_secret(db: &Database) -> anyhow::Result<[u8; 64]> {
281 let client_secret = match Self::load_decodable_client_secret::<[u8; 64]>(db).await {
282 Ok(secret) => secret,
283 _ => {
284 let secret = PlainRootSecretStrategy::random(&mut thread_rng());
285 Self::store_encodable_client_secret(db, secret)
286 .await
287 .expect("Storing client secret must work");
288 secret
289 }
290 };
291 Ok(client_secret)
292 }
293
294 pub async fn is_initialized(db: &Database) -> bool {
295 let mut dbtx = db.begin_transaction_nc().await;
296 dbtx.raw_get_bytes(&[ClientConfigKey::DB_PREFIX])
297 .await
298 .expect("Unrecoverable error occurred while reading and entry from the database")
299 .is_some()
300 }
301
302 pub fn start_executor(self: &Arc<Self>) {
303 debug!(
304 target: LOG_CLIENT,
305 "Starting fedimint client executor",
306 );
307 self.executor.start_executor(self.context_gen());
308 }
309
310 pub fn federation_id(&self) -> FederationId {
311 self.federation_id
312 }
313
314 fn context_gen(self: &Arc<Self>) -> ModuleGlobalContextGen {
315 let client_inner = Arc::downgrade(self);
316 Arc::new(move |module_instance, operation| {
317 ModuleGlobalClientContext {
318 client: client_inner
319 .clone()
320 .upgrade()
321 .expect("ModuleGlobalContextGen called after client was dropped"),
322 module_instance_id: module_instance,
323 operation,
324 }
325 .into()
326 })
327 }
328
329 pub async fn config(&self) -> ClientConfig {
330 self.config.read().await.clone()
331 }
332
333 pub fn api_secret(&self) -> &Option<String> {
335 &self.api_secret
336 }
337
338 pub async fn core_api_version(&self) -> ApiVersion {
344 self.db
347 .begin_transaction_nc()
348 .await
349 .get_value(&CachedApiVersionSetKey)
350 .await
351 .map(|cached: CachedApiVersionSet| cached.0.core)
352 .unwrap_or(ApiVersion { major: 0, minor: 0 })
353 }
354
355 pub fn decoders(&self) -> &ModuleDecoderRegistry {
356 &self.decoders
357 }
358
359 fn get_module(&self, instance: ModuleInstanceId) -> &maybe_add_send_sync!(dyn IClientModule) {
361 self.try_get_module(instance)
362 .expect("Module instance not found")
363 }
364
365 fn try_get_module(
366 &self,
367 instance: ModuleInstanceId,
368 ) -> Option<&maybe_add_send_sync!(dyn IClientModule)> {
369 Some(self.modules.get(instance)?.as_ref())
370 }
371
372 pub fn has_module(&self, instance: ModuleInstanceId) -> bool {
373 self.modules.get(instance).is_some()
374 }
375
376 fn transaction_builder_get_balance(&self, builder: &TransactionBuilder) -> (Amounts, Amounts) {
382 let mut in_amounts = Amounts::ZERO;
384 let mut out_amounts = Amounts::ZERO;
385 let mut fee_amounts = Amounts::ZERO;
386
387 for input in builder.inputs() {
388 let module = self.get_module(input.input.module_instance_id());
389
390 let item_fees = module.input_fee(&input.amounts, &input.input).expect(
391 "We only build transactions with input versions that are supported by the module",
392 );
393
394 in_amounts.checked_add_mut(&input.amounts);
395 fee_amounts.checked_add_mut(&item_fees);
396 }
397
398 for output in builder.outputs() {
399 let module = self.get_module(output.output.module_instance_id());
400
401 let item_fees = module.output_fee(&output.amounts, &output.output).expect(
402 "We only build transactions with output versions that are supported by the module",
403 );
404
405 out_amounts.checked_add_mut(&output.amounts);
406 fee_amounts.checked_add_mut(&item_fees);
407 }
408
409 out_amounts.checked_add_mut(&fee_amounts);
410 (in_amounts, out_amounts)
411 }
412
413 pub fn get_internal_payment_markers(&self) -> anyhow::Result<(PublicKey, u64)> {
414 Ok((self.federation_id().to_fake_ln_pub_key(&self.secp_ctx)?, 0))
415 }
416
417 pub fn get_config_meta(&self, key: &str) -> Option<String> {
419 self.federation_config_meta.get(key).cloned()
420 }
421
422 pub(crate) fn root_secret(&self) -> DerivableSecret {
423 self.root_secret.clone()
424 }
425
426 pub async fn add_state_machines(
427 &self,
428 dbtx: &mut DatabaseTransaction<'_>,
429 states: Vec<DynState>,
430 ) -> AddStateMachinesResult {
431 self.executor.add_state_machines_dbtx(dbtx, states).await
432 }
433
434 pub async fn get_active_operations(&self) -> HashSet<OperationId> {
436 let active_states = self.executor.get_active_states().await;
437 let mut active_operations = HashSet::with_capacity(active_states.len());
438 let mut dbtx = self.db().begin_transaction_nc().await;
439 for (state, _) in active_states {
440 let operation_id = state.operation_id();
441 if dbtx
442 .get_value(&OperationLogKey { operation_id })
443 .await
444 .is_some()
445 {
446 active_operations.insert(operation_id);
447 }
448 }
449 active_operations
450 }
451
452 pub fn operation_log(&self) -> &OperationLog {
453 &self.operation_log
454 }
455
456 pub fn meta_service(&self) -> &Arc<MetaService> {
458 &self.meta_service
459 }
460
461 pub async fn get_meta_expiration_timestamp(&self) -> Option<SystemTime> {
463 let meta_service = self.meta_service();
464 let ts = meta_service
465 .get_field::<u64>(self.db(), "federation_expiry_timestamp")
466 .await
467 .and_then(|v| v.value)?;
468 Some(UNIX_EPOCH + Duration::from_secs(ts))
469 }
470
471 async fn finalize_transaction(
473 &self,
474 dbtx: &mut DatabaseTransaction<'_>,
475 operation_id: OperationId,
476 mut partial_transaction: TransactionBuilder,
477 ) -> anyhow::Result<(Transaction, Vec<DynState>, Range<u64>)> {
478 let (in_amounts, out_amounts) = self.transaction_builder_get_balance(&partial_transaction);
479
480 let mut added_inputs_bundles = vec![];
481 let mut added_outputs_bundles = vec![];
482
483 for unit in in_amounts.units().union(&out_amounts.units()) {
494 let input_amount = in_amounts.get(unit).copied().unwrap_or_default();
495 let output_amount = out_amounts.get(unit).copied().unwrap_or_default();
496 if input_amount == output_amount {
497 continue;
498 }
499
500 let Some((module_id, module)) = self.primary_module_for_unit(*unit) else {
501 bail!("No module to balance a partial transaction (affected unit: {unit:?}");
502 };
503
504 let (added_input_bundle, added_output_bundle) = module
505 .create_final_inputs_and_outputs(
506 module_id,
507 dbtx,
508 operation_id,
509 *unit,
510 input_amount,
511 output_amount,
512 )
513 .await?;
514
515 added_inputs_bundles.push(added_input_bundle);
516 added_outputs_bundles.push(added_output_bundle);
517 }
518
519 let change_range = Range {
523 start: partial_transaction.outputs().count() as u64,
524 end: (partial_transaction.outputs().count() as u64
525 + added_outputs_bundles
526 .iter()
527 .map(|output| output.outputs().len() as u64)
528 .sum::<u64>()),
529 };
530
531 for added_inputs in added_inputs_bundles {
532 partial_transaction = partial_transaction.with_inputs(added_inputs);
533 }
534
535 for added_outputs in added_outputs_bundles {
536 partial_transaction = partial_transaction.with_outputs(added_outputs);
537 }
538
539 let (input_amounts, output_amounts) =
540 self.transaction_builder_get_balance(&partial_transaction);
541
542 for (unit, output_amount) in output_amounts {
543 let input_amount = input_amounts.get(&unit).copied().unwrap_or_default();
544
545 assert!(input_amount >= output_amount, "Transaction is underfunded");
546 }
547
548 let (tx, states) = partial_transaction.build(&self.secp_ctx, thread_rng());
549
550 Ok((tx, states, change_range))
551 }
552
553 pub async fn finalize_and_submit_transaction<F, M>(
565 &self,
566 operation_id: OperationId,
567 operation_type: &str,
568 operation_meta_gen: F,
569 tx_builder: TransactionBuilder,
570 ) -> anyhow::Result<OutPointRange>
571 where
572 F: Fn(OutPointRange) -> M + Clone + MaybeSend + MaybeSync,
573 M: serde::Serialize + MaybeSend,
574 {
575 let operation_type = operation_type.to_owned();
576
577 let autocommit_res = self
578 .db
579 .autocommit(
580 |dbtx, _| {
581 let operation_type = operation_type.clone();
582 let tx_builder = tx_builder.clone();
583 let operation_meta_gen = operation_meta_gen.clone();
584 Box::pin(async move {
585 self.finalize_and_submit_transaction_dbtx(
586 dbtx,
587 operation_id,
588 &operation_type,
589 operation_meta_gen,
590 tx_builder,
591 )
592 .await
593 })
594 },
595 Some(100), )
597 .await;
598
599 match autocommit_res {
600 Ok(txid) => Ok(txid),
601 Err(AutocommitError::ClosureError { error, .. }) => Err(error),
602 Err(AutocommitError::CommitFailed {
603 attempts,
604 last_error,
605 }) => panic!(
606 "Failed to commit tx submission dbtx after {attempts} attempts: {last_error}"
607 ),
608 }
609 }
610
611 pub async fn finalize_and_submit_transaction_dbtx<F, M>(
614 &self,
615 dbtx: &mut DatabaseTransaction<'_>,
616 operation_id: OperationId,
617 operation_type: &str,
618 operation_meta_gen: F,
619 tx_builder: TransactionBuilder,
620 ) -> anyhow::Result<OutPointRange>
621 where
622 F: FnOnce(OutPointRange) -> M + MaybeSend,
623 M: serde::Serialize + MaybeSend,
624 {
625 if Client::operation_exists_dbtx(dbtx, operation_id).await {
626 bail!("There already exists an operation with id {operation_id:?}")
627 }
628
629 let out_point_range = self
630 .finalize_and_submit_transaction_inner(dbtx, operation_id, tx_builder)
631 .await?;
632
633 self.operation_log()
634 .add_operation_log_entry_dbtx(
635 dbtx,
636 operation_id,
637 operation_type,
638 operation_meta_gen(out_point_range),
639 )
640 .await;
641
642 Ok(out_point_range)
643 }
644
645 async fn finalize_and_submit_transaction_inner(
646 &self,
647 dbtx: &mut DatabaseTransaction<'_>,
648 operation_id: OperationId,
649 tx_builder: TransactionBuilder,
650 ) -> anyhow::Result<OutPointRange> {
651 let (transaction, mut states, change_range) = self
652 .finalize_transaction(&mut dbtx.to_ref_nc(), operation_id, tx_builder)
653 .await?;
654
655 if transaction.consensus_encode_to_vec().len() > Transaction::MAX_TX_SIZE {
656 let inputs = transaction
657 .inputs
658 .iter()
659 .map(DynInput::module_instance_id)
660 .collect::<Vec<_>>();
661 let outputs = transaction
662 .outputs
663 .iter()
664 .map(DynOutput::module_instance_id)
665 .collect::<Vec<_>>();
666 warn!(
667 target: LOG_CLIENT_NET_API,
668 size=%transaction.consensus_encode_to_vec().len(),
669 ?inputs,
670 ?outputs,
671 "Transaction too large",
672 );
673 debug!(target: LOG_CLIENT_NET_API, ?transaction, "transaction details");
674 bail!(
675 "The generated transaction would be rejected by the federation for being too large."
676 );
677 }
678
679 let txid = transaction.tx_hash();
680
681 debug!(target: LOG_CLIENT_NET_API, %txid, ?transaction, "Finalized and submitting transaction");
682
683 let tx_submission_sm = DynState::from_typed(
684 TRANSACTION_SUBMISSION_MODULE_INSTANCE,
685 TxSubmissionStatesSM {
686 operation_id,
687 state: TxSubmissionStates::Created(transaction),
688 },
689 );
690 states.push(tx_submission_sm);
691
692 self.executor.add_state_machines_dbtx(dbtx, states).await?;
693
694 self.log_event_dbtx(dbtx, None, TxCreatedEvent { txid, operation_id })
695 .await;
696
697 Ok(OutPointRange::new(txid, IdxRange::from(change_range)))
698 }
699
700 async fn transaction_update_stream(
701 &self,
702 operation_id: OperationId,
703 ) -> BoxStream<'static, TxSubmissionStatesSM> {
704 self.executor
705 .notifier()
706 .module_notifier::<TxSubmissionStatesSM>(
707 TRANSACTION_SUBMISSION_MODULE_INSTANCE,
708 self.final_client.clone(),
709 )
710 .subscribe(operation_id)
711 .await
712 }
713
714 pub async fn operation_exists(&self, operation_id: OperationId) -> bool {
715 let mut dbtx = self.db().begin_transaction_nc().await;
716
717 Client::operation_exists_dbtx(&mut dbtx, operation_id).await
718 }
719
720 pub async fn operation_exists_dbtx(
721 dbtx: &mut DatabaseTransaction<'_>,
722 operation_id: OperationId,
723 ) -> bool {
724 let active_state_exists = dbtx
725 .find_by_prefix(&ActiveOperationStateKeyPrefix { operation_id })
726 .await
727 .next()
728 .await
729 .is_some();
730
731 let inactive_state_exists = dbtx
732 .find_by_prefix(&InactiveOperationStateKeyPrefix { operation_id })
733 .await
734 .next()
735 .await
736 .is_some();
737
738 active_state_exists || inactive_state_exists
739 }
740
741 pub async fn has_active_states(&self, operation_id: OperationId) -> bool {
742 self.db
743 .begin_transaction_nc()
744 .await
745 .find_by_prefix(&ActiveOperationStateKeyPrefix { operation_id })
746 .await
747 .next()
748 .await
749 .is_some()
750 }
751
752 pub async fn await_primary_bitcoin_module_output(
755 &self,
756 operation_id: OperationId,
757 out_point: OutPoint,
758 ) -> anyhow::Result<()> {
759 self.primary_module_for_unit(AmountUnit::BITCOIN)
760 .ok_or_else(|| anyhow!("No primary module available"))?
761 .1
762 .await_primary_module_output(operation_id, out_point)
763 .await
764 }
765
766 pub fn get_first_module<M: ClientModule>(
768 &'_ self,
769 ) -> anyhow::Result<ClientModuleInstance<'_, M>> {
770 let module_kind = M::kind();
771 let id = self
772 .get_first_instance(&module_kind)
773 .ok_or_else(|| format_err!("No modules found of kind {module_kind}"))?;
774 let module: &M = self
775 .try_get_module(id)
776 .ok_or_else(|| format_err!("Unknown module instance {id}"))?
777 .as_any()
778 .downcast_ref::<M>()
779 .ok_or_else(|| format_err!("Module is not of type {}", std::any::type_name::<M>()))?;
780 let (db, _) = self.db().with_prefix_module_id(id);
781 Ok(ClientModuleInstance {
782 id,
783 db,
784 api: self.api().with_module(id),
785 module,
786 })
787 }
788
789 pub fn get_module_client_dyn(
790 &self,
791 instance_id: ModuleInstanceId,
792 ) -> anyhow::Result<&maybe_add_send_sync!(dyn IClientModule)> {
793 self.try_get_module(instance_id)
794 .ok_or(anyhow!("Unknown module instance {}", instance_id))
795 }
796
797 pub fn db(&self) -> &Database {
798 &self.db
799 }
800
801 pub fn endpoints(&self) -> &ConnectorRegistry {
802 &self.connectors
803 }
804
805 pub async fn transaction_updates(&self, operation_id: OperationId) -> TransactionUpdates {
808 TransactionUpdates {
809 update_stream: self.transaction_update_stream(operation_id).await,
810 }
811 }
812
813 pub fn get_first_instance(&self, module_kind: &ModuleKind) -> Option<ModuleInstanceId> {
815 self.modules
816 .iter_modules()
817 .find(|(_, kind, _module)| *kind == module_kind)
818 .map(|(instance_id, _, _)| instance_id)
819 }
820
821 pub async fn root_secret_encoding<T: Decodable>(&self) -> anyhow::Result<T> {
824 get_decoded_client_secret::<T>(self.db()).await
825 }
826
827 pub async fn await_primary_bitcoin_module_outputs(
830 &self,
831 operation_id: OperationId,
832 outputs: Vec<OutPoint>,
833 ) -> anyhow::Result<()> {
834 for out_point in outputs {
835 self.await_primary_bitcoin_module_output(operation_id, out_point)
836 .await?;
837 }
838
839 Ok(())
840 }
841
842 pub async fn get_config_json(&self) -> JsonClientConfig {
848 self.config().await.to_json()
849 }
850
851 #[doc(hidden)]
854 pub async fn get_balance_for_btc(&self) -> anyhow::Result<Amount> {
857 self.get_balance_for_unit(AmountUnit::BITCOIN).await
858 }
859
860 pub async fn get_balance_for_unit(&self, unit: AmountUnit) -> anyhow::Result<Amount> {
861 let (id, module) = self
862 .primary_module_for_unit(unit)
863 .ok_or_else(|| anyhow!("Primary module not available"))?;
864 Ok(module
865 .get_balance(id, &mut self.db().begin_transaction_nc().await, unit)
866 .await)
867 }
868
869 pub async fn subscribe_balance_changes(&self, unit: AmountUnit) -> BoxStream<'static, Amount> {
872 let primary_module_things =
873 if let Some((primary_module_id, primary_module)) = self.primary_module_for_unit(unit) {
874 let balance_changes = primary_module.subscribe_balance_changes().await;
875 let initial_balance = self
876 .get_balance_for_unit(unit)
877 .await
878 .expect("Primary is present");
879
880 Some((
881 primary_module_id,
882 primary_module.clone(),
883 balance_changes,
884 initial_balance,
885 ))
886 } else {
887 None
888 };
889 let db = self.db().clone();
890
891 Box::pin(async_stream::stream! {
892 let Some((primary_module_id, primary_module, mut balance_changes, initial_balance)) = primary_module_things else {
893 pending().await
896 };
897
898
899 yield initial_balance;
900 let mut prev_balance = initial_balance;
901 while let Some(()) = balance_changes.next().await {
902 let mut dbtx = db.begin_transaction_nc().await;
903 let balance = primary_module
904 .get_balance(primary_module_id, &mut dbtx, unit)
905 .await;
906
907 if balance != prev_balance {
909 prev_balance = balance;
910 yield balance;
911 }
912 }
913 })
914 }
915
916 async fn make_api_version_request(
921 delay: Duration,
922 peer_id: PeerId,
923 api: &DynGlobalApi,
924 ) -> (
925 PeerId,
926 Result<SupportedApiVersionsSummary, fedimint_connectors::error::ServerError>,
927 ) {
928 runtime::sleep(delay).await;
929 (
930 peer_id,
931 api.request_single_peer::<SupportedApiVersionsSummary>(
932 VERSION_ENDPOINT.to_owned(),
933 ApiRequestErased::default(),
934 peer_id,
935 )
936 .await,
937 )
938 }
939
940 fn create_api_version_backoff() -> impl Iterator<Item = Duration> {
946 custom_backoff(Duration::from_millis(200), Duration::from_secs(600), None)
947 }
948
949 pub async fn fetch_common_api_versions_from_all_peers(
952 num_peers: NumPeers,
953 api: DynGlobalApi,
954 db: Database,
955 num_responses_sender: watch::Sender<usize>,
956 ) {
957 let mut backoff = Self::create_api_version_backoff();
958
959 let mut requests = FuturesUnordered::new();
962
963 for peer_id in num_peers.peer_ids() {
964 requests.push(Self::make_api_version_request(
965 Duration::ZERO,
966 peer_id,
967 &api,
968 ));
969 }
970
971 let mut num_responses = 0;
972
973 while let Some((peer_id, response)) = requests.next().await {
974 let retry = match response {
975 Err(err) => {
976 let has_previous_response = db
977 .begin_transaction_nc()
978 .await
979 .get_value(&PeerLastApiVersionsSummaryKey(peer_id))
980 .await
981 .is_some();
982 debug!(
983 target: LOG_CLIENT,
984 %peer_id,
985 err = %err.fmt_compact(),
986 %has_previous_response,
987 "Failed to refresh API versions of a peer"
988 );
989
990 !has_previous_response
991 }
992 Ok(o) => {
993 let mut dbtx = db.begin_transaction().await;
996 dbtx.insert_entry(
997 &PeerLastApiVersionsSummaryKey(peer_id),
998 &PeerLastApiVersionsSummary(o),
999 )
1000 .await;
1001 dbtx.commit_tx().await;
1002 false
1003 }
1004 };
1005
1006 if retry {
1007 requests.push(Self::make_api_version_request(
1008 backoff.next().expect("Keeps retrying"),
1009 peer_id,
1010 &api,
1011 ));
1012 } else {
1013 num_responses += 1;
1014 num_responses_sender.send_replace(num_responses);
1015 }
1016 }
1017 }
1018
1019 pub async fn fetch_peers_api_versions_from_threshold_of_peers(
1023 num_peers: NumPeers,
1024 api: DynGlobalApi,
1025 ) -> BTreeMap<PeerId, SupportedApiVersionsSummary> {
1026 let mut backoff = Self::create_api_version_backoff();
1027
1028 let mut requests = FuturesUnordered::new();
1031
1032 for peer_id in num_peers.peer_ids() {
1033 requests.push(Self::make_api_version_request(
1034 Duration::ZERO,
1035 peer_id,
1036 &api,
1037 ));
1038 }
1039
1040 let mut successful_responses = BTreeMap::new();
1041
1042 while successful_responses.len() < num_peers.threshold()
1043 && let Some((peer_id, response)) = requests.next().await
1044 {
1045 let retry = match response {
1046 Err(err) => {
1047 debug!(
1048 target: LOG_CLIENT,
1049 %peer_id,
1050 err = %err.fmt_compact(),
1051 "Failed to fetch API versions from peer"
1052 );
1053 true
1054 }
1055 Ok(response) => {
1056 successful_responses.insert(peer_id, response);
1057 false
1058 }
1059 };
1060
1061 if retry {
1062 requests.push(Self::make_api_version_request(
1063 backoff.next().expect("Keeps retrying"),
1064 peer_id,
1065 &api,
1066 ));
1067 }
1068 }
1069
1070 successful_responses
1071 }
1072
1073 pub async fn fetch_common_api_versions(
1075 config: &ClientConfig,
1076 api: &DynGlobalApi,
1077 ) -> anyhow::Result<BTreeMap<PeerId, SupportedApiVersionsSummary>> {
1078 debug!(
1079 target: LOG_CLIENT,
1080 "Fetching common api versions"
1081 );
1082
1083 let num_peers = NumPeers::from(config.global.api_endpoints.len());
1084
1085 let peer_api_version_sets =
1086 Self::fetch_peers_api_versions_from_threshold_of_peers(num_peers, api.clone()).await;
1087
1088 Ok(peer_api_version_sets)
1089 }
1090
1091 pub async fn write_api_version_cache(
1095 dbtx: &mut DatabaseTransaction<'_>,
1096 api_version_set: ApiVersionSet,
1097 ) {
1098 debug!(
1099 target: LOG_CLIENT,
1100 value = ?api_version_set,
1101 "Writing API version set to cache"
1102 );
1103
1104 dbtx.insert_entry(
1105 &CachedApiVersionSetKey,
1106 &CachedApiVersionSet(api_version_set),
1107 )
1108 .await;
1109 }
1110
1111 pub async fn store_prefetched_api_versions(
1116 db: &Database,
1117 config: &ClientConfig,
1118 client_module_init: &ClientModuleInitRegistry,
1119 peer_api_versions: &BTreeMap<PeerId, SupportedApiVersionsSummary>,
1120 ) {
1121 debug!(
1122 target: LOG_CLIENT,
1123 "Storing {} prefetched peer API version responses and calculating common version set",
1124 peer_api_versions.len()
1125 );
1126
1127 let mut dbtx = db.begin_transaction().await;
1128 let client_supported_versions =
1130 Self::supported_api_versions_summary_static(config, client_module_init);
1131 match fedimint_client_module::api_version_discovery::discover_common_api_versions_set(
1132 &client_supported_versions,
1133 peer_api_versions,
1134 ) {
1135 Ok(common_api_versions) => {
1136 Self::write_api_version_cache(&mut dbtx.to_ref_nc(), common_api_versions).await;
1138 debug!(target: LOG_CLIENT, "Calculated and stored common API version set");
1139 }
1140 Err(err) => {
1141 debug!(target: LOG_CLIENT, err = %err.fmt_compact_anyhow(), "Failed to calculate common API versions from prefetched data");
1142 }
1143 }
1144
1145 for (peer_id, peer_api_versions) in peer_api_versions {
1147 dbtx.insert_entry(
1148 &PeerLastApiVersionsSummaryKey(*peer_id),
1149 &PeerLastApiVersionsSummary(peer_api_versions.clone()),
1150 )
1151 .await;
1152 }
1153 dbtx.commit_tx().await;
1154 debug!(target: LOG_CLIENT, "Stored individual peer API version responses");
1155 }
1156
1157 pub fn supported_api_versions_summary_static(
1159 config: &ClientConfig,
1160 client_module_init: &ClientModuleInitRegistry,
1161 ) -> SupportedApiVersionsSummary {
1162 SupportedApiVersionsSummary {
1163 core: SupportedCoreApiVersions {
1164 core_consensus: config.global.consensus_version,
1165 api: MultiApiVersion::try_from_iter(SUPPORTED_CORE_API_VERSIONS.to_owned())
1166 .expect("must not have conflicting versions"),
1167 },
1168 modules: config
1169 .modules
1170 .iter()
1171 .filter_map(|(&module_instance_id, module_config)| {
1172 client_module_init
1173 .get(module_config.kind())
1174 .map(|module_init| {
1175 (
1176 module_instance_id,
1177 SupportedModuleApiVersions {
1178 core_consensus: config.global.consensus_version,
1179 module_consensus: module_config.version,
1180 api: module_init.supported_api_versions(),
1181 },
1182 )
1183 })
1184 })
1185 .collect(),
1186 }
1187 }
1188
1189 pub async fn load_and_refresh_common_api_version(&self) -> anyhow::Result<ApiVersionSet> {
1190 Self::load_and_refresh_common_api_version_static(
1191 &self.config().await,
1192 &self.module_inits,
1193 self.connectors.clone(),
1194 &self.api,
1195 &self.db,
1196 &self.task_group,
1197 )
1198 .await
1199 }
1200
1201 async fn load_and_refresh_common_api_version_static(
1207 config: &ClientConfig,
1208 module_init: &ClientModuleInitRegistry,
1209 connectors: ConnectorRegistry,
1210 api: &DynGlobalApi,
1211 db: &Database,
1212 task_group: &TaskGroup,
1213 ) -> anyhow::Result<ApiVersionSet> {
1214 if let Some(v) = db
1215 .begin_transaction_nc()
1216 .await
1217 .get_value(&CachedApiVersionSetKey)
1218 .await
1219 {
1220 debug!(
1221 target: LOG_CLIENT,
1222 "Found existing cached common api versions"
1223 );
1224 let config = config.clone();
1225 let client_module_init = module_init.clone();
1226 let api = api.clone();
1227 let db = db.clone();
1228 let task_group = task_group.clone();
1229 task_group
1232 .clone()
1233 .spawn_cancellable("refresh_common_api_version_static", async move {
1234 connectors.wait_for_initialized_connections().await;
1235
1236 if let Err(error) = Self::refresh_common_api_version_static(
1237 &config,
1238 &client_module_init,
1239 &api,
1240 &db,
1241 task_group,
1242 false,
1243 )
1244 .await
1245 {
1246 warn!(
1247 target: LOG_CLIENT,
1248 err = %error.fmt_compact_anyhow(), "Failed to discover common api versions"
1249 );
1250 }
1251 });
1252
1253 return Ok(v.0);
1254 }
1255
1256 info!(
1257 target: LOG_CLIENT,
1258 "Fetching initial API versions "
1259 );
1260 Self::refresh_common_api_version_static(
1261 config,
1262 module_init,
1263 api,
1264 db,
1265 task_group.clone(),
1266 true,
1267 )
1268 .await
1269 }
1270
1271 async fn refresh_common_api_version_static(
1272 config: &ClientConfig,
1273 client_module_init: &ClientModuleInitRegistry,
1274 api: &DynGlobalApi,
1275 db: &Database,
1276 task_group: TaskGroup,
1277 block_until_ok: bool,
1278 ) -> anyhow::Result<ApiVersionSet> {
1279 debug!(
1280 target: LOG_CLIENT,
1281 "Refreshing common api versions"
1282 );
1283
1284 let (num_responses_sender, mut num_responses_receiver) = tokio::sync::watch::channel(0);
1285 let num_peers = NumPeers::from(config.global.api_endpoints.len());
1286
1287 task_group.spawn_cancellable("refresh peers api versions", {
1288 Client::fetch_common_api_versions_from_all_peers(
1289 num_peers,
1290 api.clone(),
1291 db.clone(),
1292 num_responses_sender,
1293 )
1294 });
1295
1296 let common_api_versions = loop {
1297 let _: Result<_, Elapsed> = runtime::timeout(
1305 Duration::from_secs(30),
1306 num_responses_receiver.wait_for(|num| num_peers.threshold() <= *num),
1307 )
1308 .await;
1309
1310 let peer_api_version_sets = Self::load_peers_last_api_versions(db, num_peers).await;
1311
1312 match fedimint_client_module::api_version_discovery::discover_common_api_versions_set(
1313 &Self::supported_api_versions_summary_static(config, client_module_init),
1314 &peer_api_version_sets,
1315 ) {
1316 Ok(o) => break o,
1317 Err(err) if block_until_ok => {
1318 warn!(
1319 target: LOG_CLIENT,
1320 err = %err.fmt_compact_anyhow(),
1321 "Failed to discover API version to use. Retrying..."
1322 );
1323 continue;
1324 }
1325 Err(e) => return Err(e),
1326 }
1327 };
1328
1329 debug!(
1330 target: LOG_CLIENT,
1331 value = ?common_api_versions,
1332 "Updating the cached common api versions"
1333 );
1334 let mut dbtx = db.begin_transaction().await;
1335 let _ = dbtx
1336 .insert_entry(
1337 &CachedApiVersionSetKey,
1338 &CachedApiVersionSet(common_api_versions.clone()),
1339 )
1340 .await;
1341
1342 dbtx.commit_tx().await;
1343
1344 Ok(common_api_versions)
1345 }
1346
1347 pub async fn get_metadata(&self) -> Metadata {
1349 self.db
1350 .begin_transaction_nc()
1351 .await
1352 .get_value(&ClientMetadataKey)
1353 .await
1354 .unwrap_or_else(|| {
1355 warn!(
1356 target: LOG_CLIENT,
1357 "Missing existing metadata. This key should have been set on Client init"
1358 );
1359 Metadata::empty()
1360 })
1361 }
1362
1363 pub async fn set_metadata(&self, metadata: &Metadata) {
1365 self.db
1366 .autocommit::<_, _, anyhow::Error>(
1367 |dbtx, _| {
1368 Box::pin(async {
1369 Self::set_metadata_dbtx(dbtx, metadata).await;
1370 Ok(())
1371 })
1372 },
1373 None,
1374 )
1375 .await
1376 .expect("Failed to autocommit metadata");
1377 }
1378
1379 pub fn has_pending_recoveries(&self) -> bool {
1380 !self
1381 .client_recovery_progress_receiver
1382 .borrow()
1383 .iter()
1384 .all(|(_id, progress)| progress.is_done())
1385 }
1386
1387 pub async fn wait_for_all_recoveries(&self) -> anyhow::Result<()> {
1395 let mut recovery_receiver = self.client_recovery_progress_receiver.clone();
1396 recovery_receiver
1397 .wait_for(|in_progress| {
1398 in_progress
1399 .iter()
1400 .all(|(_id, progress)| progress.is_done())
1401 })
1402 .await
1403 .context("Recovery task completed and update receiver disconnected, but some modules failed to recover")?;
1404
1405 Ok(())
1406 }
1407
1408 pub fn subscribe_to_recovery_progress(
1413 &self,
1414 ) -> impl Stream<Item = (ModuleInstanceId, RecoveryProgress)> + use<> {
1415 WatchStream::new(self.client_recovery_progress_receiver.clone())
1416 .flat_map(futures::stream::iter)
1417 }
1418
1419 pub async fn wait_for_module_kind_recovery(
1420 &self,
1421 module_kind: ModuleKind,
1422 ) -> anyhow::Result<()> {
1423 let mut recovery_receiver = self.client_recovery_progress_receiver.clone();
1424 let config = self.config().await;
1425 recovery_receiver
1426 .wait_for(|in_progress| {
1427 !in_progress
1428 .iter()
1429 .filter(|(module_instance_id, _progress)| {
1430 config.modules[module_instance_id].kind == module_kind
1431 })
1432 .any(|(_id, progress)| !progress.is_done())
1433 })
1434 .await
1435 .context("Recovery task completed and update receiver disconnected, but the desired modules are still unavailable or failed to recover")?;
1436
1437 Ok(())
1438 }
1439
1440 pub async fn wait_for_all_active_state_machines(&self) -> anyhow::Result<()> {
1441 loop {
1442 if self.executor.get_active_states().await.is_empty() {
1443 break;
1444 }
1445 sleep(Duration::from_millis(100)).await;
1446 }
1447 Ok(())
1448 }
1449
1450 pub async fn set_metadata_dbtx(dbtx: &mut DatabaseTransaction<'_>, metadata: &Metadata) {
1452 dbtx.insert_new_entry(&ClientMetadataKey, metadata).await;
1453 }
1454
1455 fn spawn_module_recoveries_task(
1456 &self,
1457 recovery_sender: watch::Sender<BTreeMap<ModuleInstanceId, RecoveryProgress>>,
1458 module_recoveries: BTreeMap<
1459 ModuleInstanceId,
1460 Pin<Box<maybe_add_send!(dyn Future<Output = anyhow::Result<()>>)>>,
1461 >,
1462 module_recovery_progress_receivers: BTreeMap<
1463 ModuleInstanceId,
1464 watch::Receiver<RecoveryProgress>,
1465 >,
1466 ) {
1467 let db = self.db.clone();
1468 let log_ordering_wakeup_tx = self.log_ordering_wakeup_tx.clone();
1469 self.task_group
1470 .spawn("module recoveries", |_task_handle| async {
1471 Self::run_module_recoveries_task(
1472 db,
1473 log_ordering_wakeup_tx,
1474 recovery_sender,
1475 module_recoveries,
1476 module_recovery_progress_receivers,
1477 )
1478 .await;
1479 });
1480 }
1481
1482 async fn run_module_recoveries_task(
1483 db: Database,
1484 log_ordering_wakeup_tx: watch::Sender<()>,
1485 recovery_sender: watch::Sender<BTreeMap<ModuleInstanceId, RecoveryProgress>>,
1486 module_recoveries: BTreeMap<
1487 ModuleInstanceId,
1488 Pin<Box<maybe_add_send!(dyn Future<Output = anyhow::Result<()>>)>>,
1489 >,
1490 module_recovery_progress_receivers: BTreeMap<
1491 ModuleInstanceId,
1492 watch::Receiver<RecoveryProgress>,
1493 >,
1494 ) {
1495 debug!(target: LOG_CLIENT_RECOVERY, num_modules=%module_recovery_progress_receivers.len(), "Staring module recoveries");
1496 let mut completed_stream = Vec::new();
1497 let progress_stream = futures::stream::FuturesUnordered::new();
1498
1499 for (module_instance_id, f) in module_recoveries {
1500 completed_stream.push(futures::stream::once(Box::pin(async move {
1501 match f.await {
1502 Ok(()) => (module_instance_id, None),
1503 Err(err) => {
1504 warn!(
1505 target: LOG_CLIENT,
1506 err = %err.fmt_compact_anyhow(), module_instance_id, "Module recovery failed"
1507 );
1508 futures::future::pending::<()>().await;
1512 unreachable!()
1513 }
1514 }
1515 })));
1516 }
1517
1518 for (module_instance_id, rx) in module_recovery_progress_receivers {
1519 progress_stream.push(
1520 tokio_stream::wrappers::WatchStream::new(rx)
1521 .fuse()
1522 .map(move |progress| (module_instance_id, Some(progress))),
1523 );
1524 }
1525
1526 let mut futures = futures::stream::select(
1527 futures::stream::select_all(progress_stream),
1528 futures::stream::select_all(completed_stream),
1529 );
1530
1531 while let Some((module_instance_id, progress)) = futures.next().await {
1532 let mut dbtx = db.begin_transaction().await;
1533
1534 let prev_progress = *recovery_sender
1535 .borrow()
1536 .get(&module_instance_id)
1537 .expect("existing progress must be present");
1538
1539 let progress = if prev_progress.is_done() {
1540 prev_progress
1542 } else if let Some(progress) = progress {
1543 progress
1544 } else {
1545 prev_progress.to_complete()
1546 };
1547
1548 if !prev_progress.is_done() && progress.is_done() {
1549 info!(
1550 target: LOG_CLIENT,
1551 module_instance_id,
1552 progress = format!("{}/{}", progress.complete, progress.total),
1553 "Recovery complete"
1554 );
1555 dbtx.log_event(
1556 log_ordering_wakeup_tx.clone(),
1557 None,
1558 ModuleRecoveryCompleted {
1559 module_id: module_instance_id,
1560 },
1561 )
1562 .await;
1563 } else {
1564 info!(
1565 target: LOG_CLIENT,
1566 module_instance_id,
1567 progress = format!("{}/{}", progress.complete, progress.total),
1568 "Recovery progress"
1569 );
1570 }
1571
1572 dbtx.insert_entry(
1573 &ClientModuleRecovery { module_instance_id },
1574 &ClientModuleRecoveryState { progress },
1575 )
1576 .await;
1577 dbtx.commit_tx().await;
1578
1579 recovery_sender.send_modify(|v| {
1580 v.insert(module_instance_id, progress);
1581 });
1582 }
1583 debug!(target: LOG_CLIENT_RECOVERY, "Recovery executor stopped");
1584 }
1585
1586 async fn load_peers_last_api_versions(
1587 db: &Database,
1588 num_peers: NumPeers,
1589 ) -> BTreeMap<PeerId, SupportedApiVersionsSummary> {
1590 let mut peer_api_version_sets = BTreeMap::new();
1591
1592 let mut dbtx = db.begin_transaction_nc().await;
1593 for peer_id in num_peers.peer_ids() {
1594 if let Some(v) = dbtx
1595 .get_value(&PeerLastApiVersionsSummaryKey(peer_id))
1596 .await
1597 {
1598 peer_api_version_sets.insert(peer_id, v.0);
1599 }
1600 }
1601 drop(dbtx);
1602 peer_api_version_sets
1603 }
1604
1605 pub async fn get_peer_url_announcements(&self) -> BTreeMap<PeerId, SignedApiAnnouncement> {
1608 self.db()
1609 .begin_transaction_nc()
1610 .await
1611 .find_by_prefix(&ApiAnnouncementPrefix)
1612 .await
1613 .map(|(announcement_key, announcement)| (announcement_key.0, announcement))
1614 .collect()
1615 .await
1616 }
1617
1618 pub async fn get_peer_urls(&self) -> BTreeMap<PeerId, SafeUrl> {
1620 get_api_urls(&self.db, &self.config().await).await
1621 }
1622
1623 pub async fn invite_code(&self, peer: PeerId) -> Option<InviteCode> {
1626 self.get_peer_urls()
1627 .await
1628 .into_iter()
1629 .find_map(|(peer_id, url)| (peer == peer_id).then_some(url))
1630 .map(|peer_url| {
1631 InviteCode::new(
1632 peer_url.clone(),
1633 peer,
1634 self.federation_id(),
1635 self.api_secret.clone(),
1636 )
1637 })
1638 }
1639
1640 pub async fn get_guardian_public_keys_blocking(
1644 &self,
1645 ) -> BTreeMap<PeerId, fedimint_core::secp256k1::PublicKey> {
1646 self.db
1647 .autocommit(
1648 |dbtx, _| {
1649 Box::pin(async move {
1650 let config = self.config().await;
1651
1652 let guardian_pub_keys = self
1653 .get_or_backfill_broadcast_public_keys(dbtx, config)
1654 .await;
1655
1656 Result::<_, ()>::Ok(guardian_pub_keys)
1657 })
1658 },
1659 None,
1660 )
1661 .await
1662 .expect("Will retry forever")
1663 }
1664
1665 async fn get_or_backfill_broadcast_public_keys(
1666 &self,
1667 dbtx: &mut DatabaseTransaction<'_>,
1668 config: ClientConfig,
1669 ) -> BTreeMap<PeerId, PublicKey> {
1670 match config.global.broadcast_public_keys {
1671 Some(guardian_pub_keys) => guardian_pub_keys,
1672 _ => {
1673 let (guardian_pub_keys, new_config) = self.fetch_and_update_config(config).await;
1674
1675 dbtx.insert_entry(&ClientConfigKey, &new_config).await;
1676 *(self.config.write().await) = new_config;
1677 guardian_pub_keys
1678 }
1679 }
1680 }
1681
1682 async fn fetch_session_count(&self) -> FederationResult<u64> {
1683 self.api.session_count().await
1684 }
1685
1686 async fn fetch_and_update_config(
1687 &self,
1688 config: ClientConfig,
1689 ) -> (BTreeMap<PeerId, PublicKey>, ClientConfig) {
1690 let fetched_config = retry(
1691 "Fetching guardian public keys",
1692 backoff_util::background_backoff(),
1693 || async {
1694 Ok(self
1695 .api
1696 .request_current_consensus::<ClientConfig>(
1697 CLIENT_CONFIG_ENDPOINT.to_owned(),
1698 ApiRequestErased::default(),
1699 )
1700 .await?)
1701 },
1702 )
1703 .await
1704 .expect("Will never return on error");
1705
1706 let Some(guardian_pub_keys) = fetched_config.global.broadcast_public_keys else {
1707 warn!(
1708 target: LOG_CLIENT,
1709 "Guardian public keys not found in fetched config, server not updated to 0.4 yet"
1710 );
1711 pending::<()>().await;
1712 unreachable!("Pending will never return");
1713 };
1714
1715 let new_config = ClientConfig {
1716 global: GlobalClientConfig {
1717 broadcast_public_keys: Some(guardian_pub_keys.clone()),
1718 ..config.global
1719 },
1720 modules: config.modules,
1721 };
1722 (guardian_pub_keys, new_config)
1723 }
1724
1725 pub fn handle_global_rpc(
1726 &self,
1727 method: String,
1728 params: serde_json::Value,
1729 ) -> BoxStream<'_, anyhow::Result<serde_json::Value>> {
1730 Box::pin(try_stream! {
1731 match method.as_str() {
1732 "get_balance" => {
1733 let balance = self.get_balance_for_btc().await.unwrap_or_default();
1734 yield serde_json::to_value(balance)?;
1735 }
1736 "subscribe_balance_changes" => {
1737 let req: GetBalanceChangesRequest= serde_json::from_value(params)?;
1738 let mut stream = self.subscribe_balance_changes(req.unit).await;
1739 while let Some(balance) = stream.next().await {
1740 yield serde_json::to_value(balance)?;
1741 }
1742 }
1743 "get_config" => {
1744 let config = self.config().await;
1745 yield serde_json::to_value(config)?;
1746 }
1747 "get_federation_id" => {
1748 let federation_id = self.federation_id();
1749 yield serde_json::to_value(federation_id)?;
1750 }
1751 "get_invite_code" => {
1752 let req: GetInviteCodeRequest = serde_json::from_value(params)?;
1753 let invite_code = self.invite_code(req.peer).await;
1754 yield serde_json::to_value(invite_code)?;
1755 }
1756 "get_operation" => {
1757 let req: GetOperationIdRequest = serde_json::from_value(params)?;
1758 let operation = self.operation_log().get_operation(req.operation_id).await;
1759 yield serde_json::to_value(operation)?;
1760 }
1761 "list_operations" => {
1762 let req: ListOperationsParams = serde_json::from_value(params)?;
1763 let limit = if req.limit.is_none() && req.last_seen.is_none() {
1764 usize::MAX
1765 } else {
1766 req.limit.unwrap_or(usize::MAX)
1767 };
1768 let operations = self.operation_log()
1769 .paginate_operations_rev(limit, req.last_seen)
1770 .await;
1771 yield serde_json::to_value(operations)?;
1772 }
1773 "session_count" => {
1774 let count = self.fetch_session_count().await?;
1775 yield serde_json::to_value(count)?;
1776 }
1777 "has_pending_recoveries" => {
1778 let has_pending = self.has_pending_recoveries();
1779 yield serde_json::to_value(has_pending)?;
1780 }
1781 "wait_for_all_recoveries" => {
1782 self.wait_for_all_recoveries().await?;
1783 yield serde_json::Value::Null;
1784 }
1785 "subscribe_to_recovery_progress" => {
1786 let mut stream = self.subscribe_to_recovery_progress();
1787 while let Some((module_id, progress)) = stream.next().await {
1788 yield serde_json::json!({
1789 "module_id": module_id,
1790 "progress": progress
1791 });
1792 }
1793 }
1794 "backup_to_federation" => {
1795 let metadata = if params.is_null() {
1796 Metadata::from_json_serialized(serde_json::json!({}))
1797 } else {
1798 Metadata::from_json_serialized(params)
1799 };
1800 self.backup_to_federation(metadata).await?;
1801 yield serde_json::Value::Null;
1802 }
1803 _ => {
1804 Err(anyhow::format_err!("Unknown method: {}", method))?;
1805 unreachable!()
1806 },
1807 }
1808 })
1809 }
1810
1811 pub async fn log_event<E>(&self, module_id: Option<ModuleInstanceId>, event: E)
1812 where
1813 E: Event + Send,
1814 {
1815 let mut dbtx = self.db.begin_transaction().await;
1816 self.log_event_dbtx(&mut dbtx, module_id, event).await;
1817 dbtx.commit_tx().await;
1818 }
1819
1820 pub async fn log_event_dbtx<E, Cap>(
1821 &self,
1822 dbtx: &mut DatabaseTransaction<'_, Cap>,
1823 module_id: Option<ModuleInstanceId>,
1824 event: E,
1825 ) where
1826 E: Event + Send,
1827 Cap: Send,
1828 {
1829 dbtx.log_event(self.log_ordering_wakeup_tx.clone(), module_id, event)
1830 .await;
1831 }
1832
1833 pub async fn log_event_raw_dbtx<Cap>(
1834 &self,
1835 dbtx: &mut DatabaseTransaction<'_, Cap>,
1836 kind: EventKind,
1837 module: Option<(ModuleKind, ModuleInstanceId)>,
1838 payload: Vec<u8>,
1839 persist: EventPersistence,
1840 ) where
1841 Cap: Send,
1842 {
1843 let module_id = module.as_ref().map(|m| m.1);
1844 let module_kind = module.map(|m| m.0);
1845 dbtx.log_event_raw(
1846 self.log_ordering_wakeup_tx.clone(),
1847 kind,
1848 module_kind,
1849 module_id,
1850 payload,
1851 persist,
1852 )
1853 .await;
1854 }
1855
1856 pub fn built_in_application_event_log_tracker(&self) -> DynEventLogTrimableTracker {
1868 struct BuiltInApplicationEventLogTracker;
1869
1870 #[apply(async_trait_maybe_send!)]
1871 impl EventLogTrimableTracker for BuiltInApplicationEventLogTracker {
1872 async fn store(
1874 &mut self,
1875 dbtx: &mut DatabaseTransaction<NonCommittable>,
1876 pos: EventLogTrimableId,
1877 ) -> anyhow::Result<()> {
1878 dbtx.insert_entry(&DefaultApplicationEventLogKey, &pos)
1879 .await;
1880 Ok(())
1881 }
1882
1883 async fn load(
1885 &mut self,
1886 dbtx: &mut DatabaseTransaction<NonCommittable>,
1887 ) -> anyhow::Result<Option<EventLogTrimableId>> {
1888 Ok(dbtx.get_value(&DefaultApplicationEventLogKey).await)
1889 }
1890 }
1891 Box::new(BuiltInApplicationEventLogTracker)
1892 }
1893
1894 pub async fn handle_historical_events<F, R>(
1902 &self,
1903 tracker: fedimint_eventlog::DynEventLogTracker,
1904 handler_fn: F,
1905 ) -> anyhow::Result<()>
1906 where
1907 F: Fn(&mut DatabaseTransaction<NonCommittable>, EventLogEntry) -> R,
1908 R: Future<Output = anyhow::Result<()>>,
1909 {
1910 fedimint_eventlog::handle_events(
1911 self.db.clone(),
1912 tracker,
1913 self.log_event_added_rx.clone(),
1914 handler_fn,
1915 )
1916 .await
1917 }
1918
1919 pub async fn handle_events<F, R>(
1938 &self,
1939 tracker: fedimint_eventlog::DynEventLogTrimableTracker,
1940 handler_fn: F,
1941 ) -> anyhow::Result<()>
1942 where
1943 F: Fn(&mut DatabaseTransaction<NonCommittable>, EventLogEntry) -> R,
1944 R: Future<Output = anyhow::Result<()>>,
1945 {
1946 fedimint_eventlog::handle_trimable_events(
1947 self.db.clone(),
1948 tracker,
1949 self.log_event_added_rx.clone(),
1950 handler_fn,
1951 )
1952 .await
1953 }
1954
1955 pub async fn get_event_log(
1956 &self,
1957 pos: Option<EventLogId>,
1958 limit: u64,
1959 ) -> Vec<PersistedLogEntry> {
1960 self.get_event_log_dbtx(&mut self.db.begin_transaction_nc().await, pos, limit)
1961 .await
1962 }
1963
1964 pub async fn get_event_log_trimable(
1965 &self,
1966 pos: Option<EventLogTrimableId>,
1967 limit: u64,
1968 ) -> Vec<PersistedLogEntry> {
1969 self.get_event_log_trimable_dbtx(&mut self.db.begin_transaction_nc().await, pos, limit)
1970 .await
1971 }
1972
1973 pub async fn get_event_log_dbtx<Cap>(
1974 &self,
1975 dbtx: &mut DatabaseTransaction<'_, Cap>,
1976 pos: Option<EventLogId>,
1977 limit: u64,
1978 ) -> Vec<PersistedLogEntry>
1979 where
1980 Cap: Send,
1981 {
1982 dbtx.get_event_log(pos, limit).await
1983 }
1984
1985 pub async fn get_event_log_trimable_dbtx<Cap>(
1986 &self,
1987 dbtx: &mut DatabaseTransaction<'_, Cap>,
1988 pos: Option<EventLogTrimableId>,
1989 limit: u64,
1990 ) -> Vec<PersistedLogEntry>
1991 where
1992 Cap: Send,
1993 {
1994 dbtx.get_event_log_trimable(pos, limit).await
1995 }
1996
1997 pub fn get_event_log_transient_receiver(&self) -> broadcast::Receiver<EventLogEntry> {
1999 self.log_event_added_transient_tx.subscribe()
2000 }
2001
2002 pub fn log_event_added_rx(&self) -> watch::Receiver<()> {
2004 self.log_event_added_rx.clone()
2005 }
2006
2007 pub fn iroh_enable_dht(&self) -> bool {
2008 self.iroh_enable_dht
2009 }
2010
2011 pub(crate) async fn run_core_migrations(
2012 db_no_decoders: &Database,
2013 ) -> Result<(), anyhow::Error> {
2014 let mut dbtx = db_no_decoders.begin_transaction().await;
2015 apply_migrations_core_client_dbtx(&mut dbtx.to_ref_nc(), "fedimint-client".to_string())
2016 .await?;
2017 if is_running_in_test_env() {
2018 verify_client_db_integrity_dbtx(&mut dbtx.to_ref_nc()).await;
2019 }
2020 dbtx.commit_tx_result().await?;
2021 Ok(())
2022 }
2023
2024 fn primary_modules_for_unit(
2026 &self,
2027 unit: AmountUnit,
2028 ) -> impl Iterator<Item = (ModuleInstanceId, &DynClientModule)> {
2029 self.primary_modules
2030 .iter()
2031 .flat_map(move |(_prio, candidates)| {
2032 candidates
2033 .specific
2034 .get(&unit)
2035 .into_iter()
2036 .flatten()
2037 .copied()
2038 .chain(candidates.wildcard.iter().copied())
2040 })
2041 .map(|id| (id, self.modules.get_expect(id)))
2042 }
2043
2044 pub fn primary_module_for_unit(
2048 &self,
2049 unit: AmountUnit,
2050 ) -> Option<(ModuleInstanceId, &DynClientModule)> {
2051 self.primary_modules_for_unit(unit).next()
2052 }
2053
2054 pub fn primary_module_for_btc(&self) -> (ModuleInstanceId, &DynClientModule) {
2056 self.primary_module_for_unit(AmountUnit::BITCOIN)
2057 .expect("No primary module for Bitcoin")
2058 }
2059}
2060
2061#[apply(async_trait_maybe_send!)]
2062impl ClientContextIface for Client {
2063 fn get_module(&self, instance: ModuleInstanceId) -> &maybe_add_send_sync!(dyn IClientModule) {
2064 Client::get_module(self, instance)
2065 }
2066
2067 fn api_clone(&self) -> DynGlobalApi {
2068 Client::api_clone(self)
2069 }
2070 fn decoders(&self) -> &ModuleDecoderRegistry {
2071 Client::decoders(self)
2072 }
2073
2074 async fn finalize_and_submit_transaction(
2075 &self,
2076 operation_id: OperationId,
2077 operation_type: &str,
2078 operation_meta_gen: Box<maybe_add_send_sync!(dyn Fn(OutPointRange) -> serde_json::Value)>,
2079 tx_builder: TransactionBuilder,
2080 ) -> anyhow::Result<OutPointRange> {
2081 Client::finalize_and_submit_transaction(
2082 self,
2083 operation_id,
2084 operation_type,
2085 &operation_meta_gen,
2087 tx_builder,
2088 )
2089 .await
2090 }
2091
2092 async fn finalize_and_submit_transaction_dbtx(
2093 &self,
2094 dbtx: &mut DatabaseTransaction<'_>,
2095 operation_id: OperationId,
2096 operation_type: &str,
2097 operation_meta_gen: Box<maybe_add_send_sync!(dyn Fn(OutPointRange) -> serde_json::Value)>,
2098 tx_builder: TransactionBuilder,
2099 ) -> anyhow::Result<OutPointRange> {
2100 Client::finalize_and_submit_transaction_dbtx(
2101 self,
2102 dbtx,
2103 operation_id,
2104 operation_type,
2105 &operation_meta_gen,
2106 tx_builder,
2107 )
2108 .await
2109 }
2110
2111 async fn finalize_and_submit_transaction_inner(
2112 &self,
2113 dbtx: &mut DatabaseTransaction<'_>,
2114 operation_id: OperationId,
2115 tx_builder: TransactionBuilder,
2116 ) -> anyhow::Result<OutPointRange> {
2117 Client::finalize_and_submit_transaction_inner(self, dbtx, operation_id, tx_builder).await
2118 }
2119
2120 async fn transaction_updates(&self, operation_id: OperationId) -> TransactionUpdates {
2121 Client::transaction_updates(self, operation_id).await
2122 }
2123
2124 async fn await_primary_module_outputs(
2125 &self,
2126 operation_id: OperationId,
2127 outputs: Vec<OutPoint>,
2129 ) -> anyhow::Result<()> {
2130 Client::await_primary_bitcoin_module_outputs(self, operation_id, outputs).await
2131 }
2132
2133 fn operation_log(&self) -> &dyn IOperationLog {
2134 Client::operation_log(self)
2135 }
2136
2137 async fn has_active_states(&self, operation_id: OperationId) -> bool {
2138 Client::has_active_states(self, operation_id).await
2139 }
2140
2141 async fn operation_exists(&self, operation_id: OperationId) -> bool {
2142 Client::operation_exists(self, operation_id).await
2143 }
2144
2145 async fn config(&self) -> ClientConfig {
2146 Client::config(self).await
2147 }
2148
2149 fn db(&self) -> &Database {
2150 Client::db(self)
2151 }
2152
2153 fn executor(&self) -> &(maybe_add_send_sync!(dyn IExecutor + 'static)) {
2154 Client::executor(self)
2155 }
2156
2157 async fn invite_code(&self, peer: PeerId) -> Option<InviteCode> {
2158 Client::invite_code(self, peer).await
2159 }
2160
2161 fn get_internal_payment_markers(&self) -> anyhow::Result<(PublicKey, u64)> {
2162 Client::get_internal_payment_markers(self)
2163 }
2164
2165 async fn log_event_json(
2166 &self,
2167 dbtx: &mut DatabaseTransaction<'_, NonCommittable>,
2168 module_kind: Option<ModuleKind>,
2169 module_id: ModuleInstanceId,
2170 kind: EventKind,
2171 payload: serde_json::Value,
2172 persist: EventPersistence,
2173 ) {
2174 dbtx.ensure_global()
2175 .expect("Must be called with global dbtx");
2176 self.log_event_raw_dbtx(
2177 dbtx,
2178 kind,
2179 module_kind.map(|kind| (kind, module_id)),
2180 serde_json::to_vec(&payload).expect("Serialization can't fail"),
2181 persist,
2182 )
2183 .await;
2184 }
2185
2186 async fn read_operation_active_states<'dbtx>(
2187 &self,
2188 operation_id: OperationId,
2189 module_id: ModuleInstanceId,
2190 dbtx: &'dbtx mut DatabaseTransaction<'_>,
2191 ) -> Pin<Box<maybe_add_send!(dyn Stream<Item = (ActiveStateKey, ActiveStateMeta)> + 'dbtx)>>
2192 {
2193 Box::pin(
2194 dbtx.find_by_prefix(&ActiveModuleOperationStateKeyPrefix {
2195 operation_id,
2196 module_instance: module_id,
2197 })
2198 .await
2199 .map(move |(k, v)| (k.0, v)),
2200 )
2201 }
2202 async fn read_operation_inactive_states<'dbtx>(
2203 &self,
2204 operation_id: OperationId,
2205 module_id: ModuleInstanceId,
2206 dbtx: &'dbtx mut DatabaseTransaction<'_>,
2207 ) -> Pin<Box<maybe_add_send!(dyn Stream<Item = (InactiveStateKey, InactiveStateMeta)> + 'dbtx)>>
2208 {
2209 Box::pin(
2210 dbtx.find_by_prefix(&InactiveModuleOperationStateKeyPrefix {
2211 operation_id,
2212 module_instance: module_id,
2213 })
2214 .await
2215 .map(move |(k, v)| (k.0, v)),
2216 )
2217 }
2218}
2219
2220impl fmt::Debug for Client {
2222 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2223 write!(f, "Client")
2224 }
2225}
2226
2227pub fn client_decoders<'a>(
2228 registry: &ModuleInitRegistry<DynClientModuleInit>,
2229 module_kinds: impl Iterator<Item = (ModuleInstanceId, &'a ModuleKind)>,
2230) -> ModuleDecoderRegistry {
2231 let mut modules = BTreeMap::new();
2232 for (id, kind) in module_kinds {
2233 let Some(init) = registry.get(kind) else {
2234 debug!("Detected configuration for unsupported module id: {id}, kind: {kind}");
2235 continue;
2236 };
2237
2238 modules.insert(
2239 id,
2240 (
2241 kind.clone(),
2242 IClientModuleInit::decoder(AsRef::<dyn IClientModuleInit + 'static>::as_ref(init)),
2243 ),
2244 );
2245 }
2246 ModuleDecoderRegistry::from(modules)
2247}