Skip to main content

fedimint_client/
client.rs

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
103/// List of core api versions supported by the implementation.
104/// Notably `major` version is the one being supported, and corresponding
105/// `minor` version is the one required (for given `major` version).
106const SUPPORTED_CORE_API_VERSIONS: &[fedimint_core::module::ApiVersion] =
107    &[ApiVersion { major: 0, minor: 0 }];
108
109/// Primary module candidates at specific priority level
110#[derive(Default)]
111pub(crate) struct PrimaryModuleCandidates {
112    /// Modules that listed specific units they handle
113    specific: BTreeMap<AmountUnit, Vec<ModuleInstanceId>>,
114    /// Modules handling any unit
115    wildcard: Vec<ModuleInstanceId>,
116}
117
118/// Main client type
119///
120/// A handle and API to interacting with a single federation. End user
121/// applications that want to support interacting with multiple federations at
122/// the same time, will need to instantiate and manage multiple instances of
123/// this struct.
124///
125/// Under the hood it is starting and managing service tasks, state machines,
126/// database and other resources required.
127///
128/// This type is shared externally and internally, and
129/// [`crate::ClientHandle`] is responsible for external lifecycle management
130/// and resource freeing of the [`Client`].
131pub 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    /// Updates about client recovery progress
153    client_recovery_progress_receiver:
154        watch::Receiver<BTreeMap<ModuleInstanceId, RecoveryProgress>>,
155
156    /// Internal client sender to wake up log ordering task every time a
157    /// (unuordered) log event is added.
158    log_ordering_wakeup_tx: watch::Sender<()>,
159    /// Receiver for events fired every time (ordered) log event is added.
160    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    /// Initialize a client builder that can be configured to create a new
186    /// client.
187    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    /// Returns a stream that emits the current connection status of all peers
200    /// whenever any peer's status changes. Emits initial state immediately.
201    pub fn connection_status_stream(&self) -> impl Stream<Item = BTreeMap<PeerId, bool>> {
202        self.api.connection_status_stream()
203    }
204
205    /// Get the [`TaskGroup`] that is tied to Client's lifetime.
206    pub fn task_group(&self) -> &TaskGroup {
207        &self.task_group
208    }
209
210    /// Returns all registered Prometheus metrics encoded in text format.
211    ///
212    /// This can be used by downstream clients to expose metrics via their own
213    /// HTTP server or print them for debugging purposes.
214    pub fn get_metrics() -> anyhow::Result<String> {
215        fedimint_metrics::get_metrics()
216    }
217
218    /// Useful for our CLI tooling, not meant for external use
219    #[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        // Don't overwrite an existing secret
246        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    // TODO: change to `-> Option<&str>`
334    pub fn api_secret(&self) -> &Option<String> {
335        &self.api_secret
336    }
337
338    /// Returns the core API version that the federation supports
339    ///
340    /// This reads from the cached version stored during client initialization.
341    /// If no cache is available (e.g., during initial setup), returns a default
342    /// version (0, 0).
343    pub async fn core_api_version(&self) -> ApiVersion {
344        // Try to get from cache. If not available, return a conservative
345        // default. The cache should always be populated after successful client init.
346        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    /// Returns a reference to the module, panics if not found
360    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    /// Returns the input amount and output amount of a transaction
377    ///
378    /// # Panics
379    /// If any of the input or output versions in the transaction builder are
380    /// unknown by the respective module.
381    fn transaction_builder_get_balance(&self, builder: &TransactionBuilder) -> (Amounts, Amounts) {
382        // FIXME: prevent overflows, currently not suitable for untrusted input
383        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    /// Get metadata value from the federation config itself
418    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    // TODO: implement as part of [`OperationLog`]
435    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    /// Get the meta manager to read meta fields.
457    pub fn meta_service(&self) -> &Arc<MetaService> {
458        &self.meta_service
459    }
460
461    /// Get the meta manager to read meta fields.
462    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    /// Adds funding to a transaction or removes over-funding via change.
472    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        // The way currently things are implemented is OK for modules which can
484        // collect a fee relative to one being used, but will break down in any
485        // fancy scenarios. Future TODOs:
486        //
487        // * create_final_inputs_and_outputs needs to get broken down, so we can use
488        //   primary modules using priorities (possibly separate prios for inputs and
489        //   outputs to be able to drain modules, etc.); we need the split "check if
490        //   possible" and "take" steps,
491        // * extra inputs and outputs adding fees needs to be taken into account,
492        //   possibly with some looping
493        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        // This is the range of  outputs that will be added to the transaction
520        // in order to balance it. Notice that it may stay empty in case the transaction
521        // is already balanced.
522        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    /// Add funding and/or change to the transaction builder as needed, finalize
554    /// the transaction and submit it to the federation.
555    ///
556    /// ## Errors
557    /// The function will return an error if the operation with given ID already
558    /// exists.
559    ///
560    /// ## Panics
561    /// The function will panic if the database transaction collides with
562    /// other and fails with others too often, this should not happen except for
563    /// excessively concurrent scenarios.
564    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), // TODO: handle what happens after 100 retries
596            )
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    /// See [`Self::finalize_and_submit_transaction`], just inside a database
612    /// transaction.
613    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    /// Waits for an output from the primary module to reach its final
753    /// state.
754    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    /// Returns a reference to a typed module client instance by kind
767    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    /// Returns a stream of transaction updates for the given operation id that
806    /// can later be used to watch for a specific transaction being accepted.
807    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    /// Returns the instance id of the first module of the given kind.
814    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    /// Returns the data from which the client's root secret is derived (e.g.
822    /// BIP39 seed phrase struct).
823    pub async fn root_secret_encoding<T: Decodable>(&self) -> anyhow::Result<T> {
824        get_decoded_client_secret::<T>(self.db()).await
825    }
826
827    /// Waits for outputs from the primary module to reach its final
828    /// state.
829    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    /// Returns the config of the client in JSON format.
843    ///
844    /// Compared to the consensus module format where module configs are binary
845    /// encoded this format cannot be cryptographically verified but is easier
846    /// to consume and to some degree human-readable.
847    pub async fn get_config_json(&self) -> JsonClientConfig {
848        self.config().await.to_json()
849    }
850
851    // Ideally this would not be in the API, but there's a lot of places where this
852    // makes it easier.
853    #[doc(hidden)]
854    /// Like [`Self::get_balance`] but returns an error if primary module is not
855    /// available
856    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    /// Returns a stream that yields the current client balance every time it
870    /// changes.
871    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                // If there is no primary module, there will not be one until client is
894                // restarted
895                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                // Deduplicate in case modules cannot always tell if the balance actually changed
908                if balance != prev_balance {
909                    prev_balance = balance;
910                    yield balance;
911                }
912            }
913        })
914    }
915
916    /// Make a single API version request to a peer after a delay.
917    ///
918    /// The delay is here to unify the type of a future both for initial request
919    /// and possible retries.
920    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    /// Create a backoff strategy for API version requests.
941    ///
942    /// Keep trying, initially somewhat aggressively, but after a while retry
943    /// very slowly, because chances for response are getting lower and
944    /// lower.
945    fn create_api_version_backoff() -> impl Iterator<Item = Duration> {
946        custom_backoff(Duration::from_millis(200), Duration::from_secs(600), None)
947    }
948
949    /// Query the federation for API version support and then calculate
950    /// the best API version to use (supported by most guardians).
951    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        // NOTE: `FuturesUnordered` is a footgun, but since we only poll it for result
960        // and make a single async db write operation, it should be OK.
961        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                    // Save the response to the database right away, just to
994                    // not lose it
995                    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    /// Fetch API versions from peers, retrying until we get threshold number of
1020    /// successful responses. Returns the successful responses collected
1021    /// from at least `num_peers.threshold()` peers.
1022    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        // NOTE: `FuturesUnordered` is a footgun, but since we only poll it for result
1029        // and collect responses, it should be OK.
1030        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    /// Fetch API versions from peers and discover common API versions to use.
1074    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    /// Write API version set to database cache.
1092    /// Used when we have a pre-calculated API version set that should be stored
1093    /// for later use.
1094    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    /// Store prefetched peer API version responses and calculate/store common
1112    /// API version set. This processes the individual peer responses by
1113    /// storing them in the database and calculating the common API version
1114    /// set for caching.
1115    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        // Calculate common API version set from individual responses
1129        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                // Write the calculated common API version set to database cache
1137                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        // Store individual peer responses to database
1146        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    /// [`SupportedApiVersionsSummary`] that the client and its modules support
1158    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    /// Load the common api versions to use from cache and start a background
1202    /// process to refresh them.
1203    ///
1204    /// This is a compromise, so we not have to wait for version discovery to
1205    /// complete every time a [`Client`] is being built.
1206    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            // Separate task group, because we actually don't want to be waiting for this to
1230            // finish, and it's just best effort.
1231            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            // Wait to collect enough answers before calculating a set of common api
1298            // versions to use. Note that all peers individual responses from
1299            // previous attempts are still being used, and requests, or even
1300            // retries for response of peers are not actually cancelled, as they
1301            // are happening on a separate task. This is all just to bound the
1302            // time user can be waiting for the join operation to finish, at the
1303            // risk of picking wrong version in very rare circumstances.
1304            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    /// Get the client [`Metadata`]
1348    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    /// Set the client [`Metadata`]
1364    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    /// Wait for all module recoveries to finish
1388    ///
1389    /// This will block until the recovery task is done with recoveries.
1390    /// Returns success if all recovery tasks are complete (success case),
1391    /// or an error if some modules could not complete the recovery at the time.
1392    ///
1393    /// A bit of a heavy approach.
1394    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    /// Subscribe to recover progress for all the modules.
1409    ///
1410    /// This stream can contain duplicate progress for a module.
1411    /// Don't use this stream for detecting completion of recovery.
1412    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    /// Set the client [`Metadata`]
1451    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                        // a module recovery that failed reports and error and
1509                        // just never finishes, so we don't need a separate state
1510                        // for it
1511                        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                // since updates might be out of order, once done, stick with it
1541                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    /// You likely want to use [`Client::get_peer_urls`]. This function returns
1606    /// only the announcements and doesn't use the config as fallback.
1607    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    /// Returns a list of guardian API URLs
1619    pub async fn get_peer_urls(&self) -> BTreeMap<PeerId, SafeUrl> {
1620        get_api_urls(&self.db, &self.config().await).await
1621    }
1622
1623    /// Create an invite code with the api endpoint of the given peer which can
1624    /// be used to download this client config
1625    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    /// Blocks till the client has synced the guardian public key set
1641    /// (introduced in version 0.4) and returns it. Once it has been fetched
1642    /// once this function is guaranteed to return immediately.
1643    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    /// Built in event log (trimmable) tracker
1857    ///
1858    /// For the convenience of downstream applications, [`Client`] can store
1859    /// internally event log position for the main application using/driving it.
1860    ///
1861    /// Note that this position is a singleton, so this tracker should not be
1862    /// used for multiple purposes or applications, etc. at the same time.
1863    ///
1864    /// If the application has a need to follow log using multiple trackers, it
1865    /// should implement own [`DynEventLogTrimableTracker`] and store its
1866    /// persient data by itself.
1867    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            // Store position in the event log
1873            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            /// Load the last previous stored position (or None if never stored)
1884            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    /// Like [`Self::handle_events`] but for historical data.
1895    ///
1896    ///
1897    /// This function can be used to process subset of events
1898    /// that is infrequent and important enough to be persisted
1899    /// forever. Most applications should prefer to use [`Self::handle_events`]
1900    /// which emits *all* events.
1901    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    /// Handle events emitted by the client
1920    ///
1921    /// This is a preferred method for reactive & asynchronous
1922    /// processing of events emitted by the client.
1923    ///
1924    /// It needs a `tracker` that will persist the position in the log
1925    /// as it is being handled. You can use the
1926    /// [`Client::built_in_application_event_log_tracker`] if this call is
1927    /// used for the single main application handling this instance of the
1928    /// [`Client`]. Otherwise you should implement your own tracker.
1929    ///
1930    /// This handler will call `handle_fn` with ever event emitted by
1931    /// [`Client`], including transient ones. The caller should atomically
1932    /// handle each event it is interested in and ignore other ones.
1933    ///
1934    /// This method returns only when client is shutting down or on internal
1935    /// error, so typically should be called in a background task dedicated
1936    /// to handling events.
1937    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    /// Register to receiver all new transient (unpersisted) events
1998    pub fn get_event_log_transient_receiver(&self) -> broadcast::Receiver<EventLogEntry> {
1999        self.log_event_added_transient_tx.subscribe()
2000    }
2001
2002    /// Get a receiver that signals when new events are added to the event log
2003    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    /// Iterator over primary modules for a given `unit`
2025    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                    // within same priority, wildcard matches come last
2039                    .chain(candidates.wildcard.iter().copied())
2040            })
2041            .map(|id| (id, self.modules.get_expect(id)))
2042    }
2043
2044    /// Primary module to use for `unit`
2045    ///
2046    /// Currently, just pick the first (highest priority) match
2047    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    /// [`Self::primary_module_for_unit`] for Bitcoin
2055    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            // |out_point_range| operation_meta_gen(out_point_range),
2086            &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        // TODO: make `impl Iterator<Item = ...>`
2128        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
2220// TODO: impl `Debug` for `Client` and derive here
2221impl 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}