Skip to main content

fedimint_client/
client.rs

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