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