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
115const 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#[derive(Default)]
130pub(crate) struct PrimaryModuleCandidates {
131 specific: BTreeMap<AmountUnit, Vec<ModuleInstanceId>>,
133 wildcard: Vec<ModuleInstanceId>,
135}
136
137pub(crate) type ModuleRecoveryFuture =
140 Pin<Box<maybe_add_send!(dyn Future<Output = Result<Option<Amount>, ClientModuleError>>)>>;
141
142#[derive(Clone, Debug)]
167pub(crate) enum RecoveryStatus {
168 InProgress(RecoveryProgress),
172 Failed {
176 last_progress: RecoveryProgress,
177 error: Arc<ClientModuleError>,
178 },
179}
180
181impl RecoveryStatus {
182 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 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
206pub 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 client_span: Span,
244
245 client_recovery_status_receiver: watch::Receiver<BTreeMap<ModuleInstanceId, RecoveryStatus>>,
258
259 log_ordering_wakeup_tx: watch::Sender<()>,
262 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 #[allow(dead_code)]
273 user_bitcoind_rpc: Option<DynBitcoindRpc>,
274 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 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 pub fn connection_status_stream(&self) -> impl Stream<Item = BTreeMap<PeerId, PeerStatus>> {
328 self.api.connection_status_stream()
329 }
330
331 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 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 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 pub fn task_group(&self) -> &TaskGroup {
406 &self.task_group
407 }
408
409 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 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 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 pub fn get_metrics() -> Result<String, fedimint_metrics::prometheus::Error> {
459 fedimint_metrics::get_metrics()
460 }
461
462 #[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 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 pub fn api_secret(&self) -> &Option<String> {
584 &self.api_secret
585 }
586
587 pub async fn core_api_version(&self) -> ApiVersion {
593 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 pub async fn chain_id(&self) -> Result<ChainId, FederationError> {
611 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 let chain_id = self.api.chain_id().await?;
624
625 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 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 fn transaction_builder_get_balance(&self, builder: &TransactionBuilder) -> (Amounts, Amounts) {
660 let mut in_amounts = Amounts::ZERO;
662 let mut out_amounts = Amounts::ZERO;
663 let mut fee_amounts = Amounts::ZERO;
664
665 for input in builder.inputs() {
666 let module = self.get_module(input.input.module_instance_id());
667
668 let item_fees = module.input_fee(&input.amounts, &input.input).expect(
669 "We only build transactions with input versions that are supported by the module",
670 );
671
672 in_amounts.checked_add_mut(&input.amounts);
673 fee_amounts.checked_add_mut(&item_fees);
674 }
675
676 for output in builder.outputs() {
677 let module = self.get_module(output.output.module_instance_id());
678
679 let item_fees = module.output_fee(&output.amounts, &output.output).expect(
680 "We only build transactions with output versions that are supported by the module",
681 );
682
683 out_amounts.checked_add_mut(&output.amounts);
684 fee_amounts.checked_add_mut(&item_fees);
685 }
686
687 out_amounts.checked_add_mut(&fee_amounts);
688 (in_amounts, out_amounts)
689 }
690
691 pub fn get_internal_payment_markers(
692 &self,
693 ) -> Result<(PublicKey, u64), bitcoin::secp256k1::Error> {
694 Ok((self.federation_id().to_fake_ln_pub_key(&self.secp_ctx)?, 0))
695 }
696
697 pub fn get_config_meta(&self, key: &str) -> Option<String> {
699 self.federation_config_meta.get(key).cloned()
700 }
701
702 pub(crate) fn root_secret(&self) -> DerivableSecret {
703 self.root_secret.clone()
704 }
705
706 pub async fn add_state_machines(
707 &self,
708 dbtx: &mut DatabaseTransaction<'_>,
709 states: Vec<DynState>,
710 ) -> AddStateMachinesResult {
711 self.executor.add_state_machines_dbtx(dbtx, states).await
712 }
713
714 pub async fn get_active_operations(&self) -> HashSet<OperationId> {
716 let active_states = self.executor.get_active_states().await;
717 let mut active_operations = HashSet::with_capacity(active_states.len());
718 let mut dbtx = self.db().begin_transaction_nc().await;
719 for (state, _) in active_states {
720 let operation_id = state.operation_id();
721 if dbtx
722 .get_value(&OperationLogKey { operation_id })
723 .await
724 .is_some()
725 {
726 active_operations.insert(operation_id);
727 }
728 }
729 active_operations
730 }
731
732 pub fn operation_log(&self) -> &OperationLog {
733 &self.operation_log
734 }
735
736 pub fn meta_service(&self) -> &Arc<MetaService> {
738 &self.meta_service
739 }
740
741 pub async fn get_meta_expiration_timestamp(&self) -> Option<SystemTime> {
743 let meta_service = self.meta_service();
744 let ts = meta_service
745 .get_field::<u64>(self.db(), "federation_expiry_timestamp")
746 .await
747 .and_then(|v| v.value)?;
748 Some(UNIX_EPOCH + Duration::from_secs(ts))
749 }
750
751 async fn finalize_transaction(
753 &self,
754 dbtx: &mut DatabaseTransaction<'_>,
755 operation_id: OperationId,
756 mut partial_transaction: TransactionBuilder,
757 ) -> Result<FinalizedTransaction, TransactionSubmitError> {
758 let (in_amounts, out_amounts) = self.transaction_builder_get_balance(&partial_transaction);
759
760 let mut added_inputs_bundles = vec![];
761 let mut added_outputs_bundles = vec![];
762
763 for unit in in_amounts.units().union(&out_amounts.units()) {
774 let input_amount = in_amounts.get(unit).copied().unwrap_or_default();
775 let output_amount = out_amounts.get(unit).copied().unwrap_or_default();
776 if input_amount == output_amount {
777 continue;
778 }
779
780 let Some((module_id, module)) = self.primary_module_for_unit(*unit) else {
781 return Err(TransactionSubmitError::NoPrimaryModule { unit: *unit });
782 };
783
784 let (added_input_bundle, added_output_bundle) = module
785 .create_final_inputs_and_outputs(
786 module_id,
787 dbtx,
788 operation_id,
789 *unit,
790 input_amount,
791 output_amount,
792 )
793 .await?;
794
795 added_inputs_bundles.push(added_input_bundle);
796 added_outputs_bundles.push(added_output_bundle);
797 }
798
799 let change_range = Range {
803 start: partial_transaction.outputs().count() as u64,
804 end: (partial_transaction.outputs().count() as u64
805 + added_outputs_bundles
806 .iter()
807 .map(|output| output.outputs().len() as u64)
808 .sum::<u64>()),
809 };
810
811 for added_inputs in added_inputs_bundles {
812 partial_transaction = partial_transaction.with_inputs(added_inputs);
813 }
814
815 for added_outputs in added_outputs_bundles {
816 partial_transaction = partial_transaction.with_outputs(added_outputs);
817 }
818
819 let (input_amounts, output_amounts) =
820 self.transaction_builder_get_balance(&partial_transaction);
821
822 for (unit, output_amount) in output_amounts {
823 let input_amount = input_amounts.get(&unit).copied().unwrap_or_default();
824
825 assert!(input_amount >= output_amount, "Transaction is underfunded");
826 }
827
828 let fees = {
832 let mut input_total = Amounts::ZERO;
833 for input in partial_transaction.inputs() {
834 input_total
835 .checked_add_mut(&input.amounts)
836 .expect("Own transaction amounts don't overflow");
837 }
838 let mut output_total = Amounts::ZERO;
839 for output in partial_transaction.outputs() {
840 output_total
841 .checked_add_mut(&output.amounts)
842 .expect("Own transaction amounts don't overflow");
843 }
844 input_total
845 .checked_sub(&output_total)
846 .expect("Inputs >= outputs for own transactions")
847 };
848
849 let (transaction, states) = partial_transaction.build(&self.secp_ctx, thread_rng());
850
851 Ok(FinalizedTransaction {
852 transaction,
853 states,
854 change_range,
855 fees,
856 })
857 }
858
859 pub async fn fee_quote(
879 &self,
880 operation_id: OperationId,
881 request: FeeQuoteRequest,
882 ) -> Result<FeeQuote, TransactionSubmitError> {
883 let FeeQuoteRequest {
884 input_amount,
885 output_amount,
886 input_fee,
887 output_fee,
888 } = request;
889
890 let mut gross_input = input_amount.clone();
894 let mut gross_output = output_amount.clone();
895 let mut input_fees = input_fee.clone();
896 let mut output_fees = output_fee.clone();
897
898 let balance_input = input_amount;
904 let balance_output = output_amount
905 .checked_add(&input_fee)
906 .and_then(|amounts| amounts.checked_add(&output_fee))
907 .expect("explicit amounts and fees cannot overflow an Amounts");
908
909 let mut dbtx = self.db.begin_transaction_nc().await;
913
914 for unit in balance_input.units().union(&balance_output.units()) {
917 let balance_input_amount = balance_input.get(unit).copied().unwrap_or_default();
918 let balance_output_amount = balance_output.get(unit).copied().unwrap_or_default();
919 if balance_input_amount == balance_output_amount {
920 continue;
921 }
922
923 let Some((module_id, module)) = self.primary_module_for_unit(*unit) else {
924 return Err(TransactionSubmitError::NoPrimaryModule { unit: *unit });
925 };
926
927 let (change_input, change_output) = module
928 .create_final_inputs_and_outputs(
929 module_id,
930 &mut dbtx.to_ref_nc(),
931 operation_id,
932 *unit,
933 balance_input_amount,
934 balance_output_amount,
935 )
936 .await?;
937
938 for input in change_input.inputs() {
944 let module = self.get_module(input.input.module_instance_id());
945 let fee = module
946 .input_fee(&input.amounts, &input.input)
947 .expect("Primary module must know its own change input fees");
948 gross_input.checked_add_mut(&input.amounts);
949 input_fees.checked_add_mut(&fee);
950 }
951
952 for output in change_output.outputs() {
953 let module = self.get_module(output.output.module_instance_id());
954 let fee = module
955 .output_fee(&output.amounts, &output.output)
956 .expect("Primary module must know its own change output fees");
957 gross_output.checked_add_mut(&output.amounts);
958 output_fees.checked_add_mut(&fee);
959 }
960 }
961
962 dbtx.ignore_uncommitted();
965
966 let mut dust = Amounts::ZERO;
971 for unit in gross_input.units().union(&gross_output.units()) {
972 let total = gross_input
973 .get(unit)
974 .copied()
975 .unwrap_or_default()
976 .saturating_sub(gross_output.get(unit).copied().unwrap_or_default());
977 let fees = input_fees.get(unit).copied().unwrap_or_default()
978 + output_fees.get(unit).copied().unwrap_or_default();
979 dust = dust
980 .checked_add_unit(total.saturating_sub(fees), *unit)
981 .expect("dust cannot overflow an Amounts");
982 }
983
984 Ok(FeeQuote {
985 input: input_fees,
986 output: output_fees,
987 dust,
988 })
989 }
990
991 pub async fn finalize_and_submit_transaction<F, M>(
1025 &self,
1026 operation_id: OperationId,
1027 operation_type: &str,
1028 operation_meta_gen: F,
1029 tx_builder: TransactionBuilder,
1030 ) -> Result<OutPointRange, TransactionSubmitError>
1031 where
1032 F: Fn(OutPointRange) -> M + Clone + MaybeSend + MaybeSync,
1033 M: serde::Serialize + MaybeSend,
1034 {
1035 let operation_type = operation_type.to_owned();
1036
1037 let autocommit_res = self
1038 .db
1039 .autocommit(
1040 |dbtx, _| {
1041 let operation_type = operation_type.clone();
1042 let tx_builder = tx_builder.clone();
1043 let operation_meta_gen = operation_meta_gen.clone();
1044 Box::pin(async move {
1045 self.finalize_and_submit_transaction_dbtx(
1046 dbtx,
1047 operation_id,
1048 &operation_type,
1049 operation_meta_gen,
1050 tx_builder,
1051 )
1052 .await
1053 })
1054 },
1055 Some(100), )
1057 .await;
1058
1059 match autocommit_res {
1060 Ok(txid) => Ok(txid),
1061 Err(AutocommitError::ClosureError { error, .. }) => Err(error),
1062 Err(AutocommitError::CommitFailed { last_error, .. }) => {
1063 Err(TransactionSubmitError::Database(last_error))
1064 }
1065 }
1066 }
1067
1068 pub async fn finalize_and_submit_transaction_dbtx<F, M>(
1077 &self,
1078 dbtx: &mut DatabaseTransaction<'_>,
1079 operation_id: OperationId,
1080 operation_type: &str,
1081 operation_meta_gen: F,
1082 tx_builder: TransactionBuilder,
1083 ) -> Result<OutPointRange, TransactionSubmitError>
1084 where
1085 F: FnOnce(OutPointRange) -> M + MaybeSend,
1086 M: serde::Serialize + MaybeSend,
1087 {
1088 if Client::operation_exists_dbtx(dbtx, operation_id).await {
1089 return Err(OperationAlreadyExistsError { operation_id }.into());
1090 }
1091
1092 let out_point_range = self
1093 .finalize_and_submit_transaction_inner(dbtx, operation_id, tx_builder)
1094 .await?;
1095
1096 self.operation_log()
1097 .add_operation_log_entry_dbtx(
1098 dbtx,
1099 operation_id,
1100 operation_type,
1101 operation_meta_gen(out_point_range),
1102 )
1103 .await;
1104
1105 Ok(out_point_range)
1106 }
1107
1108 async fn finalize_and_submit_transaction_inner(
1109 &self,
1110 dbtx: &mut DatabaseTransaction<'_>,
1111 operation_id: OperationId,
1112 tx_builder: TransactionBuilder,
1113 ) -> Result<OutPointRange, TransactionSubmitError> {
1114 let FinalizedTransaction {
1115 transaction,
1116 mut states,
1117 change_range,
1118 fees,
1119 } = self
1120 .finalize_transaction(&mut dbtx.to_ref_nc(), operation_id, tx_builder)
1121 .await?;
1122
1123 if transaction.consensus_encode_to_vec().len() > Transaction::MAX_TX_SIZE {
1124 let inputs = transaction
1125 .inputs
1126 .iter()
1127 .map(DynInput::module_instance_id)
1128 .collect::<Vec<_>>();
1129 let outputs = transaction
1130 .outputs
1131 .iter()
1132 .map(DynOutput::module_instance_id)
1133 .collect::<Vec<_>>();
1134 warn!(
1135 target: LOG_CLIENT_NET_API,
1136 size=%transaction.consensus_encode_to_vec().len(),
1137 ?inputs,
1138 ?outputs,
1139 "Transaction too large",
1140 );
1141 debug!(target: LOG_CLIENT_NET_API, ?transaction, "transaction details");
1142 return Err(TransactionSubmitError::TransactionTooLarge {
1143 size: transaction.consensus_encode_to_vec().len(),
1144 max: Transaction::MAX_TX_SIZE,
1145 });
1146 }
1147
1148 let txid = transaction.tx_hash();
1149
1150 debug!(
1151 target: LOG_CLIENT_NET_API,
1152 %txid,
1153 operation_id = %operation_id.fmt_short(),
1154 ?transaction,
1155 "Finalized and submitting transaction",
1156 );
1157
1158 let tx_submission_sm = DynState::from_typed(
1159 TRANSACTION_SUBMISSION_MODULE_INSTANCE,
1160 TxSubmissionStatesSM {
1161 operation_id,
1162 state: TxSubmissionStates::Created(transaction),
1163 },
1164 );
1165 states.push(tx_submission_sm);
1166
1167 self.executor.add_state_machines_dbtx(dbtx, states).await?;
1168
1169 dbtx.insert_new_entry(&TransactionFeesKey(txid), &fees)
1170 .await;
1171
1172 self.log_event_dbtx(dbtx, None, TxCreatedEvent { txid, operation_id })
1173 .await;
1174
1175 Ok(OutPointRange::new(txid, IdxRange::from(change_range)))
1176 }
1177
1178 async fn transaction_update_stream(
1179 &self,
1180 operation_id: OperationId,
1181 ) -> BoxStream<'static, TxSubmissionStatesSM> {
1182 self.executor
1183 .notifier()
1184 .module_notifier::<TxSubmissionStatesSM>(
1185 TRANSACTION_SUBMISSION_MODULE_INSTANCE,
1186 self.final_client.clone(),
1187 )
1188 .subscribe(operation_id)
1189 .await
1190 }
1191
1192 pub async fn operation_exists(&self, operation_id: OperationId) -> bool {
1193 let mut dbtx = self.db().begin_transaction_nc().await;
1194
1195 Client::operation_exists_dbtx(&mut dbtx, operation_id).await
1196 }
1197
1198 pub async fn operation_exists_dbtx(
1199 dbtx: &mut DatabaseTransaction<'_>,
1200 operation_id: OperationId,
1201 ) -> bool {
1202 let active_state_exists = dbtx
1203 .find_by_prefix(&ActiveOperationStateKeyPrefix { operation_id })
1204 .await
1205 .next()
1206 .await
1207 .is_some();
1208
1209 let inactive_state_exists = dbtx
1210 .find_by_prefix(&InactiveOperationStateKeyPrefix { operation_id })
1211 .await
1212 .next()
1213 .await
1214 .is_some();
1215
1216 active_state_exists || inactive_state_exists
1217 }
1218
1219 pub async fn has_active_states(&self, operation_id: OperationId) -> bool {
1221 self.executor.has_active_states(operation_id).await
1222 }
1223
1224 pub async fn await_no_active_states(&self, operation_id: OperationId) {
1244 self.executor.await_no_active_states(operation_id).await;
1245 }
1246
1247 pub async fn get_operation_fees(
1266 &self,
1267 operation_id: OperationId,
1268 ) -> Result<Option<Amounts>, OperationNotFoundError> {
1269 if !self.operation_exists(operation_id).await {
1270 return Err(OperationNotFoundError { operation_id });
1271 }
1272
1273 let (active_states, inactive_states) =
1274 self.executor().get_operation_states(operation_id).await;
1275
1276 let states = active_states
1277 .into_iter()
1278 .map(|(state, _)| state)
1279 .chain(inactive_states.into_iter().map(|(state, _)| state));
1280
1281 let accepted_transactions = states
1282 .filter_map(|state| {
1283 let tx_state = state.as_any().downcast_ref::<TxSubmissionStatesSM>()?;
1284
1285 match &tx_state.state {
1286 TxSubmissionStates::Accepted(transaction_id) => Some(*transaction_id),
1287 _ => None,
1288 }
1289 })
1290 .collect::<HashSet<_>>();
1291
1292 let mut dbtx = self.db.begin_transaction_nc().await;
1294 let mut total_fees = Amounts::ZERO;
1295 for txid in &accepted_transactions {
1296 let Some(fees) = dbtx.get_value(&TransactionFeesKey(*txid)).await else {
1297 return Ok(None);
1298 };
1299 total_fees = total_fees
1300 .checked_add(&fees)
1301 .expect("Fee amounts don't overflow in practice");
1302 }
1303
1304 Ok(Some(total_fees))
1305 }
1306
1307 pub async fn await_primary_bitcoin_module_output(
1310 &self,
1311 operation_id: OperationId,
1312 out_point: OutPoint,
1313 ) -> Result<(), TransactionSubmitError> {
1314 self.primary_module_for_unit(AmountUnit::BITCOIN)
1315 .ok_or(TransactionSubmitError::NoPrimaryModule {
1316 unit: AmountUnit::BITCOIN,
1317 })?
1318 .1
1319 .await_primary_module_output(operation_id, out_point)
1320 .await
1321 .map_err(TransactionSubmitError::PrimaryModule)
1322 }
1323
1324 pub fn get_first_module<M: ClientModule>(
1326 &'_ self,
1327 ) -> Result<ClientModuleInstance<'_, M>, ModuleLookupError> {
1328 let module_kind = M::kind();
1329 let id = self.get_first_instance(&module_kind).ok_or_else(|| {
1330 ModuleLookupError::NoModuleOfKind {
1331 kind: module_kind.clone(),
1332 }
1333 })?;
1334 let module: &M = self
1335 .try_get_module(id)
1336 .ok_or(ModuleLookupError::UnknownInstance { instance_id: id })?
1337 .as_any()
1338 .downcast_ref::<M>()
1339 .ok_or(ModuleLookupError::WrongModuleType {
1340 instance_id: id,
1341 expected: std::any::type_name::<M>(),
1342 })?;
1343 let (db, _) = self.db().with_prefix_module_id(id);
1344 Ok(ClientModuleInstance {
1345 id,
1346 db,
1347 api: self.api().with_module(id),
1348 module,
1349 })
1350 }
1351
1352 pub fn get_module_client_dyn(
1353 &self,
1354 instance_id: ModuleInstanceId,
1355 ) -> Result<&maybe_add_send_sync!(dyn IClientModule), ModuleLookupError> {
1356 self.try_get_module(instance_id)
1357 .ok_or(ModuleLookupError::UnknownInstance { instance_id })
1358 }
1359
1360 pub fn db(&self) -> &Database {
1361 &self.db
1362 }
1363
1364 pub fn endpoints(&self) -> &ConnectorRegistry {
1365 &self.connectors
1366 }
1367
1368 pub async fn transaction_updates(&self, operation_id: OperationId) -> TransactionUpdates {
1371 TransactionUpdates {
1372 update_stream: self.transaction_update_stream(operation_id).await,
1373 }
1374 }
1375
1376 pub fn get_first_instance(&self, module_kind: &ModuleKind) -> Option<ModuleInstanceId> {
1378 self.modules
1379 .iter_modules()
1380 .find(|(_, kind, _module)| *kind == module_kind)
1381 .map(|(instance_id, _, _)| instance_id)
1382 }
1383
1384 pub async fn await_primary_bitcoin_module_outputs(
1387 &self,
1388 operation_id: OperationId,
1389 outputs: Vec<OutPoint>,
1390 ) -> Result<(), TransactionSubmitError> {
1391 for out_point in outputs {
1392 self.await_primary_bitcoin_module_output(operation_id, out_point)
1393 .await?;
1394 }
1395
1396 Ok(())
1397 }
1398
1399 pub async fn get_config_json(&self) -> JsonClientConfig {
1405 self.config().await.to_json()
1406 }
1407
1408 #[doc(hidden)]
1411 pub async fn get_balance_for_btc(&self) -> Result<Amount, ModuleLookupError> {
1414 self.get_balance_for_unit(AmountUnit::BITCOIN).await
1415 }
1416
1417 pub async fn get_balance_for_unit(
1418 &self,
1419 unit: AmountUnit,
1420 ) -> Result<Amount, ModuleLookupError> {
1421 let (id, module) = self
1422 .primary_module_for_unit(unit)
1423 .ok_or(ModuleLookupError::NoPrimaryModule { unit })?;
1424 Ok(module
1425 .get_balance(id, &mut self.db().begin_transaction_nc().await, unit)
1426 .await)
1427 }
1428
1429 pub async fn subscribe_balance_changes(&self, unit: AmountUnit) -> BoxStream<'static, Amount> {
1432 let primary_module_things =
1433 if let Some((primary_module_id, primary_module)) = self.primary_module_for_unit(unit) {
1434 let balance_changes = primary_module.subscribe_balance_changes().await;
1435 let initial_balance = self
1436 .get_balance_for_unit(unit)
1437 .await
1438 .expect("Primary is present");
1439
1440 Some((
1441 primary_module_id,
1442 primary_module.clone(),
1443 balance_changes,
1444 initial_balance,
1445 ))
1446 } else {
1447 None
1448 };
1449 let db = self.db().clone();
1450
1451 Box::pin(async_stream::stream! {
1452 let Some((primary_module_id, primary_module, mut balance_changes, initial_balance)) = primary_module_things else {
1453 pending().await
1456 };
1457
1458
1459 yield initial_balance;
1460 let mut prev_balance = initial_balance;
1461 while let Some(()) = balance_changes.next().await {
1462 let mut dbtx = db.begin_transaction_nc().await;
1463 let balance = primary_module
1464 .get_balance(primary_module_id, &mut dbtx, unit)
1465 .await;
1466
1467 if balance != prev_balance {
1469 prev_balance = balance;
1470 yield balance;
1471 }
1472 }
1473 })
1474 }
1475
1476 async fn make_api_version_request(
1481 delay: Duration,
1482 peer_id: PeerId,
1483 api: &DynGlobalApi,
1484 ) -> (
1485 PeerId,
1486 Result<SupportedApiVersionsSummary, fedimint_connectors::error::ServerError>,
1487 ) {
1488 runtime::sleep(delay).await;
1489 (
1490 peer_id,
1491 api.request_single_peer::<SupportedApiVersionsSummary>(
1492 VERSION_ENDPOINT.to_owned(),
1493 ApiRequestErased::default(),
1494 peer_id,
1495 )
1496 .await,
1497 )
1498 }
1499
1500 fn create_api_version_backoff() -> impl Iterator<Item = Duration> {
1506 custom_backoff(Duration::from_millis(200), Duration::from_secs(600), None)
1507 }
1508
1509 pub async fn fetch_common_api_versions_from_all_peers(
1512 num_peers: NumPeers,
1513 api: DynGlobalApi,
1514 db: Database,
1515 num_responses_sender: watch::Sender<usize>,
1516 ) {
1517 let mut backoff = Self::create_api_version_backoff();
1518
1519 let mut requests = FuturesUnordered::new();
1522
1523 for peer_id in num_peers.peer_ids() {
1524 requests.push(Self::make_api_version_request(
1525 Duration::ZERO,
1526 peer_id,
1527 &api,
1528 ));
1529 }
1530
1531 let mut num_responses = 0;
1532
1533 while let Some((peer_id, response)) = requests.next().await {
1534 let retry = match response {
1535 Err(err) => {
1536 let has_previous_response = db
1537 .begin_transaction_nc()
1538 .await
1539 .get_value(&PeerLastApiVersionsSummaryKey(peer_id))
1540 .await
1541 .is_some();
1542 debug!(
1543 target: LOG_CLIENT,
1544 %peer_id,
1545 err = %err.fmt_compact(),
1546 %has_previous_response,
1547 "Failed to refresh API versions of a peer"
1548 );
1549
1550 !has_previous_response
1551 }
1552 Ok(o) => {
1553 let mut dbtx = db.begin_transaction().await;
1556 dbtx.insert_entry(
1557 &PeerLastApiVersionsSummaryKey(peer_id),
1558 &PeerLastApiVersionsSummary(o),
1559 )
1560 .await;
1561 dbtx.commit_tx().await;
1562 false
1563 }
1564 };
1565
1566 if retry {
1567 requests.push(Self::make_api_version_request(
1568 backoff.next().expect("Keeps retrying"),
1569 peer_id,
1570 &api,
1571 ));
1572 } else {
1573 num_responses += 1;
1574 num_responses_sender.send_replace(num_responses);
1575 }
1576 }
1577 }
1578
1579 pub async fn fetch_peers_api_versions_from_threshold_of_peers(
1583 num_peers: NumPeers,
1584 api: DynGlobalApi,
1585 ) -> BTreeMap<PeerId, SupportedApiVersionsSummary> {
1586 let mut backoff = Self::create_api_version_backoff();
1587
1588 let mut requests = FuturesUnordered::new();
1591
1592 for peer_id in num_peers.peer_ids() {
1593 requests.push(Self::make_api_version_request(
1594 Duration::ZERO,
1595 peer_id,
1596 &api,
1597 ));
1598 }
1599
1600 let mut successful_responses = BTreeMap::new();
1601
1602 while successful_responses.len() < num_peers.threshold()
1603 && let Some((peer_id, response)) = requests.next().await
1604 {
1605 let retry = match response {
1606 Err(err) => {
1607 debug!(
1608 target: LOG_CLIENT,
1609 %peer_id,
1610 err = %err.fmt_compact(),
1611 "Failed to fetch API versions from peer"
1612 );
1613 true
1614 }
1615 Ok(response) => {
1616 successful_responses.insert(peer_id, response);
1617 false
1618 }
1619 };
1620
1621 if retry {
1622 requests.push(Self::make_api_version_request(
1623 backoff.next().expect("Keeps retrying"),
1624 peer_id,
1625 &api,
1626 ));
1627 }
1628 }
1629
1630 successful_responses
1631 }
1632
1633 pub async fn fetch_common_api_versions(
1635 config: &ClientConfig,
1636 api: &DynGlobalApi,
1637 ) -> BTreeMap<PeerId, SupportedApiVersionsSummary> {
1638 debug!(
1639 target: LOG_CLIENT,
1640 "Fetching common api versions"
1641 );
1642
1643 let num_peers = NumPeers::from(config.global.api_endpoints.len());
1644
1645 Self::fetch_peers_api_versions_from_threshold_of_peers(num_peers, api.clone()).await
1646 }
1647
1648 pub async fn write_api_version_cache(
1652 dbtx: &mut DatabaseTransaction<'_>,
1653 api_version_set: ApiVersionSet,
1654 ) {
1655 debug!(
1656 target: LOG_CLIENT,
1657 value = ?api_version_set,
1658 "Writing API version set to cache"
1659 );
1660
1661 dbtx.insert_entry(
1662 &CachedApiVersionSetKey,
1663 &CachedApiVersionSet(api_version_set),
1664 )
1665 .await;
1666 }
1667
1668 pub async fn store_prefetched_api_versions(
1673 db: &Database,
1674 config: &ClientConfig,
1675 client_module_init: &ClientModuleInitRegistry,
1676 peer_api_versions: &BTreeMap<PeerId, SupportedApiVersionsSummary>,
1677 ) {
1678 debug!(
1679 target: LOG_CLIENT,
1680 "Storing {} prefetched peer API version responses and calculating common version set",
1681 peer_api_versions.len()
1682 );
1683
1684 let mut dbtx = db.begin_transaction().await;
1685 let client_supported_versions =
1687 Self::supported_api_versions_summary_static(config, client_module_init);
1688 match fedimint_client_module::api_version_discovery::discover_common_api_versions_set(
1689 &client_supported_versions,
1690 peer_api_versions,
1691 ) {
1692 Ok(common_api_versions) => {
1693 Self::write_api_version_cache(&mut dbtx.to_ref_nc(), common_api_versions).await;
1695 debug!(target: LOG_CLIENT, "Calculated and stored common API version set");
1696 }
1697 Err(err) => {
1698 debug!(target: LOG_CLIENT, err = %err.fmt_compact(), "Failed to calculate common API versions from prefetched data");
1699 }
1700 }
1701
1702 for (peer_id, peer_api_versions) in peer_api_versions {
1704 dbtx.insert_entry(
1705 &PeerLastApiVersionsSummaryKey(*peer_id),
1706 &PeerLastApiVersionsSummary(peer_api_versions.clone()),
1707 )
1708 .await;
1709 }
1710 dbtx.commit_tx().await;
1711 debug!(target: LOG_CLIENT, "Stored individual peer API version responses");
1712 }
1713
1714 pub fn supported_api_versions_summary_static(
1716 config: &ClientConfig,
1717 client_module_init: &ClientModuleInitRegistry,
1718 ) -> SupportedApiVersionsSummary {
1719 SupportedApiVersionsSummary {
1720 core: SupportedCoreApiVersions {
1721 core_consensus: config.global.consensus_version,
1722 api: MultiApiVersion::try_from_iter(SUPPORTED_CORE_API_VERSIONS.to_owned())
1723 .expect("must not have conflicting versions"),
1724 },
1725 modules: config
1726 .modules
1727 .iter()
1728 .filter_map(|(&module_instance_id, module_config)| {
1729 client_module_init
1730 .get(module_config.kind())
1731 .map(|module_init| {
1732 (
1733 module_instance_id,
1734 SupportedModuleApiVersions {
1735 core_consensus: config.global.consensus_version,
1736 module_consensus: module_config.version,
1737 api: module_init.supported_api_versions(),
1738 },
1739 )
1740 })
1741 })
1742 .collect(),
1743 }
1744 }
1745
1746 pub async fn load_and_refresh_common_api_version(
1747 &self,
1748 ) -> Result<ApiVersionSet, ApiVersionDiscoveryError> {
1749 Self::load_and_refresh_common_api_version_static(
1750 &self.config().await,
1751 &self.module_inits,
1752 self.connectors.clone(),
1753 &self.api,
1754 &self.db,
1755 &self.task_group,
1756 &self.client_span,
1757 )
1758 .await
1759 }
1760
1761 pub async fn refresh_api_versions(&self) -> Result<ApiVersionSet, ApiVersionDiscoveryError> {
1767 Self::refresh_common_api_version_static(
1768 &self.config().await,
1769 &self.module_inits,
1770 &self.api,
1771 &self.db,
1772 self.task_group.clone(),
1773 &self.client_span,
1774 true,
1775 )
1776 .await
1777 }
1778
1779 pub(crate) async fn load_and_refresh_common_api_version_static(
1785 config: &ClientConfig,
1786 module_init: &ClientModuleInitRegistry,
1787 connectors: ConnectorRegistry,
1788 api: &DynGlobalApi,
1789 db: &Database,
1790 task_group: &TaskGroup,
1791 client_span: &Span,
1792 ) -> Result<ApiVersionSet, ApiVersionDiscoveryError> {
1793 if let Some(v) = db
1794 .begin_transaction_nc()
1795 .await
1796 .get_value(&CachedApiVersionSetKey)
1797 .await
1798 {
1799 client_span.in_scope(|| {
1800 debug!(
1801 target: LOG_CLIENT,
1802 "Found existing cached common api versions"
1803 );
1804 });
1805 let config = config.clone();
1806 let client_module_init = module_init.clone();
1807 let api = api.clone();
1808 let db = db.clone();
1809 let task_group = task_group.clone();
1810 let client_span_owned = client_span.clone();
1811 task_group.clone().spawn_cancellable_with_span(
1814 client_span.clone(),
1815 "refresh_common_api_version_static",
1816 async move {
1817 connectors.wait_for_initialized_connections().await;
1818
1819 if let Err(error) = Self::refresh_common_api_version_static(
1820 &config,
1821 &client_module_init,
1822 &api,
1823 &db,
1824 task_group,
1825 &client_span_owned,
1826 false,
1827 )
1828 .await
1829 {
1830 warn!(
1831 target: LOG_CLIENT,
1832 err = %error.fmt_compact(), "Failed to discover common api versions"
1833 );
1834 }
1835 },
1836 );
1837
1838 return Ok(v.0);
1839 }
1840
1841 info!(
1842 target: LOG_CLIENT,
1843 "Fetching initial API versions "
1844 );
1845 Self::refresh_common_api_version_static(
1846 config,
1847 module_init,
1848 api,
1849 db,
1850 task_group.clone(),
1851 client_span,
1852 true,
1853 )
1854 .await
1855 }
1856
1857 async fn refresh_common_api_version_static(
1858 config: &ClientConfig,
1859 client_module_init: &ClientModuleInitRegistry,
1860 api: &DynGlobalApi,
1861 db: &Database,
1862 task_group: TaskGroup,
1863 client_span: &Span,
1864 block_until_ok: bool,
1865 ) -> Result<ApiVersionSet, ApiVersionDiscoveryError> {
1866 debug!(
1867 target: LOG_CLIENT,
1868 "Refreshing common api versions"
1869 );
1870
1871 let (num_responses_sender, mut num_responses_receiver) = tokio::sync::watch::channel(0);
1872 let num_peers = NumPeers::from(config.global.api_endpoints.len());
1873
1874 task_group.spawn_cancellable_with_span(
1875 client_span.clone(),
1876 "refresh peers api versions",
1877 Client::fetch_common_api_versions_from_all_peers(
1878 num_peers,
1879 api.clone(),
1880 db.clone(),
1881 num_responses_sender,
1882 ),
1883 );
1884
1885 let common_api_versions = loop {
1886 let _: Result<_, Elapsed> = runtime::timeout(
1894 Duration::from_secs(30),
1895 num_responses_receiver.wait_for(|num| num_peers.threshold() <= *num),
1896 )
1897 .await;
1898
1899 let peer_api_version_sets = Self::load_peers_last_api_versions(db, num_peers).await;
1900
1901 match fedimint_client_module::api_version_discovery::discover_common_api_versions_set(
1902 &Self::supported_api_versions_summary_static(config, client_module_init),
1903 &peer_api_version_sets,
1904 ) {
1905 Ok(o) => break o,
1906 Err(err) if block_until_ok => {
1907 warn!(
1908 target: LOG_CLIENT,
1909 err = %err.fmt_compact(),
1910 "Failed to discover API version to use. Retrying..."
1911 );
1912 continue;
1913 }
1914 Err(e) => return Err(e),
1915 }
1916 };
1917
1918 debug!(
1919 target: LOG_CLIENT,
1920 value = ?common_api_versions,
1921 "Updating the cached common api versions"
1922 );
1923 let mut dbtx = db.begin_transaction().await;
1924 let _ = dbtx
1925 .insert_entry(
1926 &CachedApiVersionSetKey,
1927 &CachedApiVersionSet(common_api_versions.clone()),
1928 )
1929 .await;
1930
1931 dbtx.commit_tx().await;
1932
1933 Ok(common_api_versions)
1934 }
1935
1936 pub async fn get_metadata(&self) -> Metadata {
1938 self.db
1939 .begin_transaction_nc()
1940 .await
1941 .get_value(&ClientMetadataKey)
1942 .await
1943 .unwrap_or_else(|| {
1944 warn!(
1945 target: LOG_CLIENT,
1946 "Missing existing metadata. This key should have been set on Client init"
1947 );
1948 Metadata::empty()
1949 })
1950 }
1951
1952 pub async fn set_metadata(&self, metadata: &Metadata) {
1954 self.db
1955 .autocommit::<_, _, Infallible>(
1956 |dbtx, _| {
1957 Box::pin(async {
1958 Self::set_metadata_dbtx(dbtx, metadata).await;
1959 Ok(())
1960 })
1961 },
1962 None,
1963 )
1964 .await
1965 .expect("Failed to autocommit metadata");
1966 }
1967
1968 pub fn has_pending_recoveries(&self) -> bool {
1969 !self
1970 .client_recovery_status_receiver
1971 .borrow()
1972 .values()
1973 .all(RecoveryStatus::is_successfully_done)
1974 }
1975
1976 pub fn all_modules_usable(&self) -> bool {
1987 self.client_recovery_status_receiver
1988 .borrow()
1989 .keys()
1990 .all(|module_instance_id| self.modules.get(*module_instance_id).is_some())
1991 }
1992
1993 pub async fn wait_for_all_recoveries(&self) -> Result<(), RecoveryError> {
2007 Self::wait_for_recoveries(
2008 self.client_recovery_status_receiver.clone(),
2009 |_module_instance_id| true,
2010 )
2011 .await
2012 }
2013
2014 async fn wait_for_recoveries(
2023 mut status_receiver: watch::Receiver<BTreeMap<ModuleInstanceId, RecoveryStatus>>,
2024 module_filter: impl Fn(ModuleInstanceId) -> bool,
2025 ) -> Result<(), RecoveryError> {
2026 let failure = status_receiver
2027 .wait_for(|statuses| {
2028 let matching = || {
2029 statuses
2030 .iter()
2031 .filter(|(module_instance_id, _status)| module_filter(**module_instance_id))
2032 .map(|(_module_instance_id, status)| status)
2033 };
2034
2035 matching().any(|status| matches!(status, RecoveryStatus::Failed { .. }))
2039 || matching().all(RecoveryStatus::is_successfully_done)
2040 })
2041 .await
2042 .map_err(|_closed| RecoveryError::ClientStopped)?
2043 .iter()
2046 .find_map(|(module_instance_id, status)| match status {
2047 RecoveryStatus::Failed { error, .. } if module_filter(*module_instance_id) => {
2048 Some((*module_instance_id, error.clone()))
2049 }
2050 _ => None,
2051 });
2052
2053 match failure {
2054 Some((module_instance_id, source)) => Err(RecoveryError::Failed {
2055 module_instance_id,
2056 source,
2057 }),
2058 None => Ok(()),
2059 }
2060 }
2061
2062 pub fn subscribe_to_recovery_progress(
2072 &self,
2073 ) -> impl Stream<Item = (ModuleInstanceId, RecoveryProgress)> + use<> {
2074 WatchStream::new(self.client_recovery_status_receiver.clone()).flat_map(|statuses| {
2075 futures::stream::iter(
2076 statuses
2077 .into_iter()
2078 .map(|(module_instance_id, status)| (module_instance_id, status.progress())),
2079 )
2080 })
2081 }
2082
2083 pub async fn wait_for_module_kind_recovery(
2092 &self,
2093 module_kind: ModuleKind,
2094 ) -> Result<(), RecoveryError> {
2095 let config = self.config().await;
2096 Self::wait_for_recoveries(
2097 self.client_recovery_status_receiver.clone(),
2098 move |module_instance_id| {
2099 config
2100 .modules
2101 .get(&module_instance_id)
2102 .is_some_and(|module| module.kind == module_kind)
2103 },
2104 )
2105 .await
2106 }
2107
2108 pub async fn wait_for_all_active_state_machines(&self) {
2109 loop {
2110 if self.executor.get_active_states().await.is_empty() {
2111 break;
2112 }
2113 sleep(Duration::from_millis(100)).await;
2114 }
2115 }
2116
2117 pub async fn set_metadata_dbtx(dbtx: &mut DatabaseTransaction<'_>, metadata: &Metadata) {
2119 dbtx.insert_new_entry(&ClientMetadataKey, metadata).await;
2120 }
2121
2122 fn spawn_module_recoveries_task(
2123 &self,
2124 recovery_sender: watch::Sender<BTreeMap<ModuleInstanceId, RecoveryStatus>>,
2125 module_recoveries: BTreeMap<ModuleInstanceId, ModuleRecoveryFuture>,
2126 module_recovery_progress_receivers: BTreeMap<
2127 ModuleInstanceId,
2128 watch::Receiver<RecoveryProgress>,
2129 >,
2130 module_kinds: BTreeMap<ModuleInstanceId, ModuleKind>,
2133 ) {
2134 let db = self.db.clone();
2135 let log_ordering_wakeup_tx = self.log_ordering_wakeup_tx.clone();
2136 self.spawn("module recoveries", |_task_handle| async {
2141 Self::run_module_recoveries_task(
2142 db,
2143 log_ordering_wakeup_tx,
2144 recovery_sender,
2145 module_recoveries,
2146 module_recovery_progress_receivers,
2147 module_kinds,
2148 )
2149 .await;
2150 });
2151 }
2152
2153 async fn run_module_recoveries_task(
2154 db: Database,
2155 log_ordering_wakeup_tx: watch::Sender<()>,
2156 recovery_sender: watch::Sender<BTreeMap<ModuleInstanceId, RecoveryStatus>>,
2157 module_recoveries: BTreeMap<ModuleInstanceId, ModuleRecoveryFuture>,
2158 module_recovery_progress_receivers: BTreeMap<
2159 ModuleInstanceId,
2160 watch::Receiver<RecoveryProgress>,
2161 >,
2162 module_kinds: BTreeMap<ModuleInstanceId, ModuleKind>,
2163 ) {
2164 debug!(target: LOG_CLIENT_RECOVERY, num_modules=%module_recovery_progress_receivers.len(), "Staring module recoveries");
2165
2166 enum RecoveryUpdate {
2170 Progress(RecoveryProgress),
2171 Completed(Option<Amount>),
2172 }
2173
2174 let mut completed_stream = Vec::new();
2175 let progress_stream = futures::stream::FuturesUnordered::new();
2176
2177 for (module_instance_id, f) in module_recoveries {
2178 let recovery_sender = recovery_sender.clone();
2179 completed_stream.push(futures::stream::once(Box::pin(async move {
2180 match f.await {
2181 Ok(amount) => (module_instance_id, RecoveryUpdate::Completed(amount)),
2182 Err(err) => {
2183 warn!(
2184 target: LOG_CLIENT,
2185 err = %err.fmt_compact(), module_instance_id, "Module recovery failed"
2186 );
2187 let error = Arc::new(err);
2188 recovery_sender.send_modify(|statuses| {
2195 let last_progress = statuses
2196 .get(&module_instance_id)
2197 .expect("existing status must be present")
2198 .progress();
2199 statuses.insert(
2200 module_instance_id,
2201 RecoveryStatus::Failed {
2202 last_progress,
2203 error,
2204 },
2205 );
2206 });
2207 futures::future::pending::<()>().await;
2217 unreachable!()
2218 }
2219 }
2220 })));
2221 }
2222
2223 for (module_instance_id, rx) in module_recovery_progress_receivers {
2224 progress_stream.push(
2225 tokio_stream::wrappers::WatchStream::new(rx)
2226 .fuse()
2227 .map(move |progress| (module_instance_id, RecoveryUpdate::Progress(progress))),
2228 );
2229 }
2230
2231 let mut futures = futures::stream::select(
2232 futures::stream::select_all(progress_stream),
2233 futures::stream::select_all(completed_stream),
2234 );
2235
2236 while let Some((module_instance_id, update)) = futures.next().await {
2237 let prev_status = recovery_sender
2241 .borrow()
2242 .get(&module_instance_id)
2243 .expect("existing status must be present")
2244 .clone();
2245
2246 if matches!(prev_status, RecoveryStatus::Failed { .. }) {
2256 debug!(
2257 target: LOG_CLIENT_RECOVERY,
2258 module_instance_id,
2259 "Ignoring a recovery update of a module whose recovery already failed"
2260 );
2261 continue;
2262 }
2263
2264 let prev_progress = prev_status.progress();
2265
2266 if let RecoveryUpdate::Progress(progress) = &update {
2272 if progress.is_done() {
2273 warn!(
2274 target: LOG_CLIENT_RECOVERY,
2275 module_instance_id,
2276 "Module bypassed the sanctioned recovery progress reporting API and reported a completed recovery progress. Ignoring"
2277 );
2278 continue;
2279 }
2280
2281 if progress.is_none() && !prev_progress.is_none() && !prev_progress.is_done() {
2288 warn!(
2289 target: LOG_CLIENT_RECOVERY,
2290 module_instance_id,
2291 "Module bypassed the sanctioned recovery progress reporting API and reported a none recovery progress, regressing its previous one. Ignoring"
2292 );
2293 continue;
2294 }
2295 }
2296
2297 let mut dbtx = db.begin_transaction().await;
2298
2299 let (progress, recovered_amount) = if prev_progress.is_done() {
2305 (prev_progress, None)
2307 } else {
2308 match update {
2309 RecoveryUpdate::Progress(progress) => (progress, None),
2310 RecoveryUpdate::Completed(amount) => (prev_progress.to_complete(), amount),
2311 }
2312 };
2313
2314 if !prev_progress.is_done() && progress.is_done() {
2315 info!(
2316 target: LOG_CLIENT,
2317 module_instance_id,
2318 progress = format!("{}/{}", progress.complete, progress.total),
2319 amount = ?recovered_amount,
2320 "Recovery complete"
2321 );
2322 dbtx.log_event(
2323 log_ordering_wakeup_tx.clone(),
2324 None,
2325 ModuleRecoveryCompleted {
2326 module_id: module_instance_id,
2327 kind: module_kinds.get(&module_instance_id).cloned(),
2328 amount: recovered_amount,
2329 },
2330 )
2331 .await;
2332 } else {
2333 info!(
2334 target: LOG_CLIENT,
2335 module_instance_id,
2336 kind = ?module_kinds.get(&module_instance_id),
2337 progress = format!("{}/{}", progress.complete, progress.total),
2338 "Recovery progress"
2339 );
2340 }
2341
2342 dbtx.insert_entry(
2343 &ClientModuleRecovery { module_instance_id },
2344 &ClientModuleRecoveryState { progress },
2345 )
2346 .await;
2347 dbtx.commit_tx().await;
2348
2349 recovery_sender.send_modify(|statuses| {
2350 statuses.insert(module_instance_id, RecoveryStatus::InProgress(progress));
2351 });
2352 }
2353 debug!(target: LOG_CLIENT_RECOVERY, "Recovery executor stopped");
2354 }
2355
2356 async fn load_peers_last_api_versions(
2357 db: &Database,
2358 num_peers: NumPeers,
2359 ) -> BTreeMap<PeerId, SupportedApiVersionsSummary> {
2360 let mut peer_api_version_sets = BTreeMap::new();
2361
2362 let mut dbtx = db.begin_transaction_nc().await;
2363 for peer_id in num_peers.peer_ids() {
2364 if let Some(v) = dbtx
2365 .get_value(&PeerLastApiVersionsSummaryKey(peer_id))
2366 .await
2367 {
2368 peer_api_version_sets.insert(peer_id, v.0);
2369 }
2370 }
2371 drop(dbtx);
2372 peer_api_version_sets
2373 }
2374
2375 pub async fn get_peer_url_announcements(&self) -> BTreeMap<PeerId, SignedApiAnnouncement> {
2378 self.db()
2379 .begin_transaction_nc()
2380 .await
2381 .find_by_prefix(&ApiAnnouncementPrefix)
2382 .await
2383 .map(|(announcement_key, announcement)| (announcement_key.0, announcement))
2384 .collect()
2385 .await
2386 }
2387
2388 pub async fn get_guardian_metadata(
2390 &self,
2391 ) -> BTreeMap<PeerId, fedimint_core::net::guardian_metadata::SignedGuardianMetadata> {
2392 self.db()
2393 .begin_transaction_nc()
2394 .await
2395 .find_by_prefix(&crate::guardian_metadata::GuardianMetadataPrefix)
2396 .await
2397 .map(|(key, metadata)| (key.0, metadata))
2398 .collect()
2399 .await
2400 }
2401
2402 pub async fn peer_availability(&self) -> MergedPeerAvailability {
2406 let reports =
2407 futures::future::join_all(self.api.all_peers().iter().map(|peer| async move {
2408 let report =
2409 runtime::timeout(Duration::from_secs(10), self.api.peer_availability(*peer))
2410 .await;
2411 (*peer, report)
2412 }))
2413 .await
2414 .into_iter()
2415 .filter_map(|(peer, report)| Some((peer, report.ok()?.ok()?)))
2416 .collect();
2417
2418 merge_peer_availability(
2419 &reports,
2420 fedimint_core::NumPeersExt::to_num_peers(self.api.all_peers()),
2421 )
2422 }
2423
2424 pub async fn get_peer_urls(&self) -> BTreeMap<PeerId, SafeUrl> {
2426 get_api_urls(&self.db, &self.config().await, self.iroh_enable_next).await
2427 }
2428
2429 pub async fn invite_code(&self, peer: PeerId) -> Option<InviteCode> {
2432 self.get_peer_urls()
2433 .await
2434 .into_iter()
2435 .find_map(|(peer_id, url)| (peer == peer_id).then_some(url))
2436 .map(|peer_url| {
2437 InviteCode::new(
2438 peer_url.clone(),
2439 peer,
2440 self.federation_id(),
2441 self.api_secret.clone(),
2442 )
2443 })
2444 }
2445
2446 pub async fn get_guardian_public_keys_blocking(
2450 &self,
2451 ) -> BTreeMap<PeerId, fedimint_core::secp256k1::PublicKey> {
2452 self.db
2453 .autocommit(
2454 |dbtx, _| {
2455 Box::pin(async move {
2456 let config = self.config().await;
2457
2458 let guardian_pub_keys = self
2459 .get_or_backfill_broadcast_public_keys(dbtx, config)
2460 .await;
2461
2462 Result::<_, ()>::Ok(guardian_pub_keys)
2463 })
2464 },
2465 None,
2466 )
2467 .await
2468 .expect("Will retry forever")
2469 }
2470
2471 async fn get_or_backfill_broadcast_public_keys(
2472 &self,
2473 dbtx: &mut DatabaseTransaction<'_>,
2474 config: ClientConfig,
2475 ) -> BTreeMap<PeerId, PublicKey> {
2476 match config.global.broadcast_public_keys {
2477 Some(guardian_pub_keys) => guardian_pub_keys,
2478 _ => {
2479 let (guardian_pub_keys, new_config) = self.fetch_and_update_config(config).await;
2480
2481 dbtx.insert_entry(&ClientConfigKey, &new_config).await;
2482 *(self.config.write().await) = new_config;
2483 guardian_pub_keys
2484 }
2485 }
2486 }
2487
2488 pub async fn fetch_session_count(&self) -> FederationResult<u64> {
2489 self.api.session_count().await
2490 }
2491
2492 async fn fetch_and_update_config(
2493 &self,
2494 config: ClientConfig,
2495 ) -> (BTreeMap<PeerId, PublicKey>, ClientConfig) {
2496 let fetched_config = retry(
2497 "Fetching guardian public keys",
2498 backoff_util::background_backoff(),
2499 || async {
2500 self.api
2501 .request_current_consensus::<ClientConfig>(
2502 CLIENT_CONFIG_ENDPOINT.to_owned(),
2503 ApiRequestErased::default(),
2504 )
2505 .await
2506 },
2507 )
2508 .await
2509 .expect("Will never return on error");
2510
2511 let Some(guardian_pub_keys) = fetched_config.global.broadcast_public_keys else {
2512 warn!(
2513 target: LOG_CLIENT,
2514 "Guardian public keys not found in fetched config, server not updated to 0.4 yet"
2515 );
2516 pending::<()>().await;
2517 unreachable!("Pending will never return");
2518 };
2519
2520 let new_config = ClientConfig {
2521 global: GlobalClientConfig {
2522 broadcast_public_keys: Some(guardian_pub_keys.clone()),
2523 ..config.global
2524 },
2525 modules: config.modules,
2526 };
2527 (guardian_pub_keys, new_config)
2528 }
2529
2530 pub fn handle_global_rpc(
2537 &self,
2538 method: String,
2539 params: serde_json::Value,
2540 ) -> BoxStream<'_, Result<serde_json::Value, GlobalRpcError>> {
2541 Box::pin(try_stream! {
2542 match method.as_str() {
2543 "get_balance" => {
2544 let balance = self.get_balance_for_btc().await.unwrap_or_default();
2545 yield serde_json::to_value(balance)?;
2546 }
2547 "subscribe_balance_changes" => {
2548 let req: GetBalanceChangesRequest= serde_json::from_value(params)?;
2549 let mut stream = self.subscribe_balance_changes(req.unit).await;
2550 while let Some(balance) = stream.next().await {
2551 yield serde_json::to_value(balance)?;
2552 }
2553 }
2554 "get_config" => {
2555 let config = self.config().await;
2556 yield serde_json::to_value(config)?;
2557 }
2558 "get_federation_id" => {
2559 let federation_id = self.federation_id();
2560 yield serde_json::to_value(federation_id)?;
2561 }
2562 "get_invite_code" => {
2563 let req: GetInviteCodeRequest = serde_json::from_value(params)?;
2564 let invite_code = self.invite_code(req.peer).await;
2565 yield serde_json::to_value(invite_code)?;
2566 }
2567 "get_operation" => {
2568 let req: GetOperationIdRequest = serde_json::from_value(params)?;
2569 let operation = self.operation_log().get_operation(req.operation_id).await;
2570 yield serde_json::to_value(operation)?;
2571 }
2572 "list_operations" => {
2573 let req: ListOperationsParams = serde_json::from_value(params)?;
2574 let limit = if req.limit.is_none() && req.last_seen.is_none() {
2575 usize::MAX
2576 } else {
2577 req.limit.unwrap_or(usize::MAX)
2578 };
2579 let operations = self.operation_log()
2580 .paginate_operations_rev(limit, req.last_seen)
2581 .await;
2582 yield serde_json::to_value(operations)?;
2583 }
2584 "get_event_log" => {
2585 let req: GetEventLogRequest = serde_json::from_value(params)?;
2586 let limit = req
2587 .limit
2588 .unwrap_or(DEFAULT_EVENT_LOG_PAGE_SIZE)
2589 .min(MAX_EVENT_LOG_PAGE_SIZE);
2590 let events = self.get_event_log(req.pos, limit).await;
2591 yield serde_json::to_value(events)?;
2592 }
2593 "session_count" => {
2594 let count = self.fetch_session_count().await?;
2595 yield serde_json::to_value(count)?;
2596 }
2597 "has_pending_recoveries" => {
2598 let has_pending = self.has_pending_recoveries();
2599 yield serde_json::to_value(has_pending)?;
2600 }
2601 "wait_for_all_recoveries" => {
2602 self.wait_for_all_recoveries().await?;
2603 yield serde_json::Value::Null;
2604 }
2605 "subscribe_to_recovery_progress" => {
2606 let mut stream = self.subscribe_to_recovery_progress();
2607 while let Some((module_id, progress)) = stream.next().await {
2608 yield serde_json::json!({
2609 "module_id": module_id,
2610 "progress": progress
2611 });
2612 }
2613 }
2614 #[allow(deprecated)]
2615 "backup_to_federation" => {
2616 let metadata = if params.is_null() {
2617 Metadata::from_json_serialized(serde_json::json!({}))
2618 } else {
2619 Metadata::from_json_serialized(params)
2620 };
2621 self.backup_to_federation(metadata).await?;
2622 yield serde_json::Value::Null;
2623 }
2624 _ => {
2625 Err(GlobalRpcError::UnknownMethod { method: method.clone() })?;
2626 unreachable!()
2627 },
2628 }
2629 })
2630 }
2631
2632 pub async fn log_event<E>(&self, module_id: Option<ModuleInstanceId>, event: E)
2633 where
2634 E: Event + Send,
2635 {
2636 let mut dbtx = self.db.begin_transaction().await;
2637 self.log_event_dbtx(&mut dbtx, module_id, event).await;
2638 dbtx.commit_tx().await;
2639 }
2640
2641 pub async fn log_event_dbtx<E, Cap>(
2642 &self,
2643 dbtx: &mut DatabaseTransaction<'_, Cap>,
2644 module_id: Option<ModuleInstanceId>,
2645 event: E,
2646 ) where
2647 E: Event + Send,
2648 Cap: Send,
2649 {
2650 dbtx.log_event(self.log_ordering_wakeup_tx.clone(), module_id, event)
2651 .await;
2652 }
2653
2654 pub async fn log_event_raw_dbtx<Cap>(
2655 &self,
2656 dbtx: &mut DatabaseTransaction<'_, Cap>,
2657 kind: EventKind,
2658 module: Option<(ModuleKind, ModuleInstanceId)>,
2659 payload: Vec<u8>,
2660 persist: EventPersistence,
2661 ) where
2662 Cap: Send,
2663 {
2664 let module_id = module.as_ref().map(|m| m.1);
2665 let module_kind = module.map(|m| m.0);
2666 dbtx.log_event_raw(
2667 self.log_ordering_wakeup_tx.clone(),
2668 kind,
2669 module_kind,
2670 module_id,
2671 payload,
2672 persist,
2673 )
2674 .await;
2675 }
2676
2677 pub fn built_in_application_event_log_tracker(&self) -> DynEventLogTrimableTracker {
2689 struct BuiltInApplicationEventLogTracker;
2690
2691 #[apply(async_trait_maybe_send!)]
2692 impl EventLogTrimableTracker for BuiltInApplicationEventLogTracker {
2693 async fn store(
2695 &mut self,
2696 dbtx: &mut DatabaseTransaction<NonCommittable>,
2697 pos: EventLogTrimableId,
2698 ) -> Result<(), EventLogTrackerError> {
2699 dbtx.insert_entry(&DefaultApplicationEventLogKey, &pos)
2700 .await;
2701 Ok(())
2702 }
2703
2704 async fn load(
2706 &mut self,
2707 dbtx: &mut DatabaseTransaction<NonCommittable>,
2708 ) -> Result<Option<EventLogTrimableId>, EventLogTrackerError> {
2709 Ok(dbtx.get_value(&DefaultApplicationEventLogKey).await)
2710 }
2711 }
2712 Box::new(BuiltInApplicationEventLogTracker)
2713 }
2714
2715 pub async fn handle_historical_events<F, R, E>(
2723 &self,
2724 tracker: fedimint_eventlog::DynEventLogTracker,
2725 handler_fn: F,
2726 ) -> Result<(), EventHandlerError<E>>
2727 where
2728 F: Fn(&mut DatabaseTransaction<NonCommittable>, EventLogEntry) -> R,
2729 R: Future<Output = Result<(), E>>,
2730 E: std::error::Error + 'static,
2731 {
2732 fedimint_eventlog::handle_events(
2733 self.db.clone(),
2734 tracker,
2735 self.log_event_added_rx.clone(),
2736 handler_fn,
2737 )
2738 .await
2739 }
2740
2741 pub async fn handle_events<F, R, E>(
2760 &self,
2761 tracker: fedimint_eventlog::DynEventLogTrimableTracker,
2762 handler_fn: F,
2763 ) -> Result<(), EventHandlerError<E>>
2764 where
2765 F: Fn(&mut DatabaseTransaction<NonCommittable>, EventLogEntry) -> R,
2766 R: Future<Output = Result<(), E>>,
2767 E: std::error::Error + 'static,
2768 {
2769 fedimint_eventlog::handle_trimable_events(
2770 self.db.clone(),
2771 tracker,
2772 self.log_event_added_rx.clone(),
2773 handler_fn,
2774 )
2775 .await
2776 }
2777
2778 pub async fn get_event_log(
2779 &self,
2780 pos: Option<EventLogId>,
2781 limit: u64,
2782 ) -> Vec<PersistedLogEntry> {
2783 self.get_event_log_dbtx(&mut self.db.begin_transaction_nc().await, pos, limit)
2784 .await
2785 }
2786
2787 pub async fn get_next_event_log_id(&self) -> EventLogId {
2790 self.db
2791 .begin_transaction_nc()
2792 .await
2793 .get_next_event_log_id()
2794 .await
2795 }
2796
2797 pub async fn get_event_log_trimable(
2798 &self,
2799 pos: Option<EventLogTrimableId>,
2800 limit: u64,
2801 ) -> Vec<PersistedLogEntry> {
2802 self.get_event_log_trimable_dbtx(&mut self.db.begin_transaction_nc().await, pos, limit)
2803 .await
2804 }
2805
2806 pub async fn get_event_log_dbtx<Cap>(
2807 &self,
2808 dbtx: &mut DatabaseTransaction<'_, Cap>,
2809 pos: Option<EventLogId>,
2810 limit: u64,
2811 ) -> Vec<PersistedLogEntry>
2812 where
2813 Cap: Send,
2814 {
2815 dbtx.get_event_log(pos, limit).await
2816 }
2817
2818 pub async fn get_event_log_trimable_dbtx<Cap>(
2819 &self,
2820 dbtx: &mut DatabaseTransaction<'_, Cap>,
2821 pos: Option<EventLogTrimableId>,
2822 limit: u64,
2823 ) -> Vec<PersistedLogEntry>
2824 where
2825 Cap: Send,
2826 {
2827 dbtx.get_event_log_trimable(pos, limit).await
2828 }
2829
2830 pub fn get_event_log_transient_receiver(&self) -> broadcast::Receiver<EventLogEntry> {
2832 self.log_event_added_transient_tx.subscribe()
2833 }
2834
2835 pub fn log_event_added_rx(&self) -> watch::Receiver<()> {
2837 self.log_event_added_rx.clone()
2838 }
2839
2840 pub fn iroh_enable_dht(&self) -> bool {
2841 self.iroh_enable_dht
2842 }
2843
2844 pub fn iroh_enable_next(&self) -> bool {
2847 self.iroh_enable_next
2848 }
2849
2850 pub(crate) async fn run_core_migrations(
2851 db_no_decoders: &Database,
2852 ) -> Result<(), DbMigrationError> {
2853 let mut dbtx = db_no_decoders.begin_transaction().await;
2854 apply_migrations_core_client_dbtx(&mut dbtx.to_ref_nc(), "fedimint-client".to_string())
2855 .await?;
2856 if is_running_in_test_env() {
2857 verify_client_db_integrity_dbtx(&mut dbtx.to_ref_nc()).await;
2858 }
2859 dbtx.commit_tx_result().await?;
2860 Ok(())
2861 }
2862
2863 fn primary_modules_for_unit(
2865 &self,
2866 unit: AmountUnit,
2867 ) -> impl Iterator<Item = (ModuleInstanceId, &DynClientModule)> {
2868 self.primary_modules
2869 .iter()
2870 .flat_map(move |(_prio, candidates)| {
2871 candidates
2872 .specific
2873 .get(&unit)
2874 .into_iter()
2875 .flatten()
2876 .copied()
2877 .chain(candidates.wildcard.iter().copied())
2879 })
2880 .map(|id| (id, self.modules.get_expect(id)))
2881 }
2882
2883 pub fn primary_module_for_unit(
2887 &self,
2888 unit: AmountUnit,
2889 ) -> Option<(ModuleInstanceId, &DynClientModule)> {
2890 self.primary_modules_for_unit(unit).next()
2891 }
2892
2893 pub fn primary_module_for_btc(&self) -> (ModuleInstanceId, &DynClientModule) {
2895 self.primary_module_for_unit(AmountUnit::BITCOIN)
2896 .expect("No primary module for Bitcoin")
2897 }
2898
2899 pub fn get_primary_module_for_unit<M: ClientModule>(
2912 &self,
2913 unit: AmountUnit,
2914 ) -> Result<&M, ModuleLookupError> {
2915 let mut modules = self.primary_modules_for_unit(unit).peekable();
2916 if modules.peek().is_none() {
2917 return Err(ModuleLookupError::NoPrimaryModule { unit });
2918 }
2919 modules
2920 .find_map(|(_, module)| module.as_any().downcast_ref::<M>())
2921 .ok_or_else(|| ModuleLookupError::NoPrimaryModuleOfKind {
2922 kind: M::kind(),
2923 unit,
2924 })
2925 }
2926}
2927
2928#[apply(async_trait_maybe_send!)]
2929impl ClientContextIface for Client {
2930 fn get_module(&self, instance: ModuleInstanceId) -> &maybe_add_send_sync!(dyn IClientModule) {
2931 Client::get_module(self, instance)
2932 }
2933
2934 fn api_clone(&self) -> DynGlobalApi {
2935 Client::api_clone(self)
2936 }
2937 fn decoders(&self) -> &ModuleDecoderRegistry {
2938 Client::decoders(self)
2939 }
2940
2941 async fn finalize_and_submit_transaction(
2942 &self,
2943 operation_id: OperationId,
2944 operation_type: &str,
2945 operation_meta_gen: Box<maybe_add_send_sync!(dyn Fn(OutPointRange) -> serde_json::Value)>,
2946 tx_builder: TransactionBuilder,
2947 ) -> Result<OutPointRange, TransactionSubmitError> {
2948 Client::finalize_and_submit_transaction(
2949 self,
2950 operation_id,
2951 operation_type,
2952 &operation_meta_gen,
2954 tx_builder,
2955 )
2956 .await
2957 }
2958
2959 async fn finalize_and_submit_transaction_dbtx(
2960 &self,
2961 dbtx: &mut DatabaseTransaction<'_>,
2962 operation_id: OperationId,
2963 operation_type: &str,
2964 operation_meta_gen: Box<maybe_add_send_sync!(dyn Fn(OutPointRange) -> serde_json::Value)>,
2965 tx_builder: TransactionBuilder,
2966 ) -> Result<OutPointRange, TransactionSubmitError> {
2967 Client::finalize_and_submit_transaction_dbtx(
2968 self,
2969 dbtx,
2970 operation_id,
2971 operation_type,
2972 &operation_meta_gen,
2973 tx_builder,
2974 )
2975 .await
2976 }
2977
2978 async fn finalize_and_submit_transaction_inner(
2979 &self,
2980 dbtx: &mut DatabaseTransaction<'_>,
2981 operation_id: OperationId,
2982 tx_builder: TransactionBuilder,
2983 ) -> Result<OutPointRange, TransactionSubmitError> {
2984 Client::finalize_and_submit_transaction_inner(self, dbtx, operation_id, tx_builder).await
2985 }
2986
2987 async fn fee_quote(
2988 &self,
2989 operation_id: OperationId,
2990 request: FeeQuoteRequest,
2991 ) -> Result<FeeQuote, TransactionSubmitError> {
2992 Client::fee_quote(self, operation_id, request).await
2993 }
2994
2995 async fn get_balance_for_unit(&self, unit: AmountUnit) -> Result<Amount, ModuleLookupError> {
2996 Client::get_balance_for_unit(self, unit).await
2997 }
2998
2999 async fn transaction_updates(&self, operation_id: OperationId) -> TransactionUpdates {
3000 Client::transaction_updates(self, operation_id).await
3001 }
3002
3003 async fn await_primary_module_outputs(
3004 &self,
3005 operation_id: OperationId,
3006 outputs: Vec<OutPoint>,
3008 ) -> Result<(), TransactionSubmitError> {
3009 Client::await_primary_bitcoin_module_outputs(self, operation_id, outputs).await
3010 }
3011
3012 fn operation_log(&self) -> &dyn IOperationLog {
3013 Client::operation_log(self)
3014 }
3015
3016 async fn has_active_states(&self, operation_id: OperationId) -> bool {
3017 Client::has_active_states(self, operation_id).await
3018 }
3019
3020 async fn await_no_active_states(&self, operation_id: OperationId) {
3021 Client::await_no_active_states(self, operation_id).await;
3022 }
3023
3024 async fn operation_exists(&self, operation_id: OperationId) -> bool {
3025 Client::operation_exists(self, operation_id).await
3026 }
3027
3028 async fn config(&self) -> ClientConfig {
3029 Client::config(self).await
3030 }
3031
3032 fn db(&self) -> &Database {
3033 Client::db(self)
3034 }
3035
3036 fn executor(&self) -> &(maybe_add_send_sync!(dyn IExecutor + 'static)) {
3037 Client::executor(self)
3038 }
3039
3040 async fn invite_code(&self, peer: PeerId) -> Option<InviteCode> {
3041 Client::invite_code(self, peer).await
3042 }
3043
3044 fn get_internal_payment_markers(&self) -> Result<(PublicKey, u64), bitcoin::secp256k1::Error> {
3045 Client::get_internal_payment_markers(self)
3046 }
3047
3048 async fn log_event_json(
3049 &self,
3050 dbtx: &mut DatabaseTransaction<'_, NonCommittable>,
3051 module_kind: Option<ModuleKind>,
3052 module_id: ModuleInstanceId,
3053 kind: EventKind,
3054 payload: serde_json::Value,
3055 persist: EventPersistence,
3056 ) {
3057 dbtx.ensure_global()
3058 .expect("Must be called with global dbtx");
3059 self.log_event_raw_dbtx(
3060 dbtx,
3061 kind,
3062 module_kind.map(|kind| (kind, module_id)),
3063 serde_json::to_vec(&payload).expect("Serialization can't fail"),
3064 persist,
3065 )
3066 .await;
3067 }
3068
3069 async fn read_operation_active_states<'dbtx>(
3070 &self,
3071 operation_id: OperationId,
3072 module_id: ModuleInstanceId,
3073 dbtx: &'dbtx mut DatabaseTransaction<'_>,
3074 ) -> Pin<Box<maybe_add_send!(dyn Stream<Item = (ActiveStateKey, ActiveStateMeta)> + 'dbtx)>>
3075 {
3076 Box::pin(
3077 dbtx.find_by_prefix(&ActiveModuleOperationStateKeyPrefix {
3078 operation_id,
3079 module_instance: module_id,
3080 })
3081 .await
3082 .map(move |(k, v)| (k.0, v)),
3083 )
3084 }
3085 async fn read_operation_inactive_states<'dbtx>(
3086 &self,
3087 operation_id: OperationId,
3088 module_id: ModuleInstanceId,
3089 dbtx: &'dbtx mut DatabaseTransaction<'_>,
3090 ) -> Pin<Box<maybe_add_send!(dyn Stream<Item = (InactiveStateKey, InactiveStateMeta)> + 'dbtx)>>
3091 {
3092 Box::pin(
3093 dbtx.find_by_prefix(&InactiveModuleOperationStateKeyPrefix {
3094 operation_id,
3095 module_instance: module_id,
3096 })
3097 .await
3098 .map(move |(k, v)| (k.0, v)),
3099 )
3100 }
3101}
3102
3103impl fmt::Debug for Client {
3105 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3106 write!(f, "Client")
3107 }
3108}
3109
3110pub fn client_decoders<'a>(
3111 registry: &ModuleInitRegistry<DynClientModuleInit>,
3112 module_kinds: impl Iterator<Item = (ModuleInstanceId, &'a ModuleKind)>,
3113) -> ModuleDecoderRegistry {
3114 let mut modules = BTreeMap::new();
3115 for (id, kind) in module_kinds {
3116 let Some(init) = registry.get(kind) else {
3117 debug!("Detected configuration for unsupported module id: {id}, kind: {kind}");
3118 continue;
3119 };
3120
3121 modules.insert(
3122 id,
3123 (
3124 kind.clone(),
3125 IClientModuleInit::decoder(AsRef::<dyn IClientModuleInit + 'static>::as_ref(init)),
3126 ),
3127 );
3128 }
3129 ModuleDecoderRegistry::from(modules)
3130}