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_bitcoind::DynBitcoindRpc;
19use fedimint_client_module::module::recovery::RecoveryProgress;
20use fedimint_client_module::module::{
21    ClientContextIface, ClientModule, ClientModuleRegistry, DynClientModule, FinalClientIface,
22    IClientModule, IdxRange, OutPointRange, PrimaryModulePriority,
23};
24use fedimint_client_module::oplog::IOperationLog;
25use fedimint_client_module::secret::{PlainRootSecretStrategy, RootSecretStrategy as _};
26use fedimint_client_module::sm::executor::{ActiveStateKey, IExecutor, InactiveStateKey};
27use fedimint_client_module::sm::{ActiveStateMeta, DynState, InactiveStateMeta};
28use fedimint_client_module::transaction::{
29    FeeQuote, FeeQuoteRequest, TRANSACTION_SUBMISSION_MODULE_INSTANCE, TransactionBuilder,
30    TxSubmissionStates, TxSubmissionStatesSM,
31};
32use fedimint_client_module::{
33    AddStateMachinesResult, ClientModuleInstance, GetInviteCodeRequest, ModuleGlobalContextGen,
34    ModuleRecoveryCompleted, TransactionUpdates, TxCreatedEvent,
35};
36use fedimint_connectors::{ConnectorRegistry, PeerStatus};
37use fedimint_core::config::{
38    ClientConfig, FederationId, GlobalClientConfig, JsonClientConfig, ModuleInitRegistry,
39};
40use fedimint_core::core::{DynInput, DynOutput, ModuleInstanceId, ModuleKind, OperationId};
41use fedimint_core::db::{
42    AutocommitError, Database, DatabaseRecord, DatabaseTransaction,
43    IDatabaseTransactionOpsCore as _, IDatabaseTransactionOpsCoreTyped as _, NonCommittable,
44};
45use fedimint_core::encoding::{Decodable, Encodable};
46use fedimint_core::endpoint_constants::{CLIENT_CONFIG_ENDPOINT, VERSION_ENDPOINT};
47use fedimint_core::envs::is_running_in_test_env;
48use fedimint_core::invite_code::InviteCode;
49use fedimint_core::module::registry::{ModuleDecoderRegistry, ModuleRegistry};
50use fedimint_core::module::{
51    AmountUnit, Amounts, ApiRequestErased, ApiVersion, MultiApiVersion,
52    SupportedApiVersionsSummary, SupportedCoreApiVersions, SupportedModuleApiVersions,
53};
54use fedimint_core::net::api_announcement::SignedApiAnnouncement;
55use fedimint_core::runtime::sleep;
56use fedimint_core::task::{
57    Elapsed, MaybeSend, MaybeSync, ShuttingDownError, TaskGroup, TaskHandle,
58};
59use fedimint_core::transaction::Transaction;
60use fedimint_core::util::backoff_util::custom_backoff;
61use fedimint_core::util::{
62    BoxStream, FmtCompact as _, FmtCompactAnyhow as _, SafeUrl, backoff_util, retry,
63};
64use fedimint_core::{
65    Amount, ChainId, NumPeers, OutPoint, PeerId, apply, async_trait_maybe_send, maybe_add_send,
66    maybe_add_send_sync, runtime,
67};
68use fedimint_derive_secret::DerivableSecret;
69use fedimint_eventlog::{
70    DBTransactionEventLogExt as _, DynEventLogTrimableTracker, Event, EventKind, EventLogEntry,
71    EventLogId, EventLogTrimableId, EventLogTrimableTracker, EventPersistence, PersistedLogEntry,
72};
73use fedimint_logging::{LOG_CLIENT, LOG_CLIENT_NET_API, LOG_CLIENT_RECOVERY};
74use futures::stream::FuturesUnordered;
75use futures::{Stream, StreamExt as _};
76use global_ctx::ModuleGlobalClientContext;
77use serde::{Deserialize, Serialize};
78use tokio::sync::{broadcast, oneshot, watch};
79use tokio_stream::wrappers::WatchStream;
80use tracing::{Span, debug, info, warn};
81
82use crate::ClientBuilder;
83use crate::api_announcements::{ApiAnnouncementPrefix, get_api_urls};
84use crate::backup::Metadata;
85use crate::client::event_log::DefaultApplicationEventLogKey;
86use crate::db::{
87    ApiSecretKey, CachedApiVersionSet, CachedApiVersionSetKey, ChainIdKey,
88    ChronologicalOperationLogKey, ClientConfigKey, ClientMetadataKey, ClientModuleRecovery,
89    ClientModuleRecoveryState, EncodedClientSecretKey, OperationLogKey, PeerLastApiVersionsSummary,
90    PeerLastApiVersionsSummaryKey, PendingClientConfigKey, TransactionFeesKey,
91    apply_migrations_core_client_dbtx, get_decoded_client_secret, verify_client_db_integrity_dbtx,
92};
93use crate::meta::MetaService;
94use crate::module_init::{ClientModuleInitRegistry, DynClientModuleInit, IClientModuleInit};
95use crate::oplog::OperationLog;
96use crate::sm::executor::{
97    ActiveModuleOperationStateKeyPrefix, ActiveOperationStateKeyPrefix, Executor,
98    InactiveModuleOperationStateKeyPrefix, InactiveOperationStateKeyPrefix,
99};
100
101pub(crate) mod builder;
102pub(crate) mod event_log;
103pub(crate) mod global_ctx;
104pub(crate) mod handle;
105
106#[cfg(test)]
107mod tests;
108
109/// List of core api versions supported by the implementation.
110/// Notably `major` version is the one being supported, and corresponding
111/// `minor` version is the one required (for given `major` version).
112const SUPPORTED_CORE_API_VERSIONS: &[fedimint_core::module::ApiVersion] =
113    &[ApiVersion { major: 0, minor: 0 }];
114
115struct FinalizedTransaction {
116    transaction: Transaction,
117    states: Vec<DynState>,
118    change_range: Range<u64>,
119    fees: Amounts,
120}
121
122/// Primary module candidates at specific priority level
123#[derive(Default)]
124pub(crate) struct PrimaryModuleCandidates {
125    /// Modules that listed specific units they handle
126    specific: BTreeMap<AmountUnit, Vec<ModuleInstanceId>>,
127    /// Modules handling any unit
128    wildcard: Vec<ModuleInstanceId>,
129}
130
131/// An in-progress module recovery future, resolving to the amount recovered
132/// from the module (if it tracks one) once recovery completes.
133pub(crate) type ModuleRecoveryFuture =
134    Pin<Box<maybe_add_send!(dyn Future<Output = anyhow::Result<Option<Amount>>>)>>;
135
136/// The state of a single module's recovery, as tracked by the client.
137///
138/// [`RecoveryProgress`] can only ever express how far a recovery got, never
139/// that it gave up, which makes a failed recovery indistinguishable from a
140/// merely slow one and blocks every waiter on the outcome forever. Carrying the
141/// failure as a state of the very thing the progress describes makes the
142/// contradictory "done and failed" unrepresentable for a module, so a recovery
143/// that terminally fails resolves the waiters it used to strand. Only a
144/// terminal failure does: a recovery that keeps retrying instead of giving up,
145/// as fetching the federation's history does, resolves nothing, and its waiters
146/// keep waiting.
147///
148/// Only the APIs returning a result observe the failure. The ones exposing the
149/// progress itself, like [`Client::has_pending_recoveries`] and
150/// [`Client::subscribe_to_recovery_progress`], keep reporting a failed recovery
151/// as not done, which it is.
152///
153/// Internal: the failure reaches callers as the error of the waiting API, not
154/// as this type, so it stays crate-private rather than committing an
155/// accessorless public type to the API surface.
156///
157/// The module this describes is the key of the map this is stored in, so it is
158/// deliberately not repeated here: a copy could contradict the key it is
159/// filed under.
160#[derive(Clone, Debug)]
161pub(crate) enum RecoveryStatus {
162    /// The latest progress of a recovery that has not failed.
163    ///
164    /// A successfully completed recovery is represented by a done progress.
165    InProgress(RecoveryProgress),
166    /// The recovery terminally failed at `last_progress`, which is kept so the
167    /// progress-reporting APIs can keep describing the module.
168    Failed {
169        last_progress: RecoveryProgress,
170        error: String,
171    },
172}
173
174impl RecoveryStatus {
175    /// Whether the module's recovery completed successfully.
176    ///
177    /// A failure is terminal but never done: the module recovered nothing
178    /// beyond the progress it got to.
179    pub(crate) fn is_successfully_done(&self) -> bool {
180        match self {
181            Self::InProgress(progress) => progress.is_done(),
182            Self::Failed { .. } => false,
183        }
184    }
185
186    /// The progress view of the status, which is all a progress-reporting API
187    /// can express.
188    pub(crate) fn progress(&self) -> RecoveryProgress {
189        match self {
190            Self::InProgress(progress)
191            | Self::Failed {
192                last_progress: progress,
193                ..
194            } => *progress,
195        }
196    }
197}
198
199/// Main client type
200///
201/// A handle and API to interacting with a single federation. End user
202/// applications that want to support interacting with multiple federations at
203/// the same time, will need to instantiate and manage multiple instances of
204/// this struct.
205///
206/// Under the hood it is starting and managing service tasks, state machines,
207/// database and other resources required.
208///
209/// This type is shared externally and internally, and
210/// [`crate::ClientHandle`] is responsible for external lifecycle management
211/// and resource freeing of the [`Client`].
212pub struct Client {
213    final_client: FinalClientIface,
214    config: tokio::sync::RwLock<ClientConfig>,
215    api_secret: Option<String>,
216    decoders: ModuleDecoderRegistry,
217    connectors: ConnectorRegistry,
218    db: Database,
219    federation_id: FederationId,
220    federation_config_meta: BTreeMap<String, String>,
221    primary_modules: BTreeMap<PrimaryModulePriority, PrimaryModuleCandidates>,
222    pub(crate) modules: ClientModuleRegistry,
223    module_inits: ClientModuleInitRegistry,
224    executor: Executor,
225    pub(crate) api: DynGlobalApi,
226    root_secret: DerivableSecret,
227    operation_log: OperationLog,
228    secp_ctx: Secp256k1<secp256k1::All>,
229    meta_service: Arc<MetaService>,
230
231    task_group: TaskGroup,
232
233    /// Long-lived span attached to every task spawned via [`Client::spawn`] /
234    /// [`Client::spawn_cancellable`], so logs from background tasks carry the
235    /// federation prefix.
236    client_span: Span,
237
238    /// Updates about the recovery status of every recovering module, keyed by
239    /// module instance
240    ///
241    /// Keyed rather than a single slot because a `watch` channel only keeps its
242    /// latest value: if one module's terminal state overwrote another's before
243    /// a waiter observed it, [`Self::wait_for_module_kind_recovery`] could miss
244    /// a failure of its own kind and block forever. Keeping a status per module
245    /// keeps the outcome determinate no matter how many modules fail.
246    ///
247    /// A [`RecoveryStatus::Failed`] is intentionally not persisted: on a
248    /// restart the recovery is simply attempted again, which is the right thing
249    /// to do for a transient cause.
250    client_recovery_status_receiver: watch::Receiver<BTreeMap<ModuleInstanceId, RecoveryStatus>>,
251
252    /// Internal client sender to wake up log ordering task every time a
253    /// (unuordered) log event is added.
254    log_ordering_wakeup_tx: watch::Sender<()>,
255    /// Receiver for events fired every time (ordered) log event is added.
256    log_event_added_rx: watch::Receiver<()>,
257    log_event_added_transient_tx: broadcast::Sender<EventLogEntry>,
258    request_hook: ApiRequestHook,
259    iroh_enable_dht: bool,
260    iroh_enable_next: bool,
261    /// User-provided Bitcoin RPC client for modules to use
262    ///
263    /// Stored here for potential future access; currently passed to modules
264    /// during initialization.
265    #[allow(dead_code)]
266    user_bitcoind_rpc: Option<DynBitcoindRpc>,
267    /// User-provided Bitcoin RPC factory for when ChainId is not available
268    ///
269    /// This is used as a fallback when the federation doesn't support ChainId.
270    /// Modules can call this with a URL from their config to get an RPC client.
271    pub(crate) user_bitcoind_rpc_no_chain_id:
272        Option<fedimint_client_module::module::init::BitcoindRpcNoChainIdFactory>,
273}
274
275#[derive(Debug, Serialize, Deserialize)]
276struct ListOperationsParams {
277    limit: Option<usize>,
278    last_seen: Option<ChronologicalOperationLogKey>,
279}
280
281pub const DEFAULT_EVENT_LOG_PAGE_SIZE: u64 = 100;
282pub const MAX_EVENT_LOG_PAGE_SIZE: u64 = 10_000;
283
284#[derive(Debug, Clone, Serialize, Deserialize)]
285struct GetEventLogRequest {
286    pos: Option<EventLogId>,
287    limit: Option<u64>,
288}
289
290#[derive(Debug, Clone, Serialize, Deserialize)]
291pub struct GetOperationIdRequest {
292    operation_id: OperationId,
293}
294
295#[derive(Debug, Clone, Serialize, Deserialize)]
296pub struct GetBalanceChangesRequest {
297    #[serde(default = "AmountUnit::bitcoin")]
298    unit: AmountUnit,
299}
300
301impl Client {
302    /// Initialize a client builder that can be configured to create a new
303    /// client.
304    pub async fn builder() -> anyhow::Result<ClientBuilder> {
305        Ok(ClientBuilder::new())
306    }
307
308    pub fn api(&self) -> &(dyn IGlobalFederationApi + 'static) {
309        self.api.as_ref()
310    }
311
312    pub fn api_clone(&self) -> DynGlobalApi {
313        self.api.clone()
314    }
315
316    /// Returns a stream that emits the current connection status of all peers
317    /// whenever any peer's status changes. Emits initial state immediately.
318    pub fn connection_status_stream(&self) -> impl Stream<Item = BTreeMap<PeerId, PeerStatus>> {
319        self.api.connection_status_stream()
320    }
321
322    /// Establishes connections to all federation guardians once.
323    ///
324    /// Spawns tasks to connect to each guardian in the federation. Unlike
325    /// [`Self::spawn_federation_reconnect`], this only attempts to establish
326    /// connections once and completes - it does not maintain or reconnect.
327    ///
328    /// Useful for warming up connections before making API calls.
329    pub fn federation_reconnect(&self) {
330        let peers: Vec<PeerId> = self.api.all_peers().iter().copied().collect();
331
332        for peer_id in peers {
333            let api = self.api.clone();
334            self.spawn_cancellable(format!("federation-reconnect-once-{peer_id}"), async move {
335                if let Err(e) = api.get_peer_connection(peer_id).await {
336                    debug!(
337                        target: LOG_CLIENT_NET_API,
338                        %peer_id,
339                        err = %e.fmt_compact(),
340                        "Failed to connect to peer"
341                    );
342                }
343            });
344        }
345    }
346
347    /// Spawns background tasks that proactively maintain connections to all
348    /// federation guardians unconditionally.
349    ///
350    /// For each guardian, a task loops: establishes a connection, waits for it
351    /// to disconnect, then reconnects.
352    ///
353    /// The tasks are cancellable and will be terminated when the client shuts
354    /// down.
355    ///
356    /// By default [`Client`] creates connections on demand only, and share
357    /// them as long as they are alive.
358    ///
359    /// Reconnecting continuously might increase data and battery usage,
360    /// but potentially improve UX, depending on the time it takes to establish
361    /// a new network connection in given network conditions.
362    ///
363    /// Downstream users are encouraged to implement their own version of
364    /// this function, e.g. by reconnecting only when it is anticipated
365    /// that connection might be needed, or alternatively pre-warm
366    /// connections by calling [`Self::federation_reconnect`] when it seems
367    /// worthwhile.
368    pub fn spawn_federation_reconnect(&self) {
369        let peers: Vec<PeerId> = self.api.all_peers().iter().copied().collect();
370
371        for peer_id in peers {
372            let api = self.api.clone();
373            self.spawn_cancellable(format!("federation-reconnect-{peer_id}"), async move {
374                loop {
375                    match api.get_peer_connection(peer_id).await {
376                        Ok(conn) => {
377                            conn.await_disconnection().await;
378                        }
379                        Err(e) => {
380                            // Connection failed, backoff is handled inside
381                            // get_or_create_connection
382                            debug!(
383                                target: LOG_CLIENT_NET_API,
384                                %peer_id,
385                                err = %e.fmt_compact(),
386                                "Failed to connect to peer, will retry"
387                            );
388                        }
389                    }
390                }
391            });
392        }
393    }
394
395    /// Get the [`TaskGroup`] that is tied to Client's lifetime.
396    pub fn task_group(&self) -> &TaskGroup {
397        &self.task_group
398    }
399
400    /// Construct the long-lived span attached to all tasks spawned by this
401    /// client.
402    ///
403    /// `parent: None` keeps the span tree shallow; `runtime::spawn` already
404    /// wraps each task in its own `spawn(task=…)` span, so log events from
405    /// these tasks carry both `task` and `fed_id`.
406    pub(crate) fn make_client_span(federation_id: FederationId) -> Span {
407        tracing::info_span!(
408            target: LOG_CLIENT,
409            parent: None,
410            "client",
411            fed_id = %federation_id.to_prefix(),
412        )
413    }
414
415    /// Spawn a cancellable task on the client's task group, instrumented with
416    /// the client's [`Span`] so all events from the task carry `fed_id`.
417    pub(crate) fn spawn_cancellable<R>(
418        &self,
419        name: impl Into<String>,
420        future: impl Future<Output = R> + MaybeSend + 'static,
421    ) -> oneshot::Receiver<Result<R, ShuttingDownError>>
422    where
423        R: MaybeSend + 'static,
424    {
425        self.task_group
426            .spawn_cancellable_with_span(self.client_span.clone(), name, future)
427    }
428
429    /// Spawn a task on the client's task group, parented to the client's
430    /// [`Span`] so all events from the task carry `fed_id` (including the
431    /// task lifecycle events emitted by [`TaskGroup`] itself).
432    pub(crate) fn spawn<Fut, R>(
433        &self,
434        name: impl Into<String>,
435        f: impl FnOnce(TaskHandle) -> Fut + MaybeSend + 'static,
436    ) -> oneshot::Receiver<R>
437    where
438        Fut: Future<Output = R> + MaybeSend + 'static,
439        R: MaybeSend + 'static,
440    {
441        self.task_group
442            .spawn_with_span(self.client_span.clone(), name, f)
443    }
444
445    /// Returns all registered Prometheus metrics encoded in text format.
446    ///
447    /// This can be used by downstream clients to expose metrics via their own
448    /// HTTP server or print them for debugging purposes.
449    pub fn get_metrics() -> anyhow::Result<String> {
450        fedimint_metrics::get_metrics()
451    }
452
453    /// Useful for our CLI tooling, not meant for external use
454    #[doc(hidden)]
455    pub fn executor(&self) -> &Executor {
456        &self.executor
457    }
458
459    pub async fn get_config_from_db(db: &Database) -> Option<ClientConfig> {
460        let mut dbtx = db.begin_transaction_nc().await;
461        dbtx.get_value(&ClientConfigKey).await
462    }
463
464    pub async fn get_pending_config_from_db(db: &Database) -> Option<ClientConfig> {
465        let mut dbtx = db.begin_transaction_nc().await;
466        dbtx.get_value(&PendingClientConfigKey).await
467    }
468
469    pub async fn get_api_secret_from_db(db: &Database) -> Option<String> {
470        let mut dbtx = db.begin_transaction_nc().await;
471        dbtx.get_value(&ApiSecretKey).await
472    }
473
474    pub async fn store_encodable_client_secret<T: Encodable>(
475        db: &Database,
476        secret: T,
477    ) -> anyhow::Result<()> {
478        let mut dbtx = db.begin_transaction().await;
479
480        // Don't overwrite an existing secret
481        if dbtx.get_value(&EncodedClientSecretKey).await.is_some() {
482            bail!("Encoded client secret already exists, cannot overwrite")
483        }
484
485        let encoded_secret = T::consensus_encode_to_vec(&secret);
486        dbtx.insert_entry(&EncodedClientSecretKey, &encoded_secret)
487            .await;
488        dbtx.commit_tx().await;
489        Ok(())
490    }
491
492    pub async fn load_decodable_client_secret<T: Decodable>(db: &Database) -> anyhow::Result<T> {
493        let Some(secret) = Self::load_decodable_client_secret_opt(db).await? else {
494            bail!("Encoded client secret not present in DB")
495        };
496
497        Ok(secret)
498    }
499    pub async fn load_decodable_client_secret_opt<T: Decodable>(
500        db: &Database,
501    ) -> anyhow::Result<Option<T>> {
502        let mut dbtx = db.begin_transaction_nc().await;
503
504        let client_secret = dbtx.get_value(&EncodedClientSecretKey).await;
505
506        Ok(match client_secret {
507            Some(client_secret) => Some(
508                T::consensus_decode_whole(&client_secret, &ModuleRegistry::default())
509                    .context("Decoding failed")?,
510            ),
511            None => None,
512        })
513    }
514
515    pub async fn load_or_generate_client_secret(db: &Database) -> anyhow::Result<[u8; 64]> {
516        let client_secret = match Self::load_decodable_client_secret::<[u8; 64]>(db).await {
517            Ok(secret) => secret,
518            _ => {
519                let secret = PlainRootSecretStrategy::random(&mut thread_rng());
520                Self::store_encodable_client_secret(db, secret)
521                    .await
522                    .expect("Storing client secret must work");
523                secret
524            }
525        };
526        Ok(client_secret)
527    }
528
529    pub async fn is_initialized(db: &Database) -> bool {
530        let mut dbtx = db.begin_transaction_nc().await;
531        dbtx.raw_get_bytes(&[ClientConfigKey::DB_PREFIX])
532            .await
533            .expect("Unrecoverable error occurred while reading and entry from the database")
534            .is_some()
535    }
536
537    pub fn start_executor(self: &Arc<Self>) {
538        self.client_span.in_scope(|| {
539            debug!(
540                target: LOG_CLIENT,
541                "Starting fedimint client executor",
542            );
543        });
544        self.executor
545            .start_executor(self.context_gen(), self.client_span.clone());
546    }
547
548    pub fn federation_id(&self) -> FederationId {
549        self.federation_id
550    }
551
552    fn context_gen(self: &Arc<Self>) -> ModuleGlobalContextGen {
553        let client_inner = Arc::downgrade(self);
554        Arc::new(move |module_instance, operation| {
555            ModuleGlobalClientContext {
556                client: client_inner
557                    .clone()
558                    .upgrade()
559                    .expect("ModuleGlobalContextGen called after client was dropped"),
560                module_instance_id: module_instance,
561                operation,
562            }
563            .into()
564        })
565    }
566
567    pub async fn config(&self) -> ClientConfig {
568        self.config.read().await.clone()
569    }
570
571    // TODO: change to `-> Option<&str>`
572    pub fn api_secret(&self) -> &Option<String> {
573        &self.api_secret
574    }
575
576    /// Returns the core API version that the federation supports
577    ///
578    /// This reads from the cached version stored during client initialization.
579    /// If no cache is available (e.g., during initial setup), returns a default
580    /// version (0, 0).
581    pub async fn core_api_version(&self) -> ApiVersion {
582        // Try to get from cache. If not available, return a conservative
583        // default. The cache should always be populated after successful client init.
584        self.db
585            .begin_transaction_nc()
586            .await
587            .get_value(&CachedApiVersionSetKey)
588            .await
589            .map(|cached: CachedApiVersionSet| cached.0.core)
590            .unwrap_or(ApiVersion { major: 0, minor: 0 })
591    }
592
593    /// Returns the chain ID (bitcoin block hash at height 1) from the
594    /// federation
595    ///
596    /// This is cached in the database after the first successful fetch.
597    /// The chain ID uniquely identifies which bitcoin network the federation
598    /// operates on (mainnet, testnet, signet, regtest).
599    pub async fn chain_id(&self) -> anyhow::Result<ChainId> {
600        // Check cache first
601        if let Some(chain_id) = self
602            .db
603            .begin_transaction_nc()
604            .await
605            .get_value(&ChainIdKey)
606            .await
607        {
608            return Ok(chain_id);
609        }
610
611        // Fetch from federation with consensus
612        let chain_id = self.api.chain_id().await?;
613
614        // Cache the result
615        let mut dbtx = self.db.begin_transaction().await;
616        dbtx.insert_entry(&ChainIdKey, &chain_id).await;
617        dbtx.commit_tx().await;
618
619        Ok(chain_id)
620    }
621
622    pub fn decoders(&self) -> &ModuleDecoderRegistry {
623        &self.decoders
624    }
625
626    /// Returns a reference to the module, panics if not found
627    fn get_module(&self, instance: ModuleInstanceId) -> &maybe_add_send_sync!(dyn IClientModule) {
628        self.try_get_module(instance)
629            .expect("Module instance not found")
630    }
631
632    fn try_get_module(
633        &self,
634        instance: ModuleInstanceId,
635    ) -> Option<&maybe_add_send_sync!(dyn IClientModule)> {
636        Some(self.modules.get(instance)?.as_ref())
637    }
638
639    pub fn has_module(&self, instance: ModuleInstanceId) -> bool {
640        self.modules.get(instance).is_some()
641    }
642
643    /// Returns the input amount and output amount of a transaction
644    ///
645    /// # Panics
646    /// If any of the input or output versions in the transaction builder are
647    /// unknown by the respective module.
648    fn transaction_builder_get_balance(&self, builder: &TransactionBuilder) -> (Amounts, Amounts) {
649        // FIXME: prevent overflows, currently not suitable for untrusted input
650        let mut in_amounts = Amounts::ZERO;
651        let mut out_amounts = Amounts::ZERO;
652        let mut fee_amounts = Amounts::ZERO;
653
654        for input in builder.inputs() {
655            let module = self.get_module(input.input.module_instance_id());
656
657            let item_fees = module.input_fee(&input.amounts, &input.input).expect(
658                "We only build transactions with input versions that are supported by the module",
659            );
660
661            in_amounts.checked_add_mut(&input.amounts);
662            fee_amounts.checked_add_mut(&item_fees);
663        }
664
665        for output in builder.outputs() {
666            let module = self.get_module(output.output.module_instance_id());
667
668            let item_fees = module.output_fee(&output.amounts, &output.output).expect(
669                "We only build transactions with output versions that are supported by the module",
670            );
671
672            out_amounts.checked_add_mut(&output.amounts);
673            fee_amounts.checked_add_mut(&item_fees);
674        }
675
676        out_amounts.checked_add_mut(&fee_amounts);
677        (in_amounts, out_amounts)
678    }
679
680    pub fn get_internal_payment_markers(&self) -> anyhow::Result<(PublicKey, u64)> {
681        Ok((self.federation_id().to_fake_ln_pub_key(&self.secp_ctx)?, 0))
682    }
683
684    /// Get metadata value from the federation config itself
685    pub fn get_config_meta(&self, key: &str) -> Option<String> {
686        self.federation_config_meta.get(key).cloned()
687    }
688
689    pub(crate) fn root_secret(&self) -> DerivableSecret {
690        self.root_secret.clone()
691    }
692
693    pub async fn add_state_machines(
694        &self,
695        dbtx: &mut DatabaseTransaction<'_>,
696        states: Vec<DynState>,
697    ) -> AddStateMachinesResult {
698        self.executor.add_state_machines_dbtx(dbtx, states).await
699    }
700
701    // TODO: implement as part of [`OperationLog`]
702    pub async fn get_active_operations(&self) -> HashSet<OperationId> {
703        let active_states = self.executor.get_active_states().await;
704        let mut active_operations = HashSet::with_capacity(active_states.len());
705        let mut dbtx = self.db().begin_transaction_nc().await;
706        for (state, _) in active_states {
707            let operation_id = state.operation_id();
708            if dbtx
709                .get_value(&OperationLogKey { operation_id })
710                .await
711                .is_some()
712            {
713                active_operations.insert(operation_id);
714            }
715        }
716        active_operations
717    }
718
719    pub fn operation_log(&self) -> &OperationLog {
720        &self.operation_log
721    }
722
723    /// Get the meta manager to read meta fields.
724    pub fn meta_service(&self) -> &Arc<MetaService> {
725        &self.meta_service
726    }
727
728    /// Get the meta manager to read meta fields.
729    pub async fn get_meta_expiration_timestamp(&self) -> Option<SystemTime> {
730        let meta_service = self.meta_service();
731        let ts = meta_service
732            .get_field::<u64>(self.db(), "federation_expiry_timestamp")
733            .await
734            .and_then(|v| v.value)?;
735        Some(UNIX_EPOCH + Duration::from_secs(ts))
736    }
737
738    /// Adds funding to a transaction or removes over-funding via change.
739    async fn finalize_transaction(
740        &self,
741        dbtx: &mut DatabaseTransaction<'_>,
742        operation_id: OperationId,
743        mut partial_transaction: TransactionBuilder,
744    ) -> anyhow::Result<FinalizedTransaction> {
745        let (in_amounts, out_amounts) = self.transaction_builder_get_balance(&partial_transaction);
746
747        let mut added_inputs_bundles = vec![];
748        let mut added_outputs_bundles = vec![];
749
750        // The way currently things are implemented is OK for modules which can
751        // collect a fee relative to one being used, but will break down in any
752        // fancy scenarios. Future TODOs:
753        //
754        // * create_final_inputs_and_outputs needs to get broken down, so we can use
755        //   primary modules using priorities (possibly separate prios for inputs and
756        //   outputs to be able to drain modules, etc.); we need the split "check if
757        //   possible" and "take" steps,
758        // * extra inputs and outputs adding fees needs to be taken into account,
759        //   possibly with some looping
760        for unit in in_amounts.units().union(&out_amounts.units()) {
761            let input_amount = in_amounts.get(unit).copied().unwrap_or_default();
762            let output_amount = out_amounts.get(unit).copied().unwrap_or_default();
763            if input_amount == output_amount {
764                continue;
765            }
766
767            let Some((module_id, module)) = self.primary_module_for_unit(*unit) else {
768                bail!("No module to balance a partial transaction (affected unit: {unit:?}");
769            };
770
771            let (added_input_bundle, added_output_bundle) = module
772                .create_final_inputs_and_outputs(
773                    module_id,
774                    dbtx,
775                    operation_id,
776                    *unit,
777                    input_amount,
778                    output_amount,
779                )
780                .await?;
781
782            added_inputs_bundles.push(added_input_bundle);
783            added_outputs_bundles.push(added_output_bundle);
784        }
785
786        // This is the range of  outputs that will be added to the transaction
787        // in order to balance it. Notice that it may stay empty in case the transaction
788        // is already balanced.
789        let change_range = Range {
790            start: partial_transaction.outputs().count() as u64,
791            end: (partial_transaction.outputs().count() as u64
792                + added_outputs_bundles
793                    .iter()
794                    .map(|output| output.outputs().len() as u64)
795                    .sum::<u64>()),
796        };
797
798        for added_inputs in added_inputs_bundles {
799            partial_transaction = partial_transaction.with_inputs(added_inputs);
800        }
801
802        for added_outputs in added_outputs_bundles {
803            partial_transaction = partial_transaction.with_outputs(added_outputs);
804        }
805
806        let (input_amounts, output_amounts) =
807            self.transaction_builder_get_balance(&partial_transaction);
808
809        for (unit, output_amount) in output_amounts {
810            let input_amount = input_amounts.get(&unit).copied().unwrap_or_default();
811
812            assert!(input_amount >= output_amount, "Transaction is underfunded");
813        }
814
815        // Compute fees as the difference between total input and output amounts.
816        // This captures both explicit federation fees and any overpayment due to
817        // denomination constraints.
818        let fees = {
819            let mut input_total = Amounts::ZERO;
820            for input in partial_transaction.inputs() {
821                input_total
822                    .checked_add_mut(&input.amounts)
823                    .expect("Own transaction amounts don't overflow");
824            }
825            let mut output_total = Amounts::ZERO;
826            for output in partial_transaction.outputs() {
827                output_total
828                    .checked_add_mut(&output.amounts)
829                    .expect("Own transaction amounts don't overflow");
830            }
831            input_total
832                .checked_sub(&output_total)
833                .expect("Inputs >= outputs for own transactions")
834        };
835
836        let (transaction, states) = partial_transaction.build(&self.secp_ctx, thread_rng());
837
838        Ok(FinalizedTransaction {
839            transaction,
840            states,
841            change_range,
842            fees,
843        })
844    }
845
846    /// Computes the fee that finalizing and submitting a transaction with the
847    /// explicit items described by `request` would incur, without submitting
848    /// anything.
849    ///
850    /// This is the read-only twin of `Self::finalize_transaction`: it runs
851    /// the exact same primary-module balancing
852    /// (`create_final_inputs_and_outputs`, including funding selection /
853    /// consolidation / rebalancing) against a non-committable transaction
854    /// that is dropped rather than committed, so the client's funds are
855    /// read but left untouched. The quote is point-in-time: it depends on
856    /// the current inventory and can move as funds change.
857    ///
858    /// The mechanism is module-agnostic — [`FeeQuoteRequest`] only summarizes
859    /// the explicit side (gross amounts + federation fees) of whichever module
860    /// is quoting (mint, lightning, wallet, …), and the change is generated by
861    /// the primary module of each affected unit. The request and the resulting
862    /// breakdown are multi-unit, so an operation spanning several units (e.g.
863    /// Bitcoin plus a custom currency) is quoted in one call, mirroring how
864    /// `finalize_transaction` balances each unit independently.
865    pub async fn fee_quote(
866        &self,
867        operation_id: OperationId,
868        request: FeeQuoteRequest,
869    ) -> anyhow::Result<FeeQuote> {
870        let FeeQuoteRequest {
871            input_amount,
872            output_amount,
873            input_fee,
874            output_fee,
875        } = request;
876
877        // Start the totals from the explicit side; the change the primary
878        // modules add below is a disjoint set of items, accumulated into these
879        // same totals.
880        let mut gross_input = input_amount.clone();
881        let mut gross_output = output_amount.clone();
882        let mut input_fees = input_fee.clone();
883        let mut output_fees = output_fee.clone();
884
885        // The primary modules balance the transaction by minting change (and
886        // possibly pulling in extra inputs). The amounts they must balance
887        // mirror `finalize_transaction`: per unit the input side is the gross
888        // input value, the output side carries the gross output value plus
889        // every explicit fee (fees consume funding on the output side).
890        let balance_input = input_amount;
891        let balance_output = output_amount
892            .checked_add(&input_fee)
893            .and_then(|amounts| amounts.checked_add(&output_fee))
894            .expect("explicit amounts and fees cannot overflow an Amounts");
895
896        // Non-committable: the balancing writes (e.g. removing consolidated
897        // notes) are discarded on drop, so this is a pure dry-run over the real
898        // inventory.
899        let mut dbtx = self.db.begin_transaction_nc().await;
900
901        // Balance each affected unit independently via its own primary module,
902        // exactly as `finalize_transaction` does over the real transaction.
903        for unit in balance_input.units().union(&balance_output.units()) {
904            let balance_input_amount = balance_input.get(unit).copied().unwrap_or_default();
905            let balance_output_amount = balance_output.get(unit).copied().unwrap_or_default();
906            if balance_input_amount == balance_output_amount {
907                continue;
908            }
909
910            let Some((module_id, module)) = self.primary_module_for_unit(*unit) else {
911                bail!("No module to balance a partial transaction (affected unit: {unit:?}");
912            };
913
914            let (change_input, change_output) = module
915                .create_final_inputs_and_outputs(
916                    module_id,
917                    &mut dbtx.to_ref_nc(),
918                    operation_id,
919                    *unit,
920                    balance_input_amount,
921                    balance_output_amount,
922                )
923                .await?;
924
925            // Fold the change into the totals. These are a disjoint set of items
926            // from the explicit ones (the primary module only sees the scalar
927            // amounts to balance, never the explicit items), so this is not
928            // double-counting. Iterate the bundles the way `finalize_transaction`
929            // would, looking each fee up via the module that owns the item.
930            for input in change_input.inputs() {
931                let module = self.get_module(input.input.module_instance_id());
932                let fee = module
933                    .input_fee(&input.amounts, &input.input)
934                    .expect("Primary module must know its own change input fees");
935                gross_input.checked_add_mut(&input.amounts);
936                input_fees.checked_add_mut(&fee);
937            }
938
939            for output in change_output.outputs() {
940                let module = self.get_module(output.output.module_instance_id());
941                let fee = module
942                    .output_fee(&output.amounts, &output.output)
943                    .expect("Primary module must know its own change output fees");
944                gross_output.checked_add_mut(&output.amounts);
945                output_fees.checked_add_mut(&fee);
946            }
947        }
948
949        // Mark the dropped dbtx as intentionally uncommitted so the commit
950        // tracker doesn't warn.
951        dbtx.ignore_uncommitted();
952
953        // Per unit: net wallet gain = produced outputs − consumed inputs. The
954        // total fee is everything the gross input value did not become a net
955        // gain; whatever is left after the input/output fees is
956        // sub-denomination dust.
957        let mut dust = Amounts::ZERO;
958        for unit in gross_input.units().union(&gross_output.units()) {
959            let total = gross_input
960                .get(unit)
961                .copied()
962                .unwrap_or_default()
963                .saturating_sub(gross_output.get(unit).copied().unwrap_or_default());
964            let fees = input_fees.get(unit).copied().unwrap_or_default()
965                + output_fees.get(unit).copied().unwrap_or_default();
966            dust = dust
967                .checked_add_unit(total.saturating_sub(fees), *unit)
968                .expect("dust cannot overflow an Amounts");
969        }
970
971        Ok(FeeQuote {
972            input: input_fees,
973            output: output_fees,
974            dust,
975        })
976    }
977
978    /// Add funding and/or change to the transaction builder as needed, finalize
979    /// the transaction and submit it to the federation.
980    ///
981    /// ## Cancel safety
982    /// This method is cancel safe. It performs all its work (funding the
983    /// inputs, registering the state machines, and creating the operation log
984    /// entry) inside a single `autocommit` transaction, so dropping the future
985    /// either commits that unit in full or leaves the database untouched.
986    /// Submission to the federation is not awaited here, it is carried out by
987    /// the transaction-submission state machine in the executor, so cancelling
988    /// does not abort an already-committed submission.
989    ///
990    /// ## Errors
991    /// The function will return an error if the operation with given ID already
992    /// exists.
993    ///
994    /// ## Panics
995    /// The function will panic if the database transaction collides with
996    /// other and fails with others too often, this should not happen except for
997    /// excessively concurrent scenarios.
998    pub async fn finalize_and_submit_transaction<F, M>(
999        &self,
1000        operation_id: OperationId,
1001        operation_type: &str,
1002        operation_meta_gen: F,
1003        tx_builder: TransactionBuilder,
1004    ) -> anyhow::Result<OutPointRange>
1005    where
1006        F: Fn(OutPointRange) -> M + Clone + MaybeSend + MaybeSync,
1007        M: serde::Serialize + MaybeSend,
1008    {
1009        let operation_type = operation_type.to_owned();
1010
1011        let autocommit_res = self
1012            .db
1013            .autocommit(
1014                |dbtx, _| {
1015                    let operation_type = operation_type.clone();
1016                    let tx_builder = tx_builder.clone();
1017                    let operation_meta_gen = operation_meta_gen.clone();
1018                    Box::pin(async move {
1019                        self.finalize_and_submit_transaction_dbtx(
1020                            dbtx,
1021                            operation_id,
1022                            &operation_type,
1023                            operation_meta_gen,
1024                            tx_builder,
1025                        )
1026                        .await
1027                    })
1028                },
1029                Some(100), // TODO: handle what happens after 100 retries
1030            )
1031            .await;
1032
1033        match autocommit_res {
1034            Ok(txid) => Ok(txid),
1035            Err(AutocommitError::ClosureError { error, .. }) => Err(error),
1036            Err(AutocommitError::CommitFailed {
1037                attempts,
1038                last_error,
1039            }) => panic!(
1040                "Failed to commit tx submission dbtx after {attempts} attempts: {last_error}"
1041            ),
1042        }
1043    }
1044
1045    /// See [`Self::finalize_and_submit_transaction`], just inside a database
1046    /// transaction.
1047    ///
1048    /// ## Cancel safety
1049    /// Unlike [`Self::finalize_and_submit_transaction`], this does not own the
1050    /// transaction: all writes go to the passed `dbtx`, so its cancel safety
1051    /// is that of the caller's transaction. The caller is responsible for
1052    /// committing (or rolling back) `dbtx` atomically.
1053    pub async fn finalize_and_submit_transaction_dbtx<F, M>(
1054        &self,
1055        dbtx: &mut DatabaseTransaction<'_>,
1056        operation_id: OperationId,
1057        operation_type: &str,
1058        operation_meta_gen: F,
1059        tx_builder: TransactionBuilder,
1060    ) -> anyhow::Result<OutPointRange>
1061    where
1062        F: FnOnce(OutPointRange) -> M + MaybeSend,
1063        M: serde::Serialize + MaybeSend,
1064    {
1065        if Client::operation_exists_dbtx(dbtx, operation_id).await {
1066            bail!("There already exists an operation with id {operation_id:?}")
1067        }
1068
1069        let out_point_range = self
1070            .finalize_and_submit_transaction_inner(dbtx, operation_id, tx_builder)
1071            .await?;
1072
1073        self.operation_log()
1074            .add_operation_log_entry_dbtx(
1075                dbtx,
1076                operation_id,
1077                operation_type,
1078                operation_meta_gen(out_point_range),
1079            )
1080            .await;
1081
1082        Ok(out_point_range)
1083    }
1084
1085    async fn finalize_and_submit_transaction_inner(
1086        &self,
1087        dbtx: &mut DatabaseTransaction<'_>,
1088        operation_id: OperationId,
1089        tx_builder: TransactionBuilder,
1090    ) -> anyhow::Result<OutPointRange> {
1091        let FinalizedTransaction {
1092            transaction,
1093            mut states,
1094            change_range,
1095            fees,
1096        } = self
1097            .finalize_transaction(&mut dbtx.to_ref_nc(), operation_id, tx_builder)
1098            .await?;
1099
1100        if transaction.consensus_encode_to_vec().len() > Transaction::MAX_TX_SIZE {
1101            let inputs = transaction
1102                .inputs
1103                .iter()
1104                .map(DynInput::module_instance_id)
1105                .collect::<Vec<_>>();
1106            let outputs = transaction
1107                .outputs
1108                .iter()
1109                .map(DynOutput::module_instance_id)
1110                .collect::<Vec<_>>();
1111            warn!(
1112                target: LOG_CLIENT_NET_API,
1113                size=%transaction.consensus_encode_to_vec().len(),
1114                ?inputs,
1115                ?outputs,
1116                "Transaction too large",
1117            );
1118            debug!(target: LOG_CLIENT_NET_API, ?transaction, "transaction details");
1119            bail!(
1120                "The generated transaction would be rejected by the federation for being too large."
1121            );
1122        }
1123
1124        let txid = transaction.tx_hash();
1125
1126        debug!(
1127            target: LOG_CLIENT_NET_API,
1128            %txid,
1129            operation_id = %operation_id.fmt_short(),
1130            ?transaction,
1131            "Finalized and submitting transaction",
1132        );
1133
1134        let tx_submission_sm = DynState::from_typed(
1135            TRANSACTION_SUBMISSION_MODULE_INSTANCE,
1136            TxSubmissionStatesSM {
1137                operation_id,
1138                state: TxSubmissionStates::Created(transaction),
1139            },
1140        );
1141        states.push(tx_submission_sm);
1142
1143        self.executor.add_state_machines_dbtx(dbtx, states).await?;
1144
1145        dbtx.insert_new_entry(&TransactionFeesKey(txid), &fees)
1146            .await;
1147
1148        self.log_event_dbtx(dbtx, None, TxCreatedEvent { txid, operation_id })
1149            .await;
1150
1151        Ok(OutPointRange::new(txid, IdxRange::from(change_range)))
1152    }
1153
1154    async fn transaction_update_stream(
1155        &self,
1156        operation_id: OperationId,
1157    ) -> BoxStream<'static, TxSubmissionStatesSM> {
1158        self.executor
1159            .notifier()
1160            .module_notifier::<TxSubmissionStatesSM>(
1161                TRANSACTION_SUBMISSION_MODULE_INSTANCE,
1162                self.final_client.clone(),
1163            )
1164            .subscribe(operation_id)
1165            .await
1166    }
1167
1168    pub async fn operation_exists(&self, operation_id: OperationId) -> bool {
1169        let mut dbtx = self.db().begin_transaction_nc().await;
1170
1171        Client::operation_exists_dbtx(&mut dbtx, operation_id).await
1172    }
1173
1174    pub async fn operation_exists_dbtx(
1175        dbtx: &mut DatabaseTransaction<'_>,
1176        operation_id: OperationId,
1177    ) -> bool {
1178        let active_state_exists = dbtx
1179            .find_by_prefix(&ActiveOperationStateKeyPrefix { operation_id })
1180            .await
1181            .next()
1182            .await
1183            .is_some();
1184
1185        let inactive_state_exists = dbtx
1186            .find_by_prefix(&InactiveOperationStateKeyPrefix { operation_id })
1187            .await
1188            .next()
1189            .await
1190            .is_some();
1191
1192        active_state_exists || inactive_state_exists
1193    }
1194
1195    pub async fn has_active_states(&self, operation_id: OperationId) -> bool {
1196        self.db
1197            .begin_transaction_nc()
1198            .await
1199            .find_by_prefix(&ActiveOperationStateKeyPrefix { operation_id })
1200            .await
1201            .next()
1202            .await
1203            .is_some()
1204    }
1205
1206    /// Calculates the federation fees paid in the course of the operation.
1207    ///
1208    /// Federation fees are fees paid to the federation (e.g. for ecash) and do
1209    /// not include fees paid to service providers like the Lightning gateway.
1210    /// These still have to be separately reported by the module.
1211    ///
1212    /// Fees are calculated by subtracting the total output amount from the
1213    /// total input amount, any difference is either due to direct fees or
1214    /// overpayment to avoid generating uneconomic outputs.
1215    ///
1216    /// The returned amount may still increase while the operation is active;
1217    /// use [`Client::has_active_states`] to check whether it is final.
1218    ///
1219    /// Returns `Ok(None)` if any transaction's fee data is missing (e.g. for
1220    /// operations created before this feature was enabled).
1221    ///
1222    /// # Errors
1223    /// Returns an error if the operation does not exist.
1224    pub async fn get_operation_fees(
1225        &self,
1226        operation_id: OperationId,
1227    ) -> anyhow::Result<Option<Amounts>> {
1228        if !self.operation_exists(operation_id).await {
1229            bail!("Operation does not exist");
1230        }
1231
1232        let (active_states, inactive_states) =
1233            self.executor().get_operation_states(operation_id).await;
1234
1235        let states = active_states
1236            .into_iter()
1237            .map(|(state, _)| state)
1238            .chain(inactive_states.into_iter().map(|(state, _)| state));
1239
1240        let accepted_transactions = states
1241            .filter_map(|state| {
1242                let tx_state = state.as_any().downcast_ref::<TxSubmissionStatesSM>()?;
1243
1244                match &tx_state.state {
1245                    TxSubmissionStates::Accepted(transaction_id) => Some(*transaction_id),
1246                    _ => None,
1247                }
1248            })
1249            .collect::<HashSet<_>>();
1250
1251        // For each accepted transaction, look up its stored fees
1252        let mut dbtx = self.db.begin_transaction_nc().await;
1253        let mut total_fees = Amounts::ZERO;
1254        for txid in &accepted_transactions {
1255            let Some(fees) = dbtx.get_value(&TransactionFeesKey(*txid)).await else {
1256                return Ok(None);
1257            };
1258            total_fees = total_fees
1259                .checked_add(&fees)
1260                .expect("Fee amounts don't overflow in practice");
1261        }
1262
1263        Ok(Some(total_fees))
1264    }
1265
1266    /// Waits for an output from the primary module to reach its final
1267    /// state.
1268    pub async fn await_primary_bitcoin_module_output(
1269        &self,
1270        operation_id: OperationId,
1271        out_point: OutPoint,
1272    ) -> anyhow::Result<()> {
1273        self.primary_module_for_unit(AmountUnit::BITCOIN)
1274            .ok_or_else(|| anyhow!("No primary module available"))?
1275            .1
1276            .await_primary_module_output(operation_id, out_point)
1277            .await
1278    }
1279
1280    /// Returns a reference to a typed module client instance by kind
1281    pub fn get_first_module<M: ClientModule>(
1282        &'_ self,
1283    ) -> anyhow::Result<ClientModuleInstance<'_, M>> {
1284        let module_kind = M::kind();
1285        let id = self
1286            .get_first_instance(&module_kind)
1287            .ok_or_else(|| format_err!("No modules found of kind {module_kind}"))?;
1288        let module: &M = self
1289            .try_get_module(id)
1290            .ok_or_else(|| format_err!("Unknown module instance {id}"))?
1291            .as_any()
1292            .downcast_ref::<M>()
1293            .ok_or_else(|| format_err!("Module is not of type {}", std::any::type_name::<M>()))?;
1294        let (db, _) = self.db().with_prefix_module_id(id);
1295        Ok(ClientModuleInstance {
1296            id,
1297            db,
1298            api: self.api().with_module(id),
1299            module,
1300        })
1301    }
1302
1303    /// Returns an owned `Arc` to a typed module client instance by kind.
1304    ///
1305    /// Unlike [`Self::get_first_module`], this hands out a cloned `Arc` so the
1306    /// caller can hold the module independently of the `Client`'s lifetime.
1307    #[cfg(not(target_family = "wasm"))]
1308    pub fn get_first_module_arc<M: ClientModule>(&self) -> anyhow::Result<Arc<M>> {
1309        let module_kind = M::kind();
1310        let id = self
1311            .get_first_instance(&module_kind)
1312            .ok_or_else(|| format_err!("No modules found of kind {module_kind}"))?;
1313        let dyn_module = self
1314            .modules
1315            .get(id)
1316            .ok_or_else(|| format_err!("Unknown module instance {id}"))?;
1317        dyn_module
1318            .as_any_arc()
1319            .downcast::<M>()
1320            .map_err(|_| format_err!("Module is not of type {}", std::any::type_name::<M>()))
1321    }
1322
1323    pub fn get_module_client_dyn(
1324        &self,
1325        instance_id: ModuleInstanceId,
1326    ) -> anyhow::Result<&maybe_add_send_sync!(dyn IClientModule)> {
1327        self.try_get_module(instance_id)
1328            .ok_or(anyhow!("Unknown module instance {}", instance_id))
1329    }
1330
1331    pub fn db(&self) -> &Database {
1332        &self.db
1333    }
1334
1335    pub fn endpoints(&self) -> &ConnectorRegistry {
1336        &self.connectors
1337    }
1338
1339    /// Returns a stream of transaction updates for the given operation id that
1340    /// can later be used to watch for a specific transaction being accepted.
1341    pub async fn transaction_updates(&self, operation_id: OperationId) -> TransactionUpdates {
1342        TransactionUpdates {
1343            update_stream: self.transaction_update_stream(operation_id).await,
1344        }
1345    }
1346
1347    /// Returns the instance id of the first module of the given kind.
1348    pub fn get_first_instance(&self, module_kind: &ModuleKind) -> Option<ModuleInstanceId> {
1349        self.modules
1350            .iter_modules()
1351            .find(|(_, kind, _module)| *kind == module_kind)
1352            .map(|(instance_id, _, _)| instance_id)
1353    }
1354
1355    /// Returns the data from which the client's root secret is derived (e.g.
1356    /// BIP39 seed phrase struct).
1357    pub async fn root_secret_encoding<T: Decodable>(&self) -> anyhow::Result<T> {
1358        get_decoded_client_secret::<T>(self.db()).await
1359    }
1360
1361    /// Waits for outputs from the primary module to reach its final
1362    /// state.
1363    pub async fn await_primary_bitcoin_module_outputs(
1364        &self,
1365        operation_id: OperationId,
1366        outputs: Vec<OutPoint>,
1367    ) -> anyhow::Result<()> {
1368        for out_point in outputs {
1369            self.await_primary_bitcoin_module_output(operation_id, out_point)
1370                .await?;
1371        }
1372
1373        Ok(())
1374    }
1375
1376    /// Returns the config of the client in JSON format.
1377    ///
1378    /// Compared to the consensus module format where module configs are binary
1379    /// encoded this format cannot be cryptographically verified but is easier
1380    /// to consume and to some degree human-readable.
1381    pub async fn get_config_json(&self) -> JsonClientConfig {
1382        self.config().await.to_json()
1383    }
1384
1385    // Ideally this would not be in the API, but there's a lot of places where this
1386    // makes it easier.
1387    #[doc(hidden)]
1388    /// Like [`Self::get_balance`] but returns an error if primary module is not
1389    /// available
1390    pub async fn get_balance_for_btc(&self) -> anyhow::Result<Amount> {
1391        self.get_balance_for_unit(AmountUnit::BITCOIN).await
1392    }
1393
1394    pub async fn get_balance_for_unit(&self, unit: AmountUnit) -> anyhow::Result<Amount> {
1395        let (id, module) = self
1396            .primary_module_for_unit(unit)
1397            .ok_or_else(|| anyhow!("Primary module not available"))?;
1398        Ok(module
1399            .get_balance(id, &mut self.db().begin_transaction_nc().await, unit)
1400            .await)
1401    }
1402
1403    /// Returns a stream that yields the current client balance every time it
1404    /// changes.
1405    pub async fn subscribe_balance_changes(&self, unit: AmountUnit) -> BoxStream<'static, Amount> {
1406        let primary_module_things =
1407            if let Some((primary_module_id, primary_module)) = self.primary_module_for_unit(unit) {
1408                let balance_changes = primary_module.subscribe_balance_changes().await;
1409                let initial_balance = self
1410                    .get_balance_for_unit(unit)
1411                    .await
1412                    .expect("Primary is present");
1413
1414                Some((
1415                    primary_module_id,
1416                    primary_module.clone(),
1417                    balance_changes,
1418                    initial_balance,
1419                ))
1420            } else {
1421                None
1422            };
1423        let db = self.db().clone();
1424
1425        Box::pin(async_stream::stream! {
1426            let Some((primary_module_id, primary_module, mut balance_changes, initial_balance)) = primary_module_things else {
1427                // If there is no primary module, there will not be one until client is
1428                // restarted
1429                pending().await
1430            };
1431
1432
1433            yield initial_balance;
1434            let mut prev_balance = initial_balance;
1435            while let Some(()) = balance_changes.next().await {
1436                let mut dbtx = db.begin_transaction_nc().await;
1437                let balance = primary_module
1438                     .get_balance(primary_module_id, &mut dbtx, unit)
1439                    .await;
1440
1441                // Deduplicate in case modules cannot always tell if the balance actually changed
1442                if balance != prev_balance {
1443                    prev_balance = balance;
1444                    yield balance;
1445                }
1446            }
1447        })
1448    }
1449
1450    /// Make a single API version request to a peer after a delay.
1451    ///
1452    /// The delay is here to unify the type of a future both for initial request
1453    /// and possible retries.
1454    async fn make_api_version_request(
1455        delay: Duration,
1456        peer_id: PeerId,
1457        api: &DynGlobalApi,
1458    ) -> (
1459        PeerId,
1460        Result<SupportedApiVersionsSummary, fedimint_connectors::error::ServerError>,
1461    ) {
1462        runtime::sleep(delay).await;
1463        (
1464            peer_id,
1465            api.request_single_peer::<SupportedApiVersionsSummary>(
1466                VERSION_ENDPOINT.to_owned(),
1467                ApiRequestErased::default(),
1468                peer_id,
1469            )
1470            .await,
1471        )
1472    }
1473
1474    /// Create a backoff strategy for API version requests.
1475    ///
1476    /// Keep trying, initially somewhat aggressively, but after a while retry
1477    /// very slowly, because chances for response are getting lower and
1478    /// lower.
1479    fn create_api_version_backoff() -> impl Iterator<Item = Duration> {
1480        custom_backoff(Duration::from_millis(200), Duration::from_secs(600), None)
1481    }
1482
1483    /// Query the federation for API version support and then calculate
1484    /// the best API version to use (supported by most guardians).
1485    pub async fn fetch_common_api_versions_from_all_peers(
1486        num_peers: NumPeers,
1487        api: DynGlobalApi,
1488        db: Database,
1489        num_responses_sender: watch::Sender<usize>,
1490    ) {
1491        let mut backoff = Self::create_api_version_backoff();
1492
1493        // NOTE: `FuturesUnordered` is a footgun, but since we only poll it for result
1494        // and make a single async db write operation, it should be OK.
1495        let mut requests = FuturesUnordered::new();
1496
1497        for peer_id in num_peers.peer_ids() {
1498            requests.push(Self::make_api_version_request(
1499                Duration::ZERO,
1500                peer_id,
1501                &api,
1502            ));
1503        }
1504
1505        let mut num_responses = 0;
1506
1507        while let Some((peer_id, response)) = requests.next().await {
1508            let retry = match response {
1509                Err(err) => {
1510                    let has_previous_response = db
1511                        .begin_transaction_nc()
1512                        .await
1513                        .get_value(&PeerLastApiVersionsSummaryKey(peer_id))
1514                        .await
1515                        .is_some();
1516                    debug!(
1517                        target: LOG_CLIENT,
1518                        %peer_id,
1519                        err = %err.fmt_compact(),
1520                        %has_previous_response,
1521                        "Failed to refresh API versions of a peer"
1522                    );
1523
1524                    !has_previous_response
1525                }
1526                Ok(o) => {
1527                    // Save the response to the database right away, just to
1528                    // not lose it
1529                    let mut dbtx = db.begin_transaction().await;
1530                    dbtx.insert_entry(
1531                        &PeerLastApiVersionsSummaryKey(peer_id),
1532                        &PeerLastApiVersionsSummary(o),
1533                    )
1534                    .await;
1535                    dbtx.commit_tx().await;
1536                    false
1537                }
1538            };
1539
1540            if retry {
1541                requests.push(Self::make_api_version_request(
1542                    backoff.next().expect("Keeps retrying"),
1543                    peer_id,
1544                    &api,
1545                ));
1546            } else {
1547                num_responses += 1;
1548                num_responses_sender.send_replace(num_responses);
1549            }
1550        }
1551    }
1552
1553    /// Fetch API versions from peers, retrying until we get threshold number of
1554    /// successful responses. Returns the successful responses collected
1555    /// from at least `num_peers.threshold()` peers.
1556    pub async fn fetch_peers_api_versions_from_threshold_of_peers(
1557        num_peers: NumPeers,
1558        api: DynGlobalApi,
1559    ) -> BTreeMap<PeerId, SupportedApiVersionsSummary> {
1560        let mut backoff = Self::create_api_version_backoff();
1561
1562        // NOTE: `FuturesUnordered` is a footgun, but since we only poll it for result
1563        // and collect responses, it should be OK.
1564        let mut requests = FuturesUnordered::new();
1565
1566        for peer_id in num_peers.peer_ids() {
1567            requests.push(Self::make_api_version_request(
1568                Duration::ZERO,
1569                peer_id,
1570                &api,
1571            ));
1572        }
1573
1574        let mut successful_responses = BTreeMap::new();
1575
1576        while successful_responses.len() < num_peers.threshold()
1577            && let Some((peer_id, response)) = requests.next().await
1578        {
1579            let retry = match response {
1580                Err(err) => {
1581                    debug!(
1582                        target: LOG_CLIENT,
1583                        %peer_id,
1584                        err = %err.fmt_compact(),
1585                        "Failed to fetch API versions from peer"
1586                    );
1587                    true
1588                }
1589                Ok(response) => {
1590                    successful_responses.insert(peer_id, response);
1591                    false
1592                }
1593            };
1594
1595            if retry {
1596                requests.push(Self::make_api_version_request(
1597                    backoff.next().expect("Keeps retrying"),
1598                    peer_id,
1599                    &api,
1600                ));
1601            }
1602        }
1603
1604        successful_responses
1605    }
1606
1607    /// Fetch API versions from peers and discover common API versions to use.
1608    pub async fn fetch_common_api_versions(
1609        config: &ClientConfig,
1610        api: &DynGlobalApi,
1611    ) -> anyhow::Result<BTreeMap<PeerId, SupportedApiVersionsSummary>> {
1612        debug!(
1613            target: LOG_CLIENT,
1614            "Fetching common api versions"
1615        );
1616
1617        let num_peers = NumPeers::from(config.global.api_endpoints.len());
1618
1619        let peer_api_version_sets =
1620            Self::fetch_peers_api_versions_from_threshold_of_peers(num_peers, api.clone()).await;
1621
1622        Ok(peer_api_version_sets)
1623    }
1624
1625    /// Write API version set to database cache.
1626    /// Used when we have a pre-calculated API version set that should be stored
1627    /// for later use.
1628    pub async fn write_api_version_cache(
1629        dbtx: &mut DatabaseTransaction<'_>,
1630        api_version_set: ApiVersionSet,
1631    ) {
1632        debug!(
1633            target: LOG_CLIENT,
1634            value = ?api_version_set,
1635            "Writing API version set to cache"
1636        );
1637
1638        dbtx.insert_entry(
1639            &CachedApiVersionSetKey,
1640            &CachedApiVersionSet(api_version_set),
1641        )
1642        .await;
1643    }
1644
1645    /// Store prefetched peer API version responses and calculate/store common
1646    /// API version set. This processes the individual peer responses by
1647    /// storing them in the database and calculating the common API version
1648    /// set for caching.
1649    pub async fn store_prefetched_api_versions(
1650        db: &Database,
1651        config: &ClientConfig,
1652        client_module_init: &ClientModuleInitRegistry,
1653        peer_api_versions: &BTreeMap<PeerId, SupportedApiVersionsSummary>,
1654    ) {
1655        debug!(
1656            target: LOG_CLIENT,
1657            "Storing {} prefetched peer API version responses and calculating common version set",
1658            peer_api_versions.len()
1659        );
1660
1661        let mut dbtx = db.begin_transaction().await;
1662        // Calculate common API version set from individual responses
1663        let client_supported_versions =
1664            Self::supported_api_versions_summary_static(config, client_module_init);
1665        match fedimint_client_module::api_version_discovery::discover_common_api_versions_set(
1666            &client_supported_versions,
1667            peer_api_versions,
1668        ) {
1669            Ok(common_api_versions) => {
1670                // Write the calculated common API version set to database cache
1671                Self::write_api_version_cache(&mut dbtx.to_ref_nc(), common_api_versions).await;
1672                debug!(target: LOG_CLIENT, "Calculated and stored common API version set");
1673            }
1674            Err(err) => {
1675                debug!(target: LOG_CLIENT, err = %err.fmt_compact_anyhow(), "Failed to calculate common API versions from prefetched data");
1676            }
1677        }
1678
1679        // Store individual peer responses to database
1680        for (peer_id, peer_api_versions) in peer_api_versions {
1681            dbtx.insert_entry(
1682                &PeerLastApiVersionsSummaryKey(*peer_id),
1683                &PeerLastApiVersionsSummary(peer_api_versions.clone()),
1684            )
1685            .await;
1686        }
1687        dbtx.commit_tx().await;
1688        debug!(target: LOG_CLIENT, "Stored individual peer API version responses");
1689    }
1690
1691    /// [`SupportedApiVersionsSummary`] that the client and its modules support
1692    pub fn supported_api_versions_summary_static(
1693        config: &ClientConfig,
1694        client_module_init: &ClientModuleInitRegistry,
1695    ) -> SupportedApiVersionsSummary {
1696        SupportedApiVersionsSummary {
1697            core: SupportedCoreApiVersions {
1698                core_consensus: config.global.consensus_version,
1699                api: MultiApiVersion::try_from_iter(SUPPORTED_CORE_API_VERSIONS.to_owned())
1700                    .expect("must not have conflicting versions"),
1701            },
1702            modules: config
1703                .modules
1704                .iter()
1705                .filter_map(|(&module_instance_id, module_config)| {
1706                    client_module_init
1707                        .get(module_config.kind())
1708                        .map(|module_init| {
1709                            (
1710                                module_instance_id,
1711                                SupportedModuleApiVersions {
1712                                    core_consensus: config.global.consensus_version,
1713                                    module_consensus: module_config.version,
1714                                    api: module_init.supported_api_versions(),
1715                                },
1716                            )
1717                        })
1718                })
1719                .collect(),
1720        }
1721    }
1722
1723    pub async fn load_and_refresh_common_api_version(&self) -> anyhow::Result<ApiVersionSet> {
1724        Self::load_and_refresh_common_api_version_static(
1725            &self.config().await,
1726            &self.module_inits,
1727            self.connectors.clone(),
1728            &self.api,
1729            &self.db,
1730            &self.task_group,
1731            &self.client_span,
1732        )
1733        .await
1734    }
1735
1736    /// Force refresh API versions from the federation, bypassing the cache.
1737    ///
1738    /// This queries all peers for their supported API versions and calculates
1739    /// the common API version set to use. The result is stored in the database
1740    /// cache for future use.
1741    pub async fn refresh_api_versions(&self) -> anyhow::Result<ApiVersionSet> {
1742        Self::refresh_common_api_version_static(
1743            &self.config().await,
1744            &self.module_inits,
1745            &self.api,
1746            &self.db,
1747            self.task_group.clone(),
1748            &self.client_span,
1749            true,
1750        )
1751        .await
1752    }
1753
1754    /// Load the common api versions to use from cache and start a background
1755    /// process to refresh them.
1756    ///
1757    /// This is a compromise, so we not have to wait for version discovery to
1758    /// complete every time a [`Client`] is being built.
1759    pub(crate) async fn load_and_refresh_common_api_version_static(
1760        config: &ClientConfig,
1761        module_init: &ClientModuleInitRegistry,
1762        connectors: ConnectorRegistry,
1763        api: &DynGlobalApi,
1764        db: &Database,
1765        task_group: &TaskGroup,
1766        client_span: &Span,
1767    ) -> anyhow::Result<ApiVersionSet> {
1768        if let Some(v) = db
1769            .begin_transaction_nc()
1770            .await
1771            .get_value(&CachedApiVersionSetKey)
1772            .await
1773        {
1774            client_span.in_scope(|| {
1775                debug!(
1776                    target: LOG_CLIENT,
1777                    "Found existing cached common api versions"
1778                );
1779            });
1780            let config = config.clone();
1781            let client_module_init = module_init.clone();
1782            let api = api.clone();
1783            let db = db.clone();
1784            let task_group = task_group.clone();
1785            let client_span_owned = client_span.clone();
1786            // Separate task group, because we actually don't want to be waiting for this to
1787            // finish, and it's just best effort.
1788            task_group.clone().spawn_cancellable_with_span(
1789                client_span.clone(),
1790                "refresh_common_api_version_static",
1791                async move {
1792                    connectors.wait_for_initialized_connections().await;
1793
1794                    if let Err(error) = Self::refresh_common_api_version_static(
1795                        &config,
1796                        &client_module_init,
1797                        &api,
1798                        &db,
1799                        task_group,
1800                        &client_span_owned,
1801                        false,
1802                    )
1803                    .await
1804                    {
1805                        warn!(
1806                            target: LOG_CLIENT,
1807                            err = %error.fmt_compact_anyhow(), "Failed to discover common api versions"
1808                        );
1809                    }
1810                },
1811            );
1812
1813            return Ok(v.0);
1814        }
1815
1816        info!(
1817            target: LOG_CLIENT,
1818            "Fetching initial API versions "
1819        );
1820        Self::refresh_common_api_version_static(
1821            config,
1822            module_init,
1823            api,
1824            db,
1825            task_group.clone(),
1826            client_span,
1827            true,
1828        )
1829        .await
1830    }
1831
1832    async fn refresh_common_api_version_static(
1833        config: &ClientConfig,
1834        client_module_init: &ClientModuleInitRegistry,
1835        api: &DynGlobalApi,
1836        db: &Database,
1837        task_group: TaskGroup,
1838        client_span: &Span,
1839        block_until_ok: bool,
1840    ) -> anyhow::Result<ApiVersionSet> {
1841        debug!(
1842            target: LOG_CLIENT,
1843            "Refreshing common api versions"
1844        );
1845
1846        let (num_responses_sender, mut num_responses_receiver) = tokio::sync::watch::channel(0);
1847        let num_peers = NumPeers::from(config.global.api_endpoints.len());
1848
1849        task_group.spawn_cancellable_with_span(
1850            client_span.clone(),
1851            "refresh peers api versions",
1852            Client::fetch_common_api_versions_from_all_peers(
1853                num_peers,
1854                api.clone(),
1855                db.clone(),
1856                num_responses_sender,
1857            ),
1858        );
1859
1860        let common_api_versions = loop {
1861            // Wait to collect enough answers before calculating a set of common api
1862            // versions to use. Note that all peers individual responses from
1863            // previous attempts are still being used, and requests, or even
1864            // retries for response of peers are not actually cancelled, as they
1865            // are happening on a separate task. This is all just to bound the
1866            // time user can be waiting for the join operation to finish, at the
1867            // risk of picking wrong version in very rare circumstances.
1868            let _: Result<_, Elapsed> = runtime::timeout(
1869                Duration::from_secs(30),
1870                num_responses_receiver.wait_for(|num| num_peers.threshold() <= *num),
1871            )
1872            .await;
1873
1874            let peer_api_version_sets = Self::load_peers_last_api_versions(db, num_peers).await;
1875
1876            match fedimint_client_module::api_version_discovery::discover_common_api_versions_set(
1877                &Self::supported_api_versions_summary_static(config, client_module_init),
1878                &peer_api_version_sets,
1879            ) {
1880                Ok(o) => break o,
1881                Err(err) if block_until_ok => {
1882                    warn!(
1883                        target: LOG_CLIENT,
1884                        err = %err.fmt_compact_anyhow(),
1885                        "Failed to discover API version to use. Retrying..."
1886                    );
1887                    continue;
1888                }
1889                Err(e) => return Err(e),
1890            }
1891        };
1892
1893        debug!(
1894            target: LOG_CLIENT,
1895            value = ?common_api_versions,
1896            "Updating the cached common api versions"
1897        );
1898        let mut dbtx = db.begin_transaction().await;
1899        let _ = dbtx
1900            .insert_entry(
1901                &CachedApiVersionSetKey,
1902                &CachedApiVersionSet(common_api_versions.clone()),
1903            )
1904            .await;
1905
1906        dbtx.commit_tx().await;
1907
1908        Ok(common_api_versions)
1909    }
1910
1911    /// Get the client [`Metadata`]
1912    pub async fn get_metadata(&self) -> Metadata {
1913        self.db
1914            .begin_transaction_nc()
1915            .await
1916            .get_value(&ClientMetadataKey)
1917            .await
1918            .unwrap_or_else(|| {
1919                warn!(
1920                    target: LOG_CLIENT,
1921                    "Missing existing metadata. This key should have been set on Client init"
1922                );
1923                Metadata::empty()
1924            })
1925    }
1926
1927    /// Set the client [`Metadata`]
1928    pub async fn set_metadata(&self, metadata: &Metadata) {
1929        self.db
1930            .autocommit::<_, _, anyhow::Error>(
1931                |dbtx, _| {
1932                    Box::pin(async {
1933                        Self::set_metadata_dbtx(dbtx, metadata).await;
1934                        Ok(())
1935                    })
1936                },
1937                None,
1938            )
1939            .await
1940            .expect("Failed to autocommit metadata");
1941    }
1942
1943    pub fn has_pending_recoveries(&self) -> bool {
1944        !self
1945            .client_recovery_status_receiver
1946            .borrow()
1947            .values()
1948            .all(RecoveryStatus::is_successfully_done)
1949    }
1950
1951    /// Whether every module of this client can be used right now.
1952    ///
1953    /// A module that cannot be used while it recovers is left out of the module
1954    /// registry, and only becomes available once the client is reopened with
1955    /// its recovery complete. This reports whether any module is currently held
1956    /// back that way, so an application can tell whether it has to wait for
1957    /// [`Self::wait_for_all_recoveries`] and reopen before it can do anything,
1958    /// or can go ahead right away with a recovery still running.
1959    ///
1960    /// Always `true` for a client that is not recovering.
1961    pub fn all_modules_usable(&self) -> bool {
1962        self.client_recovery_status_receiver
1963            .borrow()
1964            .keys()
1965            .all(|module_instance_id| self.modules.get(*module_instance_id).is_some())
1966    }
1967
1968    /// Wait for all module recoveries to finish
1969    ///
1970    /// Returns `Ok(())` once every module recovery completed, or an error as
1971    /// soon as any one of them fails terminally.
1972    ///
1973    /// An error does not mean the recovery task is done: the failed module's
1974    /// progress stays pending forever (so [`Self::has_pending_recoveries`]
1975    /// keeps returning `true`) and the recovery task stays parked. The
1976    /// failure is in-memory only and is not persisted, so reopening the
1977    /// client retries the recovery from its last persisted, non-terminal
1978    /// progress.
1979    ///
1980    /// A bit of a heavy approach.
1981    pub async fn wait_for_all_recoveries(&self) -> anyhow::Result<()> {
1982        Self::wait_for_recoveries(
1983            self.client_recovery_status_receiver.clone(),
1984            |_module_instance_id| true,
1985            "Recovery task completed and update receiver disconnected, but some modules failed to recover",
1986        )
1987        .await
1988    }
1989
1990    /// Wait for the recovery of every module accepted by `module_filter` to
1991    /// either complete or fail.
1992    ///
1993    /// Since [`RecoveryProgress`] can never express a failure, waiting on
1994    /// progress alone would block forever on a module that gave up. A module
1995    /// that gave up is recorded as [`RecoveryStatus::Failed`] by
1996    /// [`Self::run_module_recoveries_task`] instead, so both outcomes are
1997    /// observable on the same per-module status.
1998    async fn wait_for_recoveries(
1999        mut status_receiver: watch::Receiver<BTreeMap<ModuleInstanceId, RecoveryStatus>>,
2000        module_filter: impl Fn(ModuleInstanceId) -> bool,
2001        disconnected_context: &'static str,
2002    ) -> anyhow::Result<()> {
2003        let failure = status_receiver
2004            .wait_for(|statuses| {
2005                let matching = || {
2006                    statuses
2007                        .iter()
2008                        .filter(|(module_instance_id, _status)| module_filter(**module_instance_id))
2009                        .map(|(_module_instance_id, status)| status)
2010                };
2011
2012                // A failure is terminal, so waiting for the modules that are
2013                // still recovering would only delay an outcome that can no
2014                // longer change.
2015                matching().any(|status| matches!(status, RecoveryStatus::Failed { .. }))
2016                    || matching().all(RecoveryStatus::is_successfully_done)
2017            })
2018            .await
2019            .context(disconnected_context)?
2020            // Classified from the woken-up snapshot before anything else, so a
2021            // failure of one module still wins over another one completing.
2022            .iter()
2023            .find_map(|(module_instance_id, status)| match status {
2024                RecoveryStatus::Failed { error, .. } if module_filter(*module_instance_id) => {
2025                    Some((*module_instance_id, error.clone()))
2026                }
2027                _ => None,
2028            });
2029
2030        match failure {
2031            Some((module_instance_id, error)) => Err(anyhow!(
2032                "Module recovery failed: module_instance_id={module_instance_id}, error={error}"
2033            )),
2034            None => Ok(()),
2035        }
2036    }
2037
2038    /// Subscribe to recover progress for all the modules.
2039    ///
2040    /// This stream can contain duplicate progress for a module.
2041    /// Don't use this stream for detecting completion of recovery.
2042    ///
2043    /// It can't express a failure either: a module that failed to recover keeps
2044    /// being reported with the last progress it made, which is never done. Use
2045    /// [`Self::wait_for_all_recoveries`] or
2046    /// [`Self::wait_for_module_kind_recovery`] to learn the outcome.
2047    pub fn subscribe_to_recovery_progress(
2048        &self,
2049    ) -> impl Stream<Item = (ModuleInstanceId, RecoveryProgress)> + use<> {
2050        WatchStream::new(self.client_recovery_status_receiver.clone()).flat_map(|statuses| {
2051            futures::stream::iter(
2052                statuses
2053                    .into_iter()
2054                    .map(|(module_instance_id, status)| (module_instance_id, status.progress())),
2055            )
2056        })
2057    }
2058
2059    /// Wait for the recoveries of all modules of `module_kind` to finish
2060    ///
2061    /// Returns `Ok(())` once every recovery of that kind completed, or an error
2062    /// as soon as one of them fails terminally. Failures of other module kinds
2063    /// are ignored.
2064    ///
2065    /// See [`Self::wait_for_all_recoveries`] for what an error does and does
2066    /// not say about the state of the recovery.
2067    pub async fn wait_for_module_kind_recovery(
2068        &self,
2069        module_kind: ModuleKind,
2070    ) -> anyhow::Result<()> {
2071        let config = self.config().await;
2072        Self::wait_for_recoveries(
2073            self.client_recovery_status_receiver.clone(),
2074            move |module_instance_id| {
2075                config
2076                    .modules
2077                    .get(&module_instance_id)
2078                    .is_some_and(|module| module.kind == module_kind)
2079            },
2080            "Recovery task completed and update receiver disconnected, but the desired modules are still unavailable or failed to recover",
2081        )
2082        .await
2083    }
2084
2085    pub async fn wait_for_all_active_state_machines(&self) -> anyhow::Result<()> {
2086        loop {
2087            if self.executor.get_active_states().await.is_empty() {
2088                break;
2089            }
2090            sleep(Duration::from_millis(100)).await;
2091        }
2092        Ok(())
2093    }
2094
2095    /// Set the client [`Metadata`]
2096    pub async fn set_metadata_dbtx(dbtx: &mut DatabaseTransaction<'_>, metadata: &Metadata) {
2097        dbtx.insert_new_entry(&ClientMetadataKey, metadata).await;
2098    }
2099
2100    fn spawn_module_recoveries_task(
2101        &self,
2102        recovery_sender: watch::Sender<BTreeMap<ModuleInstanceId, RecoveryStatus>>,
2103        module_recoveries: BTreeMap<ModuleInstanceId, ModuleRecoveryFuture>,
2104        module_recovery_progress_receivers: BTreeMap<
2105            ModuleInstanceId,
2106            watch::Receiver<RecoveryProgress>,
2107        >,
2108        // Sourced from the config rather than `self.modules`, since modules
2109        // currently being recovered are not yet initialized in the registry.
2110        module_kinds: BTreeMap<ModuleInstanceId, ModuleKind>,
2111    ) {
2112        let db = self.db.clone();
2113        let log_ordering_wakeup_tx = self.log_ordering_wakeup_tx.clone();
2114        // A module can finish its own final database commit before the client
2115        // persists the corresponding completed progress below. Keep the
2116        // coordinator alive across graceful shutdown so that gap cannot cause
2117        // a non-idempotent module finalization to be replayed on reopen.
2118        self.spawn("module recoveries", |_task_handle| async {
2119            Self::run_module_recoveries_task(
2120                db,
2121                log_ordering_wakeup_tx,
2122                recovery_sender,
2123                module_recoveries,
2124                module_recovery_progress_receivers,
2125                module_kinds,
2126            )
2127            .await;
2128        });
2129    }
2130
2131    async fn run_module_recoveries_task(
2132        db: Database,
2133        log_ordering_wakeup_tx: watch::Sender<()>,
2134        recovery_sender: watch::Sender<BTreeMap<ModuleInstanceId, RecoveryStatus>>,
2135        module_recoveries: BTreeMap<ModuleInstanceId, ModuleRecoveryFuture>,
2136        module_recovery_progress_receivers: BTreeMap<
2137            ModuleInstanceId,
2138            watch::Receiver<RecoveryProgress>,
2139        >,
2140        module_kinds: BTreeMap<ModuleInstanceId, ModuleKind>,
2141    ) {
2142        debug!(target: LOG_CLIENT_RECOVERY, num_modules=%module_recovery_progress_receivers.len(), "Staring module recoveries");
2143
2144        // A recovery update for a single module: either an intermediate progress
2145        // report, or the final completion carrying the recovered amount (if the
2146        // module tracks it).
2147        enum RecoveryUpdate {
2148            Progress(RecoveryProgress),
2149            Completed(Option<Amount>),
2150        }
2151
2152        let mut completed_stream = Vec::new();
2153        let progress_stream = futures::stream::FuturesUnordered::new();
2154
2155        for (module_instance_id, f) in module_recoveries {
2156            let recovery_sender = recovery_sender.clone();
2157            completed_stream.push(futures::stream::once(Box::pin(async move {
2158                match f.await {
2159                    Ok(amount) => (module_instance_id, RecoveryUpdate::Completed(amount)),
2160                    Err(err) => {
2161                        let error = err.fmt_compact_anyhow().to_string();
2162                        warn!(
2163                            target: LOG_CLIENT,
2164                            err = %error.as_str(), module_instance_id, "Module recovery failed"
2165                        );
2166                        // since the progress a module reports can't express a
2167                        // failure, record it as the terminal state of this
2168                        // module's recovery for anyone waiting on the outcome.
2169                        // Keyed by module instance so a later failure of
2170                        // another module can't overwrite this one before a
2171                        // waiter observes it.
2172                        recovery_sender.send_modify(|statuses| {
2173                            let last_progress = statuses
2174                                .get(&module_instance_id)
2175                                .expect("existing status must be present")
2176                                .progress();
2177                            statuses.insert(
2178                                module_instance_id,
2179                                RecoveryStatus::Failed {
2180                                    last_progress,
2181                                    error,
2182                                },
2183                            );
2184                        });
2185                        // The terminal failure is now published to the waiters,
2186                        // and this branch stays pending on purpose so the
2187                        // recovery coordinator and its single status sender
2188                        // remain alive. The failed module's progress never
2189                        // becomes done either, so observers that only see the
2190                        // progress keep treating the recovery as pending, which
2191                        // it effectively is. The failure is deliberately not
2192                        // persisted, so reopening the client retries the
2193                        // recovery.
2194                        futures::future::pending::<()>().await;
2195                        unreachable!()
2196                    }
2197                }
2198            })));
2199        }
2200
2201        for (module_instance_id, rx) in module_recovery_progress_receivers {
2202            progress_stream.push(
2203                tokio_stream::wrappers::WatchStream::new(rx)
2204                    .fuse()
2205                    .map(move |progress| (module_instance_id, RecoveryUpdate::Progress(progress))),
2206            );
2207        }
2208
2209        let mut futures = futures::stream::select(
2210            futures::stream::select_all(progress_stream),
2211            futures::stream::select_all(completed_stream),
2212        );
2213
2214        while let Some((module_instance_id, update)) = futures.next().await {
2215            // Cloned so the `watch::Ref` is released right here: holding it
2216            // across the awaits or the `send_modify` below would deadlock the
2217            // channel it borrows from.
2218            let prev_status = recovery_sender
2219                .borrow()
2220                .get(&module_instance_id)
2221                .expect("existing status must be present")
2222                .clone();
2223
2224            // A failure is the terminal state of a module's recovery, but
2225            // progress and completion are merged without any ordering between
2226            // them, so a stale progress update of that module can still arrive
2227            // after it. Applying it would overwrite the failure with an
2228            // in-progress status, and a waiter subscribing afterwards would
2229            // block forever again. Ignoring it entirely, without touching the
2230            // channel, also keeps subscribers from seeing a snapshot that says
2231            // nothing new. Mirrors the sticky "once done, stick with it"
2232            // handling below.
2233            if matches!(prev_status, RecoveryStatus::Failed { .. }) {
2234                debug!(
2235                    target: LOG_CLIENT_RECOVERY,
2236                    module_instance_id,
2237                    "Ignoring a recovery update of a module whose recovery already failed"
2238                );
2239                continue;
2240            }
2241
2242            let prev_progress = prev_status.progress();
2243
2244            // A module reports its progress on a channel it owns, so the values
2245            // arriving here are untrusted: `update_recovery_progress` filters
2246            // them, but a module can send on `progress_tx` directly. Reject the
2247            // values that would break the invariants downstream depends on
2248            // before anything is persisted or broadcast.
2249            if let RecoveryUpdate::Progress(progress) = &update {
2250                if progress.is_done() {
2251                    warn!(
2252                        target: LOG_CLIENT_RECOVERY,
2253                        module_instance_id,
2254                        "Module bypassed the sanctioned recovery progress reporting API and reported a completed recovery progress. Ignoring"
2255                    );
2256                    continue;
2257                }
2258
2259                // The module's channel starts at the progress the client seeded
2260                // it with, so a "none" that doesn't regress anything is the
2261                // normal start of a recovery, not a module misbehaving. Neither
2262                // is one seen after the module already completed: progress and
2263                // completion are merged without ordering between them, so a
2264                // seeded value can arrive after the completion it preceded.
2265                if progress.is_none() && !prev_progress.is_none() && !prev_progress.is_done() {
2266                    warn!(
2267                        target: LOG_CLIENT_RECOVERY,
2268                        module_instance_id,
2269                        "Module bypassed the sanctioned recovery progress reporting API and reported a none recovery progress, regressing its previous one. Ignoring"
2270                    );
2271                    continue;
2272                }
2273            }
2274
2275            let mut dbtx = db.begin_transaction().await;
2276
2277            // The recovered amount is only known once the module's recovery
2278            // future resolves, which is also the only way progress transitions
2279            // to "done": the guard above rejects a completed progress reported
2280            // by a module, so `RecoveryUpdate::Completed` stays the only
2281            // producer of a done progress, and done therefore implies success.
2282            let (progress, recovered_amount) = if prev_progress.is_done() {
2283                // since updates might be out of order, once done, stick with it
2284                (prev_progress, None)
2285            } else {
2286                match update {
2287                    RecoveryUpdate::Progress(progress) => (progress, None),
2288                    RecoveryUpdate::Completed(amount) => (prev_progress.to_complete(), amount),
2289                }
2290            };
2291
2292            if !prev_progress.is_done() && progress.is_done() {
2293                info!(
2294                    target: LOG_CLIENT,
2295                    module_instance_id,
2296                    progress = format!("{}/{}", progress.complete, progress.total),
2297                    amount = ?recovered_amount,
2298                    "Recovery complete"
2299                );
2300                dbtx.log_event(
2301                    log_ordering_wakeup_tx.clone(),
2302                    None,
2303                    ModuleRecoveryCompleted {
2304                        module_id: module_instance_id,
2305                        kind: module_kinds.get(&module_instance_id).cloned(),
2306                        amount: recovered_amount,
2307                    },
2308                )
2309                .await;
2310            } else {
2311                info!(
2312                    target: LOG_CLIENT,
2313                    module_instance_id,
2314                    kind = ?module_kinds.get(&module_instance_id),
2315                    progress = format!("{}/{}", progress.complete, progress.total),
2316                    "Recovery progress"
2317                );
2318            }
2319
2320            dbtx.insert_entry(
2321                &ClientModuleRecovery { module_instance_id },
2322                &ClientModuleRecoveryState { progress },
2323            )
2324            .await;
2325            dbtx.commit_tx().await;
2326
2327            recovery_sender.send_modify(|statuses| {
2328                statuses.insert(module_instance_id, RecoveryStatus::InProgress(progress));
2329            });
2330        }
2331        debug!(target: LOG_CLIENT_RECOVERY, "Recovery executor stopped");
2332    }
2333
2334    async fn load_peers_last_api_versions(
2335        db: &Database,
2336        num_peers: NumPeers,
2337    ) -> BTreeMap<PeerId, SupportedApiVersionsSummary> {
2338        let mut peer_api_version_sets = BTreeMap::new();
2339
2340        let mut dbtx = db.begin_transaction_nc().await;
2341        for peer_id in num_peers.peer_ids() {
2342            if let Some(v) = dbtx
2343                .get_value(&PeerLastApiVersionsSummaryKey(peer_id))
2344                .await
2345            {
2346                peer_api_version_sets.insert(peer_id, v.0);
2347            }
2348        }
2349        drop(dbtx);
2350        peer_api_version_sets
2351    }
2352
2353    /// You likely want to use [`Client::get_peer_urls`]. This function returns
2354    /// only the announcements and doesn't use the config as fallback.
2355    pub async fn get_peer_url_announcements(&self) -> BTreeMap<PeerId, SignedApiAnnouncement> {
2356        self.db()
2357            .begin_transaction_nc()
2358            .await
2359            .find_by_prefix(&ApiAnnouncementPrefix)
2360            .await
2361            .map(|(announcement_key, announcement)| (announcement_key.0, announcement))
2362            .collect()
2363            .await
2364    }
2365
2366    /// Returns guardian metadata stored in the client database
2367    pub async fn get_guardian_metadata(
2368        &self,
2369    ) -> BTreeMap<PeerId, fedimint_core::net::guardian_metadata::SignedGuardianMetadata> {
2370        self.db()
2371            .begin_transaction_nc()
2372            .await
2373            .find_by_prefix(&crate::guardian_metadata::GuardianMetadataPrefix)
2374            .await
2375            .map(|(key, metadata)| (key.0, metadata))
2376            .collect()
2377            .await
2378    }
2379
2380    /// Returns a list of guardian API URLs
2381    pub async fn get_peer_urls(&self) -> BTreeMap<PeerId, SafeUrl> {
2382        get_api_urls(&self.db, &self.config().await, self.iroh_enable_next).await
2383    }
2384
2385    /// Create an invite code with the api endpoint of the given peer which can
2386    /// be used to download this client config
2387    pub async fn invite_code(&self, peer: PeerId) -> Option<InviteCode> {
2388        self.get_peer_urls()
2389            .await
2390            .into_iter()
2391            .find_map(|(peer_id, url)| (peer == peer_id).then_some(url))
2392            .map(|peer_url| {
2393                InviteCode::new(
2394                    peer_url.clone(),
2395                    peer,
2396                    self.federation_id(),
2397                    self.api_secret.clone(),
2398                )
2399            })
2400    }
2401
2402    /// Blocks till the client has synced the guardian public key set
2403    /// (introduced in version 0.4) and returns it. Once it has been fetched
2404    /// once this function is guaranteed to return immediately.
2405    pub async fn get_guardian_public_keys_blocking(
2406        &self,
2407    ) -> BTreeMap<PeerId, fedimint_core::secp256k1::PublicKey> {
2408        self.db
2409            .autocommit(
2410                |dbtx, _| {
2411                    Box::pin(async move {
2412                        let config = self.config().await;
2413
2414                        let guardian_pub_keys = self
2415                            .get_or_backfill_broadcast_public_keys(dbtx, config)
2416                            .await;
2417
2418                        Result::<_, ()>::Ok(guardian_pub_keys)
2419                    })
2420                },
2421                None,
2422            )
2423            .await
2424            .expect("Will retry forever")
2425    }
2426
2427    async fn get_or_backfill_broadcast_public_keys(
2428        &self,
2429        dbtx: &mut DatabaseTransaction<'_>,
2430        config: ClientConfig,
2431    ) -> BTreeMap<PeerId, PublicKey> {
2432        match config.global.broadcast_public_keys {
2433            Some(guardian_pub_keys) => guardian_pub_keys,
2434            _ => {
2435                let (guardian_pub_keys, new_config) = self.fetch_and_update_config(config).await;
2436
2437                dbtx.insert_entry(&ClientConfigKey, &new_config).await;
2438                *(self.config.write().await) = new_config;
2439                guardian_pub_keys
2440            }
2441        }
2442    }
2443
2444    pub async fn fetch_session_count(&self) -> FederationResult<u64> {
2445        self.api.session_count().await
2446    }
2447
2448    async fn fetch_and_update_config(
2449        &self,
2450        config: ClientConfig,
2451    ) -> (BTreeMap<PeerId, PublicKey>, ClientConfig) {
2452        let fetched_config = retry(
2453            "Fetching guardian public keys",
2454            backoff_util::background_backoff(),
2455            || async {
2456                anyhow::Ok(
2457                    self.api
2458                        .request_current_consensus::<ClientConfig>(
2459                            CLIENT_CONFIG_ENDPOINT.to_owned(),
2460                            ApiRequestErased::default(),
2461                        )
2462                        .await?,
2463                )
2464            },
2465        )
2466        .await
2467        .expect("Will never return on error");
2468
2469        let Some(guardian_pub_keys) = fetched_config.global.broadcast_public_keys else {
2470            warn!(
2471                target: LOG_CLIENT,
2472                "Guardian public keys not found in fetched config, server not updated to 0.4 yet"
2473            );
2474            pending::<()>().await;
2475            unreachable!("Pending will never return");
2476        };
2477
2478        let new_config = ClientConfig {
2479            global: GlobalClientConfig {
2480                broadcast_public_keys: Some(guardian_pub_keys.clone()),
2481                ..config.global
2482            },
2483            modules: config.modules,
2484        };
2485        (guardian_pub_keys, new_config)
2486    }
2487
2488    pub fn handle_global_rpc(
2489        &self,
2490        method: String,
2491        params: serde_json::Value,
2492    ) -> BoxStream<'_, anyhow::Result<serde_json::Value>> {
2493        Box::pin(try_stream! {
2494            match method.as_str() {
2495                "get_balance" => {
2496                    let balance = self.get_balance_for_btc().await.unwrap_or_default();
2497                    yield serde_json::to_value(balance)?;
2498                }
2499                "subscribe_balance_changes" => {
2500                    let req: GetBalanceChangesRequest= serde_json::from_value(params)?;
2501                    let mut stream = self.subscribe_balance_changes(req.unit).await;
2502                    while let Some(balance) = stream.next().await {
2503                        yield serde_json::to_value(balance)?;
2504                    }
2505                }
2506                "get_config" => {
2507                    let config = self.config().await;
2508                    yield serde_json::to_value(config)?;
2509                }
2510                "get_federation_id" => {
2511                    let federation_id = self.federation_id();
2512                    yield serde_json::to_value(federation_id)?;
2513                }
2514                "get_invite_code" => {
2515                    let req: GetInviteCodeRequest = serde_json::from_value(params)?;
2516                    let invite_code = self.invite_code(req.peer).await;
2517                    yield serde_json::to_value(invite_code)?;
2518                }
2519                "get_operation" => {
2520                    let req: GetOperationIdRequest = serde_json::from_value(params)?;
2521                    let operation = self.operation_log().get_operation(req.operation_id).await;
2522                    yield serde_json::to_value(operation)?;
2523                }
2524                "list_operations" => {
2525                    let req: ListOperationsParams = serde_json::from_value(params)?;
2526                    let limit = if req.limit.is_none() && req.last_seen.is_none() {
2527                        usize::MAX
2528                    } else {
2529                        req.limit.unwrap_or(usize::MAX)
2530                    };
2531                    let operations = self.operation_log()
2532                        .paginate_operations_rev(limit, req.last_seen)
2533                        .await;
2534                    yield serde_json::to_value(operations)?;
2535                }
2536                "get_event_log" => {
2537                    let req: GetEventLogRequest = serde_json::from_value(params)?;
2538                    let limit = req
2539                        .limit
2540                        .unwrap_or(DEFAULT_EVENT_LOG_PAGE_SIZE)
2541                        .min(MAX_EVENT_LOG_PAGE_SIZE);
2542                    let events = self.get_event_log(req.pos, limit).await;
2543                    yield serde_json::to_value(events)?;
2544                }
2545                "session_count" => {
2546                    let count = self.fetch_session_count().await?;
2547                    yield serde_json::to_value(count)?;
2548                }
2549                "has_pending_recoveries" => {
2550                    let has_pending = self.has_pending_recoveries();
2551                    yield serde_json::to_value(has_pending)?;
2552                }
2553                "wait_for_all_recoveries" => {
2554                    self.wait_for_all_recoveries().await?;
2555                    yield serde_json::Value::Null;
2556                }
2557                "subscribe_to_recovery_progress" => {
2558                    let mut stream = self.subscribe_to_recovery_progress();
2559                    while let Some((module_id, progress)) = stream.next().await {
2560                        yield serde_json::json!({
2561                            "module_id": module_id,
2562                            "progress": progress
2563                        });
2564                    }
2565                }
2566                #[allow(deprecated)]
2567                "backup_to_federation" => {
2568                    let metadata = if params.is_null() {
2569                        Metadata::from_json_serialized(serde_json::json!({}))
2570                    } else {
2571                        Metadata::from_json_serialized(params)
2572                    };
2573                    self.backup_to_federation(metadata).await?;
2574                    yield serde_json::Value::Null;
2575                }
2576                _ => {
2577                    Err(anyhow::format_err!("Unknown method: {}", method))?;
2578                    unreachable!()
2579                },
2580            }
2581        })
2582    }
2583
2584    pub async fn log_event<E>(&self, module_id: Option<ModuleInstanceId>, event: E)
2585    where
2586        E: Event + Send,
2587    {
2588        let mut dbtx = self.db.begin_transaction().await;
2589        self.log_event_dbtx(&mut dbtx, module_id, event).await;
2590        dbtx.commit_tx().await;
2591    }
2592
2593    pub async fn log_event_dbtx<E, Cap>(
2594        &self,
2595        dbtx: &mut DatabaseTransaction<'_, Cap>,
2596        module_id: Option<ModuleInstanceId>,
2597        event: E,
2598    ) where
2599        E: Event + Send,
2600        Cap: Send,
2601    {
2602        dbtx.log_event(self.log_ordering_wakeup_tx.clone(), module_id, event)
2603            .await;
2604    }
2605
2606    pub async fn log_event_raw_dbtx<Cap>(
2607        &self,
2608        dbtx: &mut DatabaseTransaction<'_, Cap>,
2609        kind: EventKind,
2610        module: Option<(ModuleKind, ModuleInstanceId)>,
2611        payload: Vec<u8>,
2612        persist: EventPersistence,
2613    ) where
2614        Cap: Send,
2615    {
2616        let module_id = module.as_ref().map(|m| m.1);
2617        let module_kind = module.map(|m| m.0);
2618        dbtx.log_event_raw(
2619            self.log_ordering_wakeup_tx.clone(),
2620            kind,
2621            module_kind,
2622            module_id,
2623            payload,
2624            persist,
2625        )
2626        .await;
2627    }
2628
2629    /// Built in event log (trimmable) tracker
2630    ///
2631    /// For the convenience of downstream applications, [`Client`] can store
2632    /// internally event log position for the main application using/driving it.
2633    ///
2634    /// Note that this position is a singleton, so this tracker should not be
2635    /// used for multiple purposes or applications, etc. at the same time.
2636    ///
2637    /// If the application has a need to follow log using multiple trackers, it
2638    /// should implement own [`DynEventLogTrimableTracker`] and store its
2639    /// persient data by itself.
2640    pub fn built_in_application_event_log_tracker(&self) -> DynEventLogTrimableTracker {
2641        struct BuiltInApplicationEventLogTracker;
2642
2643        #[apply(async_trait_maybe_send!)]
2644        impl EventLogTrimableTracker for BuiltInApplicationEventLogTracker {
2645            // Store position in the event log
2646            async fn store(
2647                &mut self,
2648                dbtx: &mut DatabaseTransaction<NonCommittable>,
2649                pos: EventLogTrimableId,
2650            ) -> anyhow::Result<()> {
2651                dbtx.insert_entry(&DefaultApplicationEventLogKey, &pos)
2652                    .await;
2653                Ok(())
2654            }
2655
2656            /// Load the last previous stored position (or None if never stored)
2657            async fn load(
2658                &mut self,
2659                dbtx: &mut DatabaseTransaction<NonCommittable>,
2660            ) -> anyhow::Result<Option<EventLogTrimableId>> {
2661                Ok(dbtx.get_value(&DefaultApplicationEventLogKey).await)
2662            }
2663        }
2664        Box::new(BuiltInApplicationEventLogTracker)
2665    }
2666
2667    /// Like [`Self::handle_events`] but for historical data.
2668    ///
2669    ///
2670    /// This function can be used to process subset of events
2671    /// that is infrequent and important enough to be persisted
2672    /// forever. Most applications should prefer to use [`Self::handle_events`]
2673    /// which emits *all* events.
2674    pub async fn handle_historical_events<F, R>(
2675        &self,
2676        tracker: fedimint_eventlog::DynEventLogTracker,
2677        handler_fn: F,
2678    ) -> anyhow::Result<()>
2679    where
2680        F: Fn(&mut DatabaseTransaction<NonCommittable>, EventLogEntry) -> R,
2681        R: Future<Output = anyhow::Result<()>>,
2682    {
2683        fedimint_eventlog::handle_events(
2684            self.db.clone(),
2685            tracker,
2686            self.log_event_added_rx.clone(),
2687            handler_fn,
2688        )
2689        .await
2690    }
2691
2692    /// Handle events emitted by the client
2693    ///
2694    /// This is a preferred method for reactive & asynchronous
2695    /// processing of events emitted by the client.
2696    ///
2697    /// It needs a `tracker` that will persist the position in the log
2698    /// as it is being handled. You can use the
2699    /// [`Client::built_in_application_event_log_tracker`] if this call is
2700    /// used for the single main application handling this instance of the
2701    /// [`Client`]. Otherwise you should implement your own tracker.
2702    ///
2703    /// This handler will call `handle_fn` with ever event emitted by
2704    /// [`Client`], including transient ones. The caller should atomically
2705    /// handle each event it is interested in and ignore other ones.
2706    ///
2707    /// This method returns only when client is shutting down or on internal
2708    /// error, so typically should be called in a background task dedicated
2709    /// to handling events.
2710    pub async fn handle_events<F, R>(
2711        &self,
2712        tracker: fedimint_eventlog::DynEventLogTrimableTracker,
2713        handler_fn: F,
2714    ) -> anyhow::Result<()>
2715    where
2716        F: Fn(&mut DatabaseTransaction<NonCommittable>, EventLogEntry) -> R,
2717        R: Future<Output = anyhow::Result<()>>,
2718    {
2719        fedimint_eventlog::handle_trimable_events(
2720            self.db.clone(),
2721            tracker,
2722            self.log_event_added_rx.clone(),
2723            handler_fn,
2724        )
2725        .await
2726    }
2727
2728    pub async fn get_event_log(
2729        &self,
2730        pos: Option<EventLogId>,
2731        limit: u64,
2732    ) -> Vec<PersistedLogEntry> {
2733        self.get_event_log_dbtx(&mut self.db.begin_transaction_nc().await, pos, limit)
2734            .await
2735    }
2736
2737    /// Returns the id that the next entry appended to the event log will be
2738    /// assigned, i.e. the position just past the current end of the log.
2739    pub async fn get_next_event_log_id(&self) -> EventLogId {
2740        self.db
2741            .begin_transaction_nc()
2742            .await
2743            .get_next_event_log_id()
2744            .await
2745    }
2746
2747    pub async fn get_event_log_trimable(
2748        &self,
2749        pos: Option<EventLogTrimableId>,
2750        limit: u64,
2751    ) -> Vec<PersistedLogEntry> {
2752        self.get_event_log_trimable_dbtx(&mut self.db.begin_transaction_nc().await, pos, limit)
2753            .await
2754    }
2755
2756    pub async fn get_event_log_dbtx<Cap>(
2757        &self,
2758        dbtx: &mut DatabaseTransaction<'_, Cap>,
2759        pos: Option<EventLogId>,
2760        limit: u64,
2761    ) -> Vec<PersistedLogEntry>
2762    where
2763        Cap: Send,
2764    {
2765        dbtx.get_event_log(pos, limit).await
2766    }
2767
2768    pub async fn get_event_log_trimable_dbtx<Cap>(
2769        &self,
2770        dbtx: &mut DatabaseTransaction<'_, Cap>,
2771        pos: Option<EventLogTrimableId>,
2772        limit: u64,
2773    ) -> Vec<PersistedLogEntry>
2774    where
2775        Cap: Send,
2776    {
2777        dbtx.get_event_log_trimable(pos, limit).await
2778    }
2779
2780    /// Register to receiver all new transient (unpersisted) events
2781    pub fn get_event_log_transient_receiver(&self) -> broadcast::Receiver<EventLogEntry> {
2782        self.log_event_added_transient_tx.subscribe()
2783    }
2784
2785    /// Get a receiver that signals when new events are added to the event log
2786    pub fn log_event_added_rx(&self) -> watch::Receiver<()> {
2787        self.log_event_added_rx.clone()
2788    }
2789
2790    pub fn iroh_enable_dht(&self) -> bool {
2791        self.iroh_enable_dht
2792    }
2793
2794    /// Whether compatible iroh-next endpoints from guardian metadata are
2795    /// preferred.
2796    pub fn iroh_enable_next(&self) -> bool {
2797        self.iroh_enable_next
2798    }
2799
2800    pub(crate) async fn run_core_migrations(
2801        db_no_decoders: &Database,
2802    ) -> Result<(), anyhow::Error> {
2803        let mut dbtx = db_no_decoders.begin_transaction().await;
2804        apply_migrations_core_client_dbtx(&mut dbtx.to_ref_nc(), "fedimint-client".to_string())
2805            .await?;
2806        if is_running_in_test_env() {
2807            verify_client_db_integrity_dbtx(&mut dbtx.to_ref_nc()).await;
2808        }
2809        dbtx.commit_tx_result().await?;
2810        Ok(())
2811    }
2812
2813    /// Iterator over primary modules for a given `unit`
2814    fn primary_modules_for_unit(
2815        &self,
2816        unit: AmountUnit,
2817    ) -> impl Iterator<Item = (ModuleInstanceId, &DynClientModule)> {
2818        self.primary_modules
2819            .iter()
2820            .flat_map(move |(_prio, candidates)| {
2821                candidates
2822                    .specific
2823                    .get(&unit)
2824                    .into_iter()
2825                    .flatten()
2826                    .copied()
2827                    // within same priority, wildcard matches come last
2828                    .chain(candidates.wildcard.iter().copied())
2829            })
2830            .map(|id| (id, self.modules.get_expect(id)))
2831    }
2832
2833    /// Primary module to use for `unit`
2834    ///
2835    /// Currently, just pick the first (highest priority) match
2836    pub fn primary_module_for_unit(
2837        &self,
2838        unit: AmountUnit,
2839    ) -> Option<(ModuleInstanceId, &DynClientModule)> {
2840        self.primary_modules_for_unit(unit).next()
2841    }
2842
2843    /// [`Self::primary_module_for_unit`] for Bitcoin
2844    pub fn primary_module_for_btc(&self) -> (ModuleInstanceId, &DynClientModule) {
2845        self.primary_module_for_unit(AmountUnit::BITCOIN)
2846            .expect("No primary module for Bitcoin")
2847    }
2848}
2849
2850#[apply(async_trait_maybe_send!)]
2851impl ClientContextIface for Client {
2852    fn get_module(&self, instance: ModuleInstanceId) -> &maybe_add_send_sync!(dyn IClientModule) {
2853        Client::get_module(self, instance)
2854    }
2855
2856    fn api_clone(&self) -> DynGlobalApi {
2857        Client::api_clone(self)
2858    }
2859    fn decoders(&self) -> &ModuleDecoderRegistry {
2860        Client::decoders(self)
2861    }
2862
2863    async fn finalize_and_submit_transaction(
2864        &self,
2865        operation_id: OperationId,
2866        operation_type: &str,
2867        operation_meta_gen: Box<maybe_add_send_sync!(dyn Fn(OutPointRange) -> serde_json::Value)>,
2868        tx_builder: TransactionBuilder,
2869    ) -> anyhow::Result<OutPointRange> {
2870        Client::finalize_and_submit_transaction(
2871            self,
2872            operation_id,
2873            operation_type,
2874            // |out_point_range| operation_meta_gen(out_point_range),
2875            &operation_meta_gen,
2876            tx_builder,
2877        )
2878        .await
2879    }
2880
2881    async fn finalize_and_submit_transaction_dbtx(
2882        &self,
2883        dbtx: &mut DatabaseTransaction<'_>,
2884        operation_id: OperationId,
2885        operation_type: &str,
2886        operation_meta_gen: Box<maybe_add_send_sync!(dyn Fn(OutPointRange) -> serde_json::Value)>,
2887        tx_builder: TransactionBuilder,
2888    ) -> anyhow::Result<OutPointRange> {
2889        Client::finalize_and_submit_transaction_dbtx(
2890            self,
2891            dbtx,
2892            operation_id,
2893            operation_type,
2894            &operation_meta_gen,
2895            tx_builder,
2896        )
2897        .await
2898    }
2899
2900    async fn finalize_and_submit_transaction_inner(
2901        &self,
2902        dbtx: &mut DatabaseTransaction<'_>,
2903        operation_id: OperationId,
2904        tx_builder: TransactionBuilder,
2905    ) -> anyhow::Result<OutPointRange> {
2906        Client::finalize_and_submit_transaction_inner(self, dbtx, operation_id, tx_builder).await
2907    }
2908
2909    async fn fee_quote(
2910        &self,
2911        operation_id: OperationId,
2912        request: FeeQuoteRequest,
2913    ) -> anyhow::Result<FeeQuote> {
2914        Client::fee_quote(self, operation_id, request).await
2915    }
2916
2917    async fn get_balance_for_unit(&self, unit: AmountUnit) -> anyhow::Result<Amount> {
2918        Client::get_balance_for_unit(self, unit).await
2919    }
2920
2921    async fn transaction_updates(&self, operation_id: OperationId) -> TransactionUpdates {
2922        Client::transaction_updates(self, operation_id).await
2923    }
2924
2925    async fn await_primary_module_outputs(
2926        &self,
2927        operation_id: OperationId,
2928        // TODO: make `impl Iterator<Item = ...>`
2929        outputs: Vec<OutPoint>,
2930    ) -> anyhow::Result<()> {
2931        Client::await_primary_bitcoin_module_outputs(self, operation_id, outputs).await
2932    }
2933
2934    fn operation_log(&self) -> &dyn IOperationLog {
2935        Client::operation_log(self)
2936    }
2937
2938    async fn has_active_states(&self, operation_id: OperationId) -> bool {
2939        Client::has_active_states(self, operation_id).await
2940    }
2941
2942    async fn operation_exists(&self, operation_id: OperationId) -> bool {
2943        Client::operation_exists(self, operation_id).await
2944    }
2945
2946    async fn config(&self) -> ClientConfig {
2947        Client::config(self).await
2948    }
2949
2950    fn db(&self) -> &Database {
2951        Client::db(self)
2952    }
2953
2954    fn executor(&self) -> &(maybe_add_send_sync!(dyn IExecutor + 'static)) {
2955        Client::executor(self)
2956    }
2957
2958    async fn invite_code(&self, peer: PeerId) -> Option<InviteCode> {
2959        Client::invite_code(self, peer).await
2960    }
2961
2962    fn get_internal_payment_markers(&self) -> anyhow::Result<(PublicKey, u64)> {
2963        Client::get_internal_payment_markers(self)
2964    }
2965
2966    async fn log_event_json(
2967        &self,
2968        dbtx: &mut DatabaseTransaction<'_, NonCommittable>,
2969        module_kind: Option<ModuleKind>,
2970        module_id: ModuleInstanceId,
2971        kind: EventKind,
2972        payload: serde_json::Value,
2973        persist: EventPersistence,
2974    ) {
2975        dbtx.ensure_global()
2976            .expect("Must be called with global dbtx");
2977        self.log_event_raw_dbtx(
2978            dbtx,
2979            kind,
2980            module_kind.map(|kind| (kind, module_id)),
2981            serde_json::to_vec(&payload).expect("Serialization can't fail"),
2982            persist,
2983        )
2984        .await;
2985    }
2986
2987    async fn read_operation_active_states<'dbtx>(
2988        &self,
2989        operation_id: OperationId,
2990        module_id: ModuleInstanceId,
2991        dbtx: &'dbtx mut DatabaseTransaction<'_>,
2992    ) -> Pin<Box<maybe_add_send!(dyn Stream<Item = (ActiveStateKey, ActiveStateMeta)> + 'dbtx)>>
2993    {
2994        Box::pin(
2995            dbtx.find_by_prefix(&ActiveModuleOperationStateKeyPrefix {
2996                operation_id,
2997                module_instance: module_id,
2998            })
2999            .await
3000            .map(move |(k, v)| (k.0, v)),
3001        )
3002    }
3003    async fn read_operation_inactive_states<'dbtx>(
3004        &self,
3005        operation_id: OperationId,
3006        module_id: ModuleInstanceId,
3007        dbtx: &'dbtx mut DatabaseTransaction<'_>,
3008    ) -> Pin<Box<maybe_add_send!(dyn Stream<Item = (InactiveStateKey, InactiveStateMeta)> + 'dbtx)>>
3009    {
3010        Box::pin(
3011            dbtx.find_by_prefix(&InactiveModuleOperationStateKeyPrefix {
3012                operation_id,
3013                module_instance: module_id,
3014            })
3015            .await
3016            .map(move |(k, v)| (k.0, v)),
3017        )
3018    }
3019}
3020
3021// TODO: impl `Debug` for `Client` and derive here
3022impl fmt::Debug for Client {
3023    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3024        write!(f, "Client")
3025    }
3026}
3027
3028pub fn client_decoders<'a>(
3029    registry: &ModuleInitRegistry<DynClientModuleInit>,
3030    module_kinds: impl Iterator<Item = (ModuleInstanceId, &'a ModuleKind)>,
3031) -> ModuleDecoderRegistry {
3032    let mut modules = BTreeMap::new();
3033    for (id, kind) in module_kinds {
3034        let Some(init) = registry.get(kind) else {
3035            debug!("Detected configuration for unsupported module id: {id}, kind: {kind}");
3036            continue;
3037        };
3038
3039        modules.insert(
3040            id,
3041            (
3042                kind.clone(),
3043                IClientModuleInit::decoder(AsRef::<dyn IClientModuleInit + 'static>::as_ref(init)),
3044            ),
3045        );
3046    }
3047    ModuleDecoderRegistry::from(modules)
3048}