1use std::collections::{BTreeMap, HashSet};
2use std::fmt::{self, Formatter};
3use std::future::{Future, pending};
4use std::ops::Range;
5use std::pin::Pin;
6use std::sync::Arc;
7use std::time::{Duration, SystemTime, UNIX_EPOCH};
8
9use anyhow::{Context as _, anyhow, bail, format_err};
10use async_stream::try_stream;
11use bitcoin::key::Secp256k1;
12use bitcoin::key::rand::thread_rng;
13use bitcoin::secp256k1::{self, PublicKey};
14use fedimint_api_client::api::global_api::with_request_hook::ApiRequestHook;
15use fedimint_api_client::api::{
16 ApiVersionSet, DynGlobalApi, FederationApiExt as _, FederationResult, IGlobalFederationApi,
17};
18use fedimint_bitcoind::DynBitcoindRpc;
19use fedimint_client_module::module::recovery::RecoveryProgress;
20use fedimint_client_module::module::{
21 ClientContextIface, ClientModule, ClientModuleRegistry, DynClientModule, FinalClientIface,
22 IClientModule, IdxRange, OutPointRange, PrimaryModulePriority,
23};
24use fedimint_client_module::oplog::IOperationLog;
25use fedimint_client_module::secret::{PlainRootSecretStrategy, RootSecretStrategy as _};
26use fedimint_client_module::sm::executor::{ActiveStateKey, IExecutor, InactiveStateKey};
27use fedimint_client_module::sm::{ActiveStateMeta, DynState, InactiveStateMeta};
28use fedimint_client_module::transaction::{
29 FeeQuote, FeeQuoteRequest, TRANSACTION_SUBMISSION_MODULE_INSTANCE, TransactionBuilder,
30 TxSubmissionStates, TxSubmissionStatesSM,
31};
32use fedimint_client_module::{
33 AddStateMachinesResult, ClientModuleInstance, GetInviteCodeRequest, ModuleGlobalContextGen,
34 ModuleRecoveryCompleted, TransactionUpdates, TxCreatedEvent,
35};
36use fedimint_connectors::{ConnectorRegistry, PeerStatus};
37use fedimint_core::config::{
38 ClientConfig, FederationId, GlobalClientConfig, JsonClientConfig, ModuleInitRegistry,
39};
40use fedimint_core::core::{DynInput, DynOutput, ModuleInstanceId, ModuleKind, OperationId};
41use fedimint_core::db::{
42 AutocommitError, Database, DatabaseRecord, DatabaseTransaction,
43 IDatabaseTransactionOpsCore as _, IDatabaseTransactionOpsCoreTyped as _, NonCommittable,
44};
45use fedimint_core::encoding::{Decodable, Encodable};
46use fedimint_core::endpoint_constants::{CLIENT_CONFIG_ENDPOINT, VERSION_ENDPOINT};
47use fedimint_core::envs::is_running_in_test_env;
48use fedimint_core::invite_code::InviteCode;
49use fedimint_core::module::registry::{ModuleDecoderRegistry, ModuleRegistry};
50use fedimint_core::module::{
51 AmountUnit, Amounts, ApiRequestErased, ApiVersion, MultiApiVersion,
52 SupportedApiVersionsSummary, SupportedCoreApiVersions, SupportedModuleApiVersions,
53};
54use fedimint_core::net::api_announcement::SignedApiAnnouncement;
55use fedimint_core::runtime::sleep;
56use fedimint_core::task::{
57 Elapsed, MaybeSend, MaybeSync, ShuttingDownError, TaskGroup, TaskHandle,
58};
59use fedimint_core::transaction::Transaction;
60use fedimint_core::util::backoff_util::custom_backoff;
61use fedimint_core::util::{
62 BoxStream, FmtCompact as _, FmtCompactAnyhow as _, SafeUrl, backoff_util, retry,
63};
64use fedimint_core::{
65 Amount, ChainId, NumPeers, OutPoint, PeerId, apply, async_trait_maybe_send, maybe_add_send,
66 maybe_add_send_sync, runtime,
67};
68use fedimint_derive_secret::DerivableSecret;
69use fedimint_eventlog::{
70 DBTransactionEventLogExt as _, DynEventLogTrimableTracker, Event, EventKind, EventLogEntry,
71 EventLogId, EventLogTrimableId, EventLogTrimableTracker, EventPersistence, PersistedLogEntry,
72};
73use fedimint_logging::{LOG_CLIENT, LOG_CLIENT_NET_API, LOG_CLIENT_RECOVERY};
74use futures::stream::FuturesUnordered;
75use futures::{Stream, StreamExt as _};
76use global_ctx::ModuleGlobalClientContext;
77use serde::{Deserialize, Serialize};
78use tokio::sync::{broadcast, oneshot, watch};
79use tokio_stream::wrappers::WatchStream;
80use tracing::{Span, debug, info, warn};
81
82use crate::ClientBuilder;
83use crate::api_announcements::{ApiAnnouncementPrefix, get_api_urls};
84use crate::backup::Metadata;
85use crate::client::event_log::DefaultApplicationEventLogKey;
86use crate::db::{
87 ApiSecretKey, CachedApiVersionSet, CachedApiVersionSetKey, ChainIdKey,
88 ChronologicalOperationLogKey, ClientConfigKey, ClientMetadataKey, ClientModuleRecovery,
89 ClientModuleRecoveryState, EncodedClientSecretKey, OperationLogKey, PeerLastApiVersionsSummary,
90 PeerLastApiVersionsSummaryKey, PendingClientConfigKey, TransactionFeesKey,
91 apply_migrations_core_client_dbtx, get_decoded_client_secret, verify_client_db_integrity_dbtx,
92};
93use crate::meta::MetaService;
94use crate::module_init::{ClientModuleInitRegistry, DynClientModuleInit, IClientModuleInit};
95use crate::oplog::OperationLog;
96use crate::sm::executor::{
97 ActiveModuleOperationStateKeyPrefix, ActiveOperationStateKeyPrefix, Executor,
98 InactiveModuleOperationStateKeyPrefix, InactiveOperationStateKeyPrefix,
99};
100
101pub(crate) mod builder;
102pub(crate) mod event_log;
103pub(crate) mod global_ctx;
104pub(crate) mod handle;
105
106const SUPPORTED_CORE_API_VERSIONS: &[fedimint_core::module::ApiVersion] =
110 &[ApiVersion { major: 0, minor: 0 }];
111
112struct FinalizedTransaction {
113 transaction: Transaction,
114 states: Vec<DynState>,
115 change_range: Range<u64>,
116 fees: Amounts,
117}
118
119#[derive(Default)]
121pub(crate) struct PrimaryModuleCandidates {
122 specific: BTreeMap<AmountUnit, Vec<ModuleInstanceId>>,
124 wildcard: Vec<ModuleInstanceId>,
126}
127
128pub(crate) type ModuleRecoveryFuture =
131 Pin<Box<maybe_add_send!(dyn Future<Output = anyhow::Result<Option<Amount>>>)>>;
132
133pub struct Client {
147 final_client: FinalClientIface,
148 config: tokio::sync::RwLock<ClientConfig>,
149 api_secret: Option<String>,
150 decoders: ModuleDecoderRegistry,
151 connectors: ConnectorRegistry,
152 db: Database,
153 federation_id: FederationId,
154 federation_config_meta: BTreeMap<String, String>,
155 primary_modules: BTreeMap<PrimaryModulePriority, PrimaryModuleCandidates>,
156 pub(crate) modules: ClientModuleRegistry,
157 module_inits: ClientModuleInitRegistry,
158 executor: Executor,
159 pub(crate) api: DynGlobalApi,
160 root_secret: DerivableSecret,
161 operation_log: OperationLog,
162 secp_ctx: Secp256k1<secp256k1::All>,
163 meta_service: Arc<MetaService>,
164
165 task_group: TaskGroup,
166
167 client_span: Span,
171
172 client_recovery_progress_receiver:
174 watch::Receiver<BTreeMap<ModuleInstanceId, RecoveryProgress>>,
175
176 log_ordering_wakeup_tx: watch::Sender<()>,
179 log_event_added_rx: watch::Receiver<()>,
181 log_event_added_transient_tx: broadcast::Sender<EventLogEntry>,
182 request_hook: ApiRequestHook,
183 iroh_enable_dht: bool,
184 iroh_enable_next: bool,
185 #[allow(dead_code)]
190 user_bitcoind_rpc: Option<DynBitcoindRpc>,
191 pub(crate) user_bitcoind_rpc_no_chain_id:
196 Option<fedimint_client_module::module::init::BitcoindRpcNoChainIdFactory>,
197}
198
199#[derive(Debug, Serialize, Deserialize)]
200struct ListOperationsParams {
201 limit: Option<usize>,
202 last_seen: Option<ChronologicalOperationLogKey>,
203}
204
205pub const DEFAULT_EVENT_LOG_PAGE_SIZE: u64 = 100;
206pub const MAX_EVENT_LOG_PAGE_SIZE: u64 = 10_000;
207
208#[derive(Debug, Clone, Serialize, Deserialize)]
209struct GetEventLogRequest {
210 pos: Option<EventLogId>,
211 limit: Option<u64>,
212}
213
214#[derive(Debug, Clone, Serialize, Deserialize)]
215pub struct GetOperationIdRequest {
216 operation_id: OperationId,
217}
218
219#[derive(Debug, Clone, Serialize, Deserialize)]
220pub struct GetBalanceChangesRequest {
221 #[serde(default = "AmountUnit::bitcoin")]
222 unit: AmountUnit,
223}
224
225impl Client {
226 pub async fn builder() -> anyhow::Result<ClientBuilder> {
229 Ok(ClientBuilder::new())
230 }
231
232 pub fn api(&self) -> &(dyn IGlobalFederationApi + 'static) {
233 self.api.as_ref()
234 }
235
236 pub fn api_clone(&self) -> DynGlobalApi {
237 self.api.clone()
238 }
239
240 pub fn connection_status_stream(&self) -> impl Stream<Item = BTreeMap<PeerId, PeerStatus>> {
243 self.api.connection_status_stream()
244 }
245
246 pub fn federation_reconnect(&self) {
254 let peers: Vec<PeerId> = self.api.all_peers().iter().copied().collect();
255
256 for peer_id in peers {
257 let api = self.api.clone();
258 self.spawn_cancellable(format!("federation-reconnect-once-{peer_id}"), async move {
259 if let Err(e) = api.get_peer_connection(peer_id).await {
260 debug!(
261 target: LOG_CLIENT_NET_API,
262 %peer_id,
263 err = %e.fmt_compact(),
264 "Failed to connect to peer"
265 );
266 }
267 });
268 }
269 }
270
271 pub fn spawn_federation_reconnect(&self) {
293 let peers: Vec<PeerId> = self.api.all_peers().iter().copied().collect();
294
295 for peer_id in peers {
296 let api = self.api.clone();
297 self.spawn_cancellable(format!("federation-reconnect-{peer_id}"), async move {
298 loop {
299 match api.get_peer_connection(peer_id).await {
300 Ok(conn) => {
301 conn.await_disconnection().await;
302 }
303 Err(e) => {
304 debug!(
307 target: LOG_CLIENT_NET_API,
308 %peer_id,
309 err = %e.fmt_compact(),
310 "Failed to connect to peer, will retry"
311 );
312 }
313 }
314 }
315 });
316 }
317 }
318
319 pub fn task_group(&self) -> &TaskGroup {
321 &self.task_group
322 }
323
324 pub(crate) fn make_client_span(federation_id: FederationId) -> Span {
331 tracing::info_span!(
332 target: LOG_CLIENT,
333 parent: None,
334 "client",
335 fed_id = %federation_id.to_prefix(),
336 )
337 }
338
339 pub(crate) fn spawn_cancellable<R>(
342 &self,
343 name: impl Into<String>,
344 future: impl Future<Output = R> + MaybeSend + 'static,
345 ) -> oneshot::Receiver<Result<R, ShuttingDownError>>
346 where
347 R: MaybeSend + 'static,
348 {
349 self.task_group
350 .spawn_cancellable_with_span(self.client_span.clone(), name, future)
351 }
352
353 pub(crate) fn spawn<Fut, R>(
357 &self,
358 name: impl Into<String>,
359 f: impl FnOnce(TaskHandle) -> Fut + MaybeSend + 'static,
360 ) -> oneshot::Receiver<R>
361 where
362 Fut: Future<Output = R> + MaybeSend + 'static,
363 R: MaybeSend + 'static,
364 {
365 self.task_group
366 .spawn_with_span(self.client_span.clone(), name, f)
367 }
368
369 pub fn get_metrics() -> anyhow::Result<String> {
374 fedimint_metrics::get_metrics()
375 }
376
377 #[doc(hidden)]
379 pub fn executor(&self) -> &Executor {
380 &self.executor
381 }
382
383 pub async fn get_config_from_db(db: &Database) -> Option<ClientConfig> {
384 let mut dbtx = db.begin_transaction_nc().await;
385 dbtx.get_value(&ClientConfigKey).await
386 }
387
388 pub async fn get_pending_config_from_db(db: &Database) -> Option<ClientConfig> {
389 let mut dbtx = db.begin_transaction_nc().await;
390 dbtx.get_value(&PendingClientConfigKey).await
391 }
392
393 pub async fn get_api_secret_from_db(db: &Database) -> Option<String> {
394 let mut dbtx = db.begin_transaction_nc().await;
395 dbtx.get_value(&ApiSecretKey).await
396 }
397
398 pub async fn store_encodable_client_secret<T: Encodable>(
399 db: &Database,
400 secret: T,
401 ) -> anyhow::Result<()> {
402 let mut dbtx = db.begin_transaction().await;
403
404 if dbtx.get_value(&EncodedClientSecretKey).await.is_some() {
406 bail!("Encoded client secret already exists, cannot overwrite")
407 }
408
409 let encoded_secret = T::consensus_encode_to_vec(&secret);
410 dbtx.insert_entry(&EncodedClientSecretKey, &encoded_secret)
411 .await;
412 dbtx.commit_tx().await;
413 Ok(())
414 }
415
416 pub async fn load_decodable_client_secret<T: Decodable>(db: &Database) -> anyhow::Result<T> {
417 let Some(secret) = Self::load_decodable_client_secret_opt(db).await? else {
418 bail!("Encoded client secret not present in DB")
419 };
420
421 Ok(secret)
422 }
423 pub async fn load_decodable_client_secret_opt<T: Decodable>(
424 db: &Database,
425 ) -> anyhow::Result<Option<T>> {
426 let mut dbtx = db.begin_transaction_nc().await;
427
428 let client_secret = dbtx.get_value(&EncodedClientSecretKey).await;
429
430 Ok(match client_secret {
431 Some(client_secret) => Some(
432 T::consensus_decode_whole(&client_secret, &ModuleRegistry::default())
433 .map_err(|e| anyhow!("Decoding failed: {e}"))?,
434 ),
435 None => None,
436 })
437 }
438
439 pub async fn load_or_generate_client_secret(db: &Database) -> anyhow::Result<[u8; 64]> {
440 let client_secret = match Self::load_decodable_client_secret::<[u8; 64]>(db).await {
441 Ok(secret) => secret,
442 _ => {
443 let secret = PlainRootSecretStrategy::random(&mut thread_rng());
444 Self::store_encodable_client_secret(db, secret)
445 .await
446 .expect("Storing client secret must work");
447 secret
448 }
449 };
450 Ok(client_secret)
451 }
452
453 pub async fn is_initialized(db: &Database) -> bool {
454 let mut dbtx = db.begin_transaction_nc().await;
455 dbtx.raw_get_bytes(&[ClientConfigKey::DB_PREFIX])
456 .await
457 .expect("Unrecoverable error occurred while reading and entry from the database")
458 .is_some()
459 }
460
461 pub fn start_executor(self: &Arc<Self>) {
462 self.client_span.in_scope(|| {
463 debug!(
464 target: LOG_CLIENT,
465 "Starting fedimint client executor",
466 );
467 });
468 self.executor
469 .start_executor(self.context_gen(), self.client_span.clone());
470 }
471
472 pub fn federation_id(&self) -> FederationId {
473 self.federation_id
474 }
475
476 fn context_gen(self: &Arc<Self>) -> ModuleGlobalContextGen {
477 let client_inner = Arc::downgrade(self);
478 Arc::new(move |module_instance, operation| {
479 ModuleGlobalClientContext {
480 client: client_inner
481 .clone()
482 .upgrade()
483 .expect("ModuleGlobalContextGen called after client was dropped"),
484 module_instance_id: module_instance,
485 operation,
486 }
487 .into()
488 })
489 }
490
491 pub async fn config(&self) -> ClientConfig {
492 self.config.read().await.clone()
493 }
494
495 pub fn api_secret(&self) -> &Option<String> {
497 &self.api_secret
498 }
499
500 pub async fn core_api_version(&self) -> ApiVersion {
506 self.db
509 .begin_transaction_nc()
510 .await
511 .get_value(&CachedApiVersionSetKey)
512 .await
513 .map(|cached: CachedApiVersionSet| cached.0.core)
514 .unwrap_or(ApiVersion { major: 0, minor: 0 })
515 }
516
517 pub async fn chain_id(&self) -> anyhow::Result<ChainId> {
524 if let Some(chain_id) = self
526 .db
527 .begin_transaction_nc()
528 .await
529 .get_value(&ChainIdKey)
530 .await
531 {
532 return Ok(chain_id);
533 }
534
535 let chain_id = self.api.chain_id().await?;
537
538 let mut dbtx = self.db.begin_transaction().await;
540 dbtx.insert_entry(&ChainIdKey, &chain_id).await;
541 dbtx.commit_tx().await;
542
543 Ok(chain_id)
544 }
545
546 pub fn decoders(&self) -> &ModuleDecoderRegistry {
547 &self.decoders
548 }
549
550 fn get_module(&self, instance: ModuleInstanceId) -> &maybe_add_send_sync!(dyn IClientModule) {
552 self.try_get_module(instance)
553 .expect("Module instance not found")
554 }
555
556 fn try_get_module(
557 &self,
558 instance: ModuleInstanceId,
559 ) -> Option<&maybe_add_send_sync!(dyn IClientModule)> {
560 Some(self.modules.get(instance)?.as_ref())
561 }
562
563 pub fn has_module(&self, instance: ModuleInstanceId) -> bool {
564 self.modules.get(instance).is_some()
565 }
566
567 fn transaction_builder_get_balance(&self, builder: &TransactionBuilder) -> (Amounts, Amounts) {
573 let mut in_amounts = Amounts::ZERO;
575 let mut out_amounts = Amounts::ZERO;
576 let mut fee_amounts = Amounts::ZERO;
577
578 for input in builder.inputs() {
579 let module = self.get_module(input.input.module_instance_id());
580
581 let item_fees = module.input_fee(&input.amounts, &input.input).expect(
582 "We only build transactions with input versions that are supported by the module",
583 );
584
585 in_amounts.checked_add_mut(&input.amounts);
586 fee_amounts.checked_add_mut(&item_fees);
587 }
588
589 for output in builder.outputs() {
590 let module = self.get_module(output.output.module_instance_id());
591
592 let item_fees = module.output_fee(&output.amounts, &output.output).expect(
593 "We only build transactions with output versions that are supported by the module",
594 );
595
596 out_amounts.checked_add_mut(&output.amounts);
597 fee_amounts.checked_add_mut(&item_fees);
598 }
599
600 out_amounts.checked_add_mut(&fee_amounts);
601 (in_amounts, out_amounts)
602 }
603
604 pub fn get_internal_payment_markers(&self) -> anyhow::Result<(PublicKey, u64)> {
605 Ok((self.federation_id().to_fake_ln_pub_key(&self.secp_ctx)?, 0))
606 }
607
608 pub fn get_config_meta(&self, key: &str) -> Option<String> {
610 self.federation_config_meta.get(key).cloned()
611 }
612
613 pub(crate) fn root_secret(&self) -> DerivableSecret {
614 self.root_secret.clone()
615 }
616
617 pub async fn add_state_machines(
618 &self,
619 dbtx: &mut DatabaseTransaction<'_>,
620 states: Vec<DynState>,
621 ) -> AddStateMachinesResult {
622 self.executor.add_state_machines_dbtx(dbtx, states).await
623 }
624
625 pub async fn get_active_operations(&self) -> HashSet<OperationId> {
627 let active_states = self.executor.get_active_states().await;
628 let mut active_operations = HashSet::with_capacity(active_states.len());
629 let mut dbtx = self.db().begin_transaction_nc().await;
630 for (state, _) in active_states {
631 let operation_id = state.operation_id();
632 if dbtx
633 .get_value(&OperationLogKey { operation_id })
634 .await
635 .is_some()
636 {
637 active_operations.insert(operation_id);
638 }
639 }
640 active_operations
641 }
642
643 pub fn operation_log(&self) -> &OperationLog {
644 &self.operation_log
645 }
646
647 pub fn meta_service(&self) -> &Arc<MetaService> {
649 &self.meta_service
650 }
651
652 pub async fn get_meta_expiration_timestamp(&self) -> Option<SystemTime> {
654 let meta_service = self.meta_service();
655 let ts = meta_service
656 .get_field::<u64>(self.db(), "federation_expiry_timestamp")
657 .await
658 .and_then(|v| v.value)?;
659 Some(UNIX_EPOCH + Duration::from_secs(ts))
660 }
661
662 async fn finalize_transaction(
664 &self,
665 dbtx: &mut DatabaseTransaction<'_>,
666 operation_id: OperationId,
667 mut partial_transaction: TransactionBuilder,
668 ) -> anyhow::Result<FinalizedTransaction> {
669 let (in_amounts, out_amounts) = self.transaction_builder_get_balance(&partial_transaction);
670
671 let mut added_inputs_bundles = vec![];
672 let mut added_outputs_bundles = vec![];
673
674 for unit in in_amounts.units().union(&out_amounts.units()) {
685 let input_amount = in_amounts.get(unit).copied().unwrap_or_default();
686 let output_amount = out_amounts.get(unit).copied().unwrap_or_default();
687 if input_amount == output_amount {
688 continue;
689 }
690
691 let Some((module_id, module)) = self.primary_module_for_unit(*unit) else {
692 bail!("No module to balance a partial transaction (affected unit: {unit:?}");
693 };
694
695 let (added_input_bundle, added_output_bundle) = module
696 .create_final_inputs_and_outputs(
697 module_id,
698 dbtx,
699 operation_id,
700 *unit,
701 input_amount,
702 output_amount,
703 )
704 .await?;
705
706 added_inputs_bundles.push(added_input_bundle);
707 added_outputs_bundles.push(added_output_bundle);
708 }
709
710 let change_range = Range {
714 start: partial_transaction.outputs().count() as u64,
715 end: (partial_transaction.outputs().count() as u64
716 + added_outputs_bundles
717 .iter()
718 .map(|output| output.outputs().len() as u64)
719 .sum::<u64>()),
720 };
721
722 for added_inputs in added_inputs_bundles {
723 partial_transaction = partial_transaction.with_inputs(added_inputs);
724 }
725
726 for added_outputs in added_outputs_bundles {
727 partial_transaction = partial_transaction.with_outputs(added_outputs);
728 }
729
730 let (input_amounts, output_amounts) =
731 self.transaction_builder_get_balance(&partial_transaction);
732
733 for (unit, output_amount) in output_amounts {
734 let input_amount = input_amounts.get(&unit).copied().unwrap_or_default();
735
736 assert!(input_amount >= output_amount, "Transaction is underfunded");
737 }
738
739 let fees = {
743 let mut input_total = Amounts::ZERO;
744 for input in partial_transaction.inputs() {
745 input_total
746 .checked_add_mut(&input.amounts)
747 .expect("Own transaction amounts don't overflow");
748 }
749 let mut output_total = Amounts::ZERO;
750 for output in partial_transaction.outputs() {
751 output_total
752 .checked_add_mut(&output.amounts)
753 .expect("Own transaction amounts don't overflow");
754 }
755 input_total
756 .checked_sub(&output_total)
757 .expect("Inputs >= outputs for own transactions")
758 };
759
760 let (transaction, states) = partial_transaction.build(&self.secp_ctx, thread_rng());
761
762 Ok(FinalizedTransaction {
763 transaction,
764 states,
765 change_range,
766 fees,
767 })
768 }
769
770 pub async fn fee_quote(
790 &self,
791 operation_id: OperationId,
792 request: FeeQuoteRequest,
793 ) -> anyhow::Result<FeeQuote> {
794 let FeeQuoteRequest {
795 input_amount,
796 output_amount,
797 input_fee,
798 output_fee,
799 } = request;
800
801 let mut gross_input = input_amount.clone();
805 let mut gross_output = output_amount.clone();
806 let mut input_fees = input_fee.clone();
807 let mut output_fees = output_fee.clone();
808
809 let balance_input = input_amount;
815 let balance_output = output_amount
816 .checked_add(&input_fee)
817 .and_then(|amounts| amounts.checked_add(&output_fee))
818 .expect("explicit amounts and fees cannot overflow an Amounts");
819
820 let mut dbtx = self.db.begin_transaction_nc().await;
824
825 for unit in balance_input.units().union(&balance_output.units()) {
828 let balance_input_amount = balance_input.get(unit).copied().unwrap_or_default();
829 let balance_output_amount = balance_output.get(unit).copied().unwrap_or_default();
830 if balance_input_amount == balance_output_amount {
831 continue;
832 }
833
834 let Some((module_id, module)) = self.primary_module_for_unit(*unit) else {
835 bail!("No module to balance a partial transaction (affected unit: {unit:?}");
836 };
837
838 let (change_input, change_output) = module
839 .create_final_inputs_and_outputs(
840 module_id,
841 &mut dbtx.to_ref_nc(),
842 operation_id,
843 *unit,
844 balance_input_amount,
845 balance_output_amount,
846 )
847 .await?;
848
849 for input in change_input.inputs() {
855 let module = self.get_module(input.input.module_instance_id());
856 let fee = module
857 .input_fee(&input.amounts, &input.input)
858 .expect("Primary module must know its own change input fees");
859 gross_input.checked_add_mut(&input.amounts);
860 input_fees.checked_add_mut(&fee);
861 }
862
863 for output in change_output.outputs() {
864 let module = self.get_module(output.output.module_instance_id());
865 let fee = module
866 .output_fee(&output.amounts, &output.output)
867 .expect("Primary module must know its own change output fees");
868 gross_output.checked_add_mut(&output.amounts);
869 output_fees.checked_add_mut(&fee);
870 }
871 }
872
873 dbtx.ignore_uncommitted();
876
877 let mut dust = Amounts::ZERO;
882 for unit in gross_input.units().union(&gross_output.units()) {
883 let total = gross_input
884 .get(unit)
885 .copied()
886 .unwrap_or_default()
887 .saturating_sub(gross_output.get(unit).copied().unwrap_or_default());
888 let fees = input_fees.get(unit).copied().unwrap_or_default()
889 + output_fees.get(unit).copied().unwrap_or_default();
890 dust = dust
891 .checked_add_unit(total.saturating_sub(fees), *unit)
892 .expect("dust cannot overflow an Amounts");
893 }
894
895 Ok(FeeQuote {
896 input: input_fees,
897 output: output_fees,
898 dust,
899 })
900 }
901
902 pub async fn finalize_and_submit_transaction<F, M>(
914 &self,
915 operation_id: OperationId,
916 operation_type: &str,
917 operation_meta_gen: F,
918 tx_builder: TransactionBuilder,
919 ) -> anyhow::Result<OutPointRange>
920 where
921 F: Fn(OutPointRange) -> M + Clone + MaybeSend + MaybeSync,
922 M: serde::Serialize + MaybeSend,
923 {
924 let operation_type = operation_type.to_owned();
925
926 let autocommit_res = self
927 .db
928 .autocommit(
929 |dbtx, _| {
930 let operation_type = operation_type.clone();
931 let tx_builder = tx_builder.clone();
932 let operation_meta_gen = operation_meta_gen.clone();
933 Box::pin(async move {
934 self.finalize_and_submit_transaction_dbtx(
935 dbtx,
936 operation_id,
937 &operation_type,
938 operation_meta_gen,
939 tx_builder,
940 )
941 .await
942 })
943 },
944 Some(100), )
946 .await;
947
948 match autocommit_res {
949 Ok(txid) => Ok(txid),
950 Err(AutocommitError::ClosureError { error, .. }) => Err(error),
951 Err(AutocommitError::CommitFailed {
952 attempts,
953 last_error,
954 }) => panic!(
955 "Failed to commit tx submission dbtx after {attempts} attempts: {last_error}"
956 ),
957 }
958 }
959
960 pub async fn finalize_and_submit_transaction_dbtx<F, M>(
963 &self,
964 dbtx: &mut DatabaseTransaction<'_>,
965 operation_id: OperationId,
966 operation_type: &str,
967 operation_meta_gen: F,
968 tx_builder: TransactionBuilder,
969 ) -> anyhow::Result<OutPointRange>
970 where
971 F: FnOnce(OutPointRange) -> M + MaybeSend,
972 M: serde::Serialize + MaybeSend,
973 {
974 if Client::operation_exists_dbtx(dbtx, operation_id).await {
975 bail!("There already exists an operation with id {operation_id:?}")
976 }
977
978 let out_point_range = self
979 .finalize_and_submit_transaction_inner(dbtx, operation_id, tx_builder)
980 .await?;
981
982 self.operation_log()
983 .add_operation_log_entry_dbtx(
984 dbtx,
985 operation_id,
986 operation_type,
987 operation_meta_gen(out_point_range),
988 )
989 .await;
990
991 Ok(out_point_range)
992 }
993
994 async fn finalize_and_submit_transaction_inner(
995 &self,
996 dbtx: &mut DatabaseTransaction<'_>,
997 operation_id: OperationId,
998 tx_builder: TransactionBuilder,
999 ) -> anyhow::Result<OutPointRange> {
1000 let FinalizedTransaction {
1001 transaction,
1002 mut states,
1003 change_range,
1004 fees,
1005 } = self
1006 .finalize_transaction(&mut dbtx.to_ref_nc(), operation_id, tx_builder)
1007 .await?;
1008
1009 if transaction.consensus_encode_to_vec().len() > Transaction::MAX_TX_SIZE {
1010 let inputs = transaction
1011 .inputs
1012 .iter()
1013 .map(DynInput::module_instance_id)
1014 .collect::<Vec<_>>();
1015 let outputs = transaction
1016 .outputs
1017 .iter()
1018 .map(DynOutput::module_instance_id)
1019 .collect::<Vec<_>>();
1020 warn!(
1021 target: LOG_CLIENT_NET_API,
1022 size=%transaction.consensus_encode_to_vec().len(),
1023 ?inputs,
1024 ?outputs,
1025 "Transaction too large",
1026 );
1027 debug!(target: LOG_CLIENT_NET_API, ?transaction, "transaction details");
1028 bail!(
1029 "The generated transaction would be rejected by the federation for being too large."
1030 );
1031 }
1032
1033 let txid = transaction.tx_hash();
1034
1035 debug!(
1036 target: LOG_CLIENT_NET_API,
1037 %txid,
1038 operation_id = %operation_id.fmt_short(),
1039 ?transaction,
1040 "Finalized and submitting transaction",
1041 );
1042
1043 let tx_submission_sm = DynState::from_typed(
1044 TRANSACTION_SUBMISSION_MODULE_INSTANCE,
1045 TxSubmissionStatesSM {
1046 operation_id,
1047 state: TxSubmissionStates::Created(transaction),
1048 },
1049 );
1050 states.push(tx_submission_sm);
1051
1052 self.executor.add_state_machines_dbtx(dbtx, states).await?;
1053
1054 dbtx.insert_new_entry(&TransactionFeesKey(txid), &fees)
1055 .await;
1056
1057 self.log_event_dbtx(dbtx, None, TxCreatedEvent { txid, operation_id })
1058 .await;
1059
1060 Ok(OutPointRange::new(txid, IdxRange::from(change_range)))
1061 }
1062
1063 async fn transaction_update_stream(
1064 &self,
1065 operation_id: OperationId,
1066 ) -> BoxStream<'static, TxSubmissionStatesSM> {
1067 self.executor
1068 .notifier()
1069 .module_notifier::<TxSubmissionStatesSM>(
1070 TRANSACTION_SUBMISSION_MODULE_INSTANCE,
1071 self.final_client.clone(),
1072 )
1073 .subscribe(operation_id)
1074 .await
1075 }
1076
1077 pub async fn operation_exists(&self, operation_id: OperationId) -> bool {
1078 let mut dbtx = self.db().begin_transaction_nc().await;
1079
1080 Client::operation_exists_dbtx(&mut dbtx, operation_id).await
1081 }
1082
1083 pub async fn operation_exists_dbtx(
1084 dbtx: &mut DatabaseTransaction<'_>,
1085 operation_id: OperationId,
1086 ) -> bool {
1087 let active_state_exists = dbtx
1088 .find_by_prefix(&ActiveOperationStateKeyPrefix { operation_id })
1089 .await
1090 .next()
1091 .await
1092 .is_some();
1093
1094 let inactive_state_exists = dbtx
1095 .find_by_prefix(&InactiveOperationStateKeyPrefix { operation_id })
1096 .await
1097 .next()
1098 .await
1099 .is_some();
1100
1101 active_state_exists || inactive_state_exists
1102 }
1103
1104 pub async fn has_active_states(&self, operation_id: OperationId) -> bool {
1105 self.db
1106 .begin_transaction_nc()
1107 .await
1108 .find_by_prefix(&ActiveOperationStateKeyPrefix { operation_id })
1109 .await
1110 .next()
1111 .await
1112 .is_some()
1113 }
1114
1115 pub async fn get_operation_fees(
1134 &self,
1135 operation_id: OperationId,
1136 ) -> anyhow::Result<Option<Amounts>> {
1137 if !self.operation_exists(operation_id).await {
1138 bail!("Operation does not exist");
1139 }
1140
1141 let (active_states, inactive_states) =
1142 self.executor().get_operation_states(operation_id).await;
1143
1144 let states = active_states
1145 .into_iter()
1146 .map(|(state, _)| state)
1147 .chain(inactive_states.into_iter().map(|(state, _)| state));
1148
1149 let accepted_transactions = states
1150 .filter_map(|state| {
1151 let tx_state = state.as_any().downcast_ref::<TxSubmissionStatesSM>()?;
1152
1153 match &tx_state.state {
1154 TxSubmissionStates::Accepted(transaction_id) => Some(*transaction_id),
1155 _ => None,
1156 }
1157 })
1158 .collect::<HashSet<_>>();
1159
1160 let mut dbtx = self.db.begin_transaction_nc().await;
1162 let mut total_fees = Amounts::ZERO;
1163 for txid in &accepted_transactions {
1164 let Some(fees) = dbtx.get_value(&TransactionFeesKey(*txid)).await else {
1165 return Ok(None);
1166 };
1167 total_fees = total_fees
1168 .checked_add(&fees)
1169 .expect("Fee amounts don't overflow in practice");
1170 }
1171
1172 Ok(Some(total_fees))
1173 }
1174
1175 pub async fn await_primary_bitcoin_module_output(
1178 &self,
1179 operation_id: OperationId,
1180 out_point: OutPoint,
1181 ) -> anyhow::Result<()> {
1182 self.primary_module_for_unit(AmountUnit::BITCOIN)
1183 .ok_or_else(|| anyhow!("No primary module available"))?
1184 .1
1185 .await_primary_module_output(operation_id, out_point)
1186 .await
1187 }
1188
1189 pub fn get_first_module<M: ClientModule>(
1191 &'_ self,
1192 ) -> anyhow::Result<ClientModuleInstance<'_, M>> {
1193 let module_kind = M::kind();
1194 let id = self
1195 .get_first_instance(&module_kind)
1196 .ok_or_else(|| format_err!("No modules found of kind {module_kind}"))?;
1197 let module: &M = self
1198 .try_get_module(id)
1199 .ok_or_else(|| format_err!("Unknown module instance {id}"))?
1200 .as_any()
1201 .downcast_ref::<M>()
1202 .ok_or_else(|| format_err!("Module is not of type {}", std::any::type_name::<M>()))?;
1203 let (db, _) = self.db().with_prefix_module_id(id);
1204 Ok(ClientModuleInstance {
1205 id,
1206 db,
1207 api: self.api().with_module(id),
1208 module,
1209 })
1210 }
1211
1212 #[cfg(not(target_family = "wasm"))]
1217 pub fn get_first_module_arc<M: ClientModule>(&self) -> anyhow::Result<Arc<M>> {
1218 let module_kind = M::kind();
1219 let id = self
1220 .get_first_instance(&module_kind)
1221 .ok_or_else(|| format_err!("No modules found of kind {module_kind}"))?;
1222 let dyn_module = self
1223 .modules
1224 .get(id)
1225 .ok_or_else(|| format_err!("Unknown module instance {id}"))?;
1226 dyn_module
1227 .as_any_arc()
1228 .downcast::<M>()
1229 .map_err(|_| format_err!("Module is not of type {}", std::any::type_name::<M>()))
1230 }
1231
1232 pub fn get_module_client_dyn(
1233 &self,
1234 instance_id: ModuleInstanceId,
1235 ) -> anyhow::Result<&maybe_add_send_sync!(dyn IClientModule)> {
1236 self.try_get_module(instance_id)
1237 .ok_or(anyhow!("Unknown module instance {}", instance_id))
1238 }
1239
1240 pub fn db(&self) -> &Database {
1241 &self.db
1242 }
1243
1244 pub fn endpoints(&self) -> &ConnectorRegistry {
1245 &self.connectors
1246 }
1247
1248 pub async fn transaction_updates(&self, operation_id: OperationId) -> TransactionUpdates {
1251 TransactionUpdates {
1252 update_stream: self.transaction_update_stream(operation_id).await,
1253 }
1254 }
1255
1256 pub fn get_first_instance(&self, module_kind: &ModuleKind) -> Option<ModuleInstanceId> {
1258 self.modules
1259 .iter_modules()
1260 .find(|(_, kind, _module)| *kind == module_kind)
1261 .map(|(instance_id, _, _)| instance_id)
1262 }
1263
1264 pub async fn root_secret_encoding<T: Decodable>(&self) -> anyhow::Result<T> {
1267 get_decoded_client_secret::<T>(self.db()).await
1268 }
1269
1270 pub async fn await_primary_bitcoin_module_outputs(
1273 &self,
1274 operation_id: OperationId,
1275 outputs: Vec<OutPoint>,
1276 ) -> anyhow::Result<()> {
1277 for out_point in outputs {
1278 self.await_primary_bitcoin_module_output(operation_id, out_point)
1279 .await?;
1280 }
1281
1282 Ok(())
1283 }
1284
1285 pub async fn get_config_json(&self) -> JsonClientConfig {
1291 self.config().await.to_json()
1292 }
1293
1294 #[doc(hidden)]
1297 pub async fn get_balance_for_btc(&self) -> anyhow::Result<Amount> {
1300 self.get_balance_for_unit(AmountUnit::BITCOIN).await
1301 }
1302
1303 pub async fn get_balance_for_unit(&self, unit: AmountUnit) -> anyhow::Result<Amount> {
1304 let (id, module) = self
1305 .primary_module_for_unit(unit)
1306 .ok_or_else(|| anyhow!("Primary module not available"))?;
1307 Ok(module
1308 .get_balance(id, &mut self.db().begin_transaction_nc().await, unit)
1309 .await)
1310 }
1311
1312 pub async fn subscribe_balance_changes(&self, unit: AmountUnit) -> BoxStream<'static, Amount> {
1315 let primary_module_things =
1316 if let Some((primary_module_id, primary_module)) = self.primary_module_for_unit(unit) {
1317 let balance_changes = primary_module.subscribe_balance_changes().await;
1318 let initial_balance = self
1319 .get_balance_for_unit(unit)
1320 .await
1321 .expect("Primary is present");
1322
1323 Some((
1324 primary_module_id,
1325 primary_module.clone(),
1326 balance_changes,
1327 initial_balance,
1328 ))
1329 } else {
1330 None
1331 };
1332 let db = self.db().clone();
1333
1334 Box::pin(async_stream::stream! {
1335 let Some((primary_module_id, primary_module, mut balance_changes, initial_balance)) = primary_module_things else {
1336 pending().await
1339 };
1340
1341
1342 yield initial_balance;
1343 let mut prev_balance = initial_balance;
1344 while let Some(()) = balance_changes.next().await {
1345 let mut dbtx = db.begin_transaction_nc().await;
1346 let balance = primary_module
1347 .get_balance(primary_module_id, &mut dbtx, unit)
1348 .await;
1349
1350 if balance != prev_balance {
1352 prev_balance = balance;
1353 yield balance;
1354 }
1355 }
1356 })
1357 }
1358
1359 async fn make_api_version_request(
1364 delay: Duration,
1365 peer_id: PeerId,
1366 api: &DynGlobalApi,
1367 ) -> (
1368 PeerId,
1369 Result<SupportedApiVersionsSummary, fedimint_connectors::error::ServerError>,
1370 ) {
1371 runtime::sleep(delay).await;
1372 (
1373 peer_id,
1374 api.request_single_peer::<SupportedApiVersionsSummary>(
1375 VERSION_ENDPOINT.to_owned(),
1376 ApiRequestErased::default(),
1377 peer_id,
1378 )
1379 .await,
1380 )
1381 }
1382
1383 fn create_api_version_backoff() -> impl Iterator<Item = Duration> {
1389 custom_backoff(Duration::from_millis(200), Duration::from_secs(600), None)
1390 }
1391
1392 pub async fn fetch_common_api_versions_from_all_peers(
1395 num_peers: NumPeers,
1396 api: DynGlobalApi,
1397 db: Database,
1398 num_responses_sender: watch::Sender<usize>,
1399 ) {
1400 let mut backoff = Self::create_api_version_backoff();
1401
1402 let mut requests = FuturesUnordered::new();
1405
1406 for peer_id in num_peers.peer_ids() {
1407 requests.push(Self::make_api_version_request(
1408 Duration::ZERO,
1409 peer_id,
1410 &api,
1411 ));
1412 }
1413
1414 let mut num_responses = 0;
1415
1416 while let Some((peer_id, response)) = requests.next().await {
1417 let retry = match response {
1418 Err(err) => {
1419 let has_previous_response = db
1420 .begin_transaction_nc()
1421 .await
1422 .get_value(&PeerLastApiVersionsSummaryKey(peer_id))
1423 .await
1424 .is_some();
1425 debug!(
1426 target: LOG_CLIENT,
1427 %peer_id,
1428 err = %err.fmt_compact(),
1429 %has_previous_response,
1430 "Failed to refresh API versions of a peer"
1431 );
1432
1433 !has_previous_response
1434 }
1435 Ok(o) => {
1436 let mut dbtx = db.begin_transaction().await;
1439 dbtx.insert_entry(
1440 &PeerLastApiVersionsSummaryKey(peer_id),
1441 &PeerLastApiVersionsSummary(o),
1442 )
1443 .await;
1444 dbtx.commit_tx().await;
1445 false
1446 }
1447 };
1448
1449 if retry {
1450 requests.push(Self::make_api_version_request(
1451 backoff.next().expect("Keeps retrying"),
1452 peer_id,
1453 &api,
1454 ));
1455 } else {
1456 num_responses += 1;
1457 num_responses_sender.send_replace(num_responses);
1458 }
1459 }
1460 }
1461
1462 pub async fn fetch_peers_api_versions_from_threshold_of_peers(
1466 num_peers: NumPeers,
1467 api: DynGlobalApi,
1468 ) -> BTreeMap<PeerId, SupportedApiVersionsSummary> {
1469 let mut backoff = Self::create_api_version_backoff();
1470
1471 let mut requests = FuturesUnordered::new();
1474
1475 for peer_id in num_peers.peer_ids() {
1476 requests.push(Self::make_api_version_request(
1477 Duration::ZERO,
1478 peer_id,
1479 &api,
1480 ));
1481 }
1482
1483 let mut successful_responses = BTreeMap::new();
1484
1485 while successful_responses.len() < num_peers.threshold()
1486 && let Some((peer_id, response)) = requests.next().await
1487 {
1488 let retry = match response {
1489 Err(err) => {
1490 debug!(
1491 target: LOG_CLIENT,
1492 %peer_id,
1493 err = %err.fmt_compact(),
1494 "Failed to fetch API versions from peer"
1495 );
1496 true
1497 }
1498 Ok(response) => {
1499 successful_responses.insert(peer_id, response);
1500 false
1501 }
1502 };
1503
1504 if retry {
1505 requests.push(Self::make_api_version_request(
1506 backoff.next().expect("Keeps retrying"),
1507 peer_id,
1508 &api,
1509 ));
1510 }
1511 }
1512
1513 successful_responses
1514 }
1515
1516 pub async fn fetch_common_api_versions(
1518 config: &ClientConfig,
1519 api: &DynGlobalApi,
1520 ) -> anyhow::Result<BTreeMap<PeerId, SupportedApiVersionsSummary>> {
1521 debug!(
1522 target: LOG_CLIENT,
1523 "Fetching common api versions"
1524 );
1525
1526 let num_peers = NumPeers::from(config.global.api_endpoints.len());
1527
1528 let peer_api_version_sets =
1529 Self::fetch_peers_api_versions_from_threshold_of_peers(num_peers, api.clone()).await;
1530
1531 Ok(peer_api_version_sets)
1532 }
1533
1534 pub async fn write_api_version_cache(
1538 dbtx: &mut DatabaseTransaction<'_>,
1539 api_version_set: ApiVersionSet,
1540 ) {
1541 debug!(
1542 target: LOG_CLIENT,
1543 value = ?api_version_set,
1544 "Writing API version set to cache"
1545 );
1546
1547 dbtx.insert_entry(
1548 &CachedApiVersionSetKey,
1549 &CachedApiVersionSet(api_version_set),
1550 )
1551 .await;
1552 }
1553
1554 pub async fn store_prefetched_api_versions(
1559 db: &Database,
1560 config: &ClientConfig,
1561 client_module_init: &ClientModuleInitRegistry,
1562 peer_api_versions: &BTreeMap<PeerId, SupportedApiVersionsSummary>,
1563 ) {
1564 debug!(
1565 target: LOG_CLIENT,
1566 "Storing {} prefetched peer API version responses and calculating common version set",
1567 peer_api_versions.len()
1568 );
1569
1570 let mut dbtx = db.begin_transaction().await;
1571 let client_supported_versions =
1573 Self::supported_api_versions_summary_static(config, client_module_init);
1574 match fedimint_client_module::api_version_discovery::discover_common_api_versions_set(
1575 &client_supported_versions,
1576 peer_api_versions,
1577 ) {
1578 Ok(common_api_versions) => {
1579 Self::write_api_version_cache(&mut dbtx.to_ref_nc(), common_api_versions).await;
1581 debug!(target: LOG_CLIENT, "Calculated and stored common API version set");
1582 }
1583 Err(err) => {
1584 debug!(target: LOG_CLIENT, err = %err.fmt_compact_anyhow(), "Failed to calculate common API versions from prefetched data");
1585 }
1586 }
1587
1588 for (peer_id, peer_api_versions) in peer_api_versions {
1590 dbtx.insert_entry(
1591 &PeerLastApiVersionsSummaryKey(*peer_id),
1592 &PeerLastApiVersionsSummary(peer_api_versions.clone()),
1593 )
1594 .await;
1595 }
1596 dbtx.commit_tx().await;
1597 debug!(target: LOG_CLIENT, "Stored individual peer API version responses");
1598 }
1599
1600 pub fn supported_api_versions_summary_static(
1602 config: &ClientConfig,
1603 client_module_init: &ClientModuleInitRegistry,
1604 ) -> SupportedApiVersionsSummary {
1605 SupportedApiVersionsSummary {
1606 core: SupportedCoreApiVersions {
1607 core_consensus: config.global.consensus_version,
1608 api: MultiApiVersion::try_from_iter(SUPPORTED_CORE_API_VERSIONS.to_owned())
1609 .expect("must not have conflicting versions"),
1610 },
1611 modules: config
1612 .modules
1613 .iter()
1614 .filter_map(|(&module_instance_id, module_config)| {
1615 client_module_init
1616 .get(module_config.kind())
1617 .map(|module_init| {
1618 (
1619 module_instance_id,
1620 SupportedModuleApiVersions {
1621 core_consensus: config.global.consensus_version,
1622 module_consensus: module_config.version,
1623 api: module_init.supported_api_versions(),
1624 },
1625 )
1626 })
1627 })
1628 .collect(),
1629 }
1630 }
1631
1632 pub async fn load_and_refresh_common_api_version(&self) -> anyhow::Result<ApiVersionSet> {
1633 Self::load_and_refresh_common_api_version_static(
1634 &self.config().await,
1635 &self.module_inits,
1636 self.connectors.clone(),
1637 &self.api,
1638 &self.db,
1639 &self.task_group,
1640 &self.client_span,
1641 )
1642 .await
1643 }
1644
1645 pub async fn refresh_api_versions(&self) -> anyhow::Result<ApiVersionSet> {
1651 Self::refresh_common_api_version_static(
1652 &self.config().await,
1653 &self.module_inits,
1654 &self.api,
1655 &self.db,
1656 self.task_group.clone(),
1657 &self.client_span,
1658 true,
1659 )
1660 .await
1661 }
1662
1663 pub(crate) async fn load_and_refresh_common_api_version_static(
1669 config: &ClientConfig,
1670 module_init: &ClientModuleInitRegistry,
1671 connectors: ConnectorRegistry,
1672 api: &DynGlobalApi,
1673 db: &Database,
1674 task_group: &TaskGroup,
1675 client_span: &Span,
1676 ) -> anyhow::Result<ApiVersionSet> {
1677 if let Some(v) = db
1678 .begin_transaction_nc()
1679 .await
1680 .get_value(&CachedApiVersionSetKey)
1681 .await
1682 {
1683 client_span.in_scope(|| {
1684 debug!(
1685 target: LOG_CLIENT,
1686 "Found existing cached common api versions"
1687 );
1688 });
1689 let config = config.clone();
1690 let client_module_init = module_init.clone();
1691 let api = api.clone();
1692 let db = db.clone();
1693 let task_group = task_group.clone();
1694 let client_span_owned = client_span.clone();
1695 task_group.clone().spawn_cancellable_with_span(
1698 client_span.clone(),
1699 "refresh_common_api_version_static",
1700 async move {
1701 connectors.wait_for_initialized_connections().await;
1702
1703 if let Err(error) = Self::refresh_common_api_version_static(
1704 &config,
1705 &client_module_init,
1706 &api,
1707 &db,
1708 task_group,
1709 &client_span_owned,
1710 false,
1711 )
1712 .await
1713 {
1714 warn!(
1715 target: LOG_CLIENT,
1716 err = %error.fmt_compact_anyhow(), "Failed to discover common api versions"
1717 );
1718 }
1719 },
1720 );
1721
1722 return Ok(v.0);
1723 }
1724
1725 info!(
1726 target: LOG_CLIENT,
1727 "Fetching initial API versions "
1728 );
1729 Self::refresh_common_api_version_static(
1730 config,
1731 module_init,
1732 api,
1733 db,
1734 task_group.clone(),
1735 client_span,
1736 true,
1737 )
1738 .await
1739 }
1740
1741 async fn refresh_common_api_version_static(
1742 config: &ClientConfig,
1743 client_module_init: &ClientModuleInitRegistry,
1744 api: &DynGlobalApi,
1745 db: &Database,
1746 task_group: TaskGroup,
1747 client_span: &Span,
1748 block_until_ok: bool,
1749 ) -> anyhow::Result<ApiVersionSet> {
1750 debug!(
1751 target: LOG_CLIENT,
1752 "Refreshing common api versions"
1753 );
1754
1755 let (num_responses_sender, mut num_responses_receiver) = tokio::sync::watch::channel(0);
1756 let num_peers = NumPeers::from(config.global.api_endpoints.len());
1757
1758 task_group.spawn_cancellable_with_span(
1759 client_span.clone(),
1760 "refresh peers api versions",
1761 Client::fetch_common_api_versions_from_all_peers(
1762 num_peers,
1763 api.clone(),
1764 db.clone(),
1765 num_responses_sender,
1766 ),
1767 );
1768
1769 let common_api_versions = loop {
1770 let _: Result<_, Elapsed> = runtime::timeout(
1778 Duration::from_secs(30),
1779 num_responses_receiver.wait_for(|num| num_peers.threshold() <= *num),
1780 )
1781 .await;
1782
1783 let peer_api_version_sets = Self::load_peers_last_api_versions(db, num_peers).await;
1784
1785 match fedimint_client_module::api_version_discovery::discover_common_api_versions_set(
1786 &Self::supported_api_versions_summary_static(config, client_module_init),
1787 &peer_api_version_sets,
1788 ) {
1789 Ok(o) => break o,
1790 Err(err) if block_until_ok => {
1791 warn!(
1792 target: LOG_CLIENT,
1793 err = %err.fmt_compact_anyhow(),
1794 "Failed to discover API version to use. Retrying..."
1795 );
1796 continue;
1797 }
1798 Err(e) => return Err(e),
1799 }
1800 };
1801
1802 debug!(
1803 target: LOG_CLIENT,
1804 value = ?common_api_versions,
1805 "Updating the cached common api versions"
1806 );
1807 let mut dbtx = db.begin_transaction().await;
1808 let _ = dbtx
1809 .insert_entry(
1810 &CachedApiVersionSetKey,
1811 &CachedApiVersionSet(common_api_versions.clone()),
1812 )
1813 .await;
1814
1815 dbtx.commit_tx().await;
1816
1817 Ok(common_api_versions)
1818 }
1819
1820 pub async fn get_metadata(&self) -> Metadata {
1822 self.db
1823 .begin_transaction_nc()
1824 .await
1825 .get_value(&ClientMetadataKey)
1826 .await
1827 .unwrap_or_else(|| {
1828 warn!(
1829 target: LOG_CLIENT,
1830 "Missing existing metadata. This key should have been set on Client init"
1831 );
1832 Metadata::empty()
1833 })
1834 }
1835
1836 pub async fn set_metadata(&self, metadata: &Metadata) {
1838 self.db
1839 .autocommit::<_, _, anyhow::Error>(
1840 |dbtx, _| {
1841 Box::pin(async {
1842 Self::set_metadata_dbtx(dbtx, metadata).await;
1843 Ok(())
1844 })
1845 },
1846 None,
1847 )
1848 .await
1849 .expect("Failed to autocommit metadata");
1850 }
1851
1852 pub fn has_pending_recoveries(&self) -> bool {
1853 !self
1854 .client_recovery_progress_receiver
1855 .borrow()
1856 .iter()
1857 .all(|(_id, progress)| progress.is_done())
1858 }
1859
1860 pub async fn wait_for_all_recoveries(&self) -> anyhow::Result<()> {
1868 let mut recovery_receiver = self.client_recovery_progress_receiver.clone();
1869 recovery_receiver
1870 .wait_for(|in_progress| {
1871 in_progress
1872 .iter()
1873 .all(|(_id, progress)| progress.is_done())
1874 })
1875 .await
1876 .context("Recovery task completed and update receiver disconnected, but some modules failed to recover")?;
1877
1878 Ok(())
1879 }
1880
1881 pub fn subscribe_to_recovery_progress(
1886 &self,
1887 ) -> impl Stream<Item = (ModuleInstanceId, RecoveryProgress)> + use<> {
1888 WatchStream::new(self.client_recovery_progress_receiver.clone())
1889 .flat_map(futures::stream::iter)
1890 }
1891
1892 pub async fn wait_for_module_kind_recovery(
1893 &self,
1894 module_kind: ModuleKind,
1895 ) -> anyhow::Result<()> {
1896 let mut recovery_receiver = self.client_recovery_progress_receiver.clone();
1897 let config = self.config().await;
1898 recovery_receiver
1899 .wait_for(|in_progress| {
1900 !in_progress
1901 .iter()
1902 .filter(|(module_instance_id, _progress)| {
1903 config.modules[module_instance_id].kind == module_kind
1904 })
1905 .any(|(_id, progress)| !progress.is_done())
1906 })
1907 .await
1908 .context("Recovery task completed and update receiver disconnected, but the desired modules are still unavailable or failed to recover")?;
1909
1910 Ok(())
1911 }
1912
1913 pub async fn wait_for_all_active_state_machines(&self) -> anyhow::Result<()> {
1914 loop {
1915 if self.executor.get_active_states().await.is_empty() {
1916 break;
1917 }
1918 sleep(Duration::from_millis(100)).await;
1919 }
1920 Ok(())
1921 }
1922
1923 pub async fn set_metadata_dbtx(dbtx: &mut DatabaseTransaction<'_>, metadata: &Metadata) {
1925 dbtx.insert_new_entry(&ClientMetadataKey, metadata).await;
1926 }
1927
1928 fn spawn_module_recoveries_task(
1929 &self,
1930 recovery_sender: watch::Sender<BTreeMap<ModuleInstanceId, RecoveryProgress>>,
1931 module_recoveries: BTreeMap<ModuleInstanceId, ModuleRecoveryFuture>,
1932 module_recovery_progress_receivers: BTreeMap<
1933 ModuleInstanceId,
1934 watch::Receiver<RecoveryProgress>,
1935 >,
1936 module_kinds: BTreeMap<ModuleInstanceId, ModuleKind>,
1939 ) {
1940 let db = self.db.clone();
1941 let log_ordering_wakeup_tx = self.log_ordering_wakeup_tx.clone();
1942 self.spawn("module recoveries", |_task_handle| async {
1943 Self::run_module_recoveries_task(
1944 db,
1945 log_ordering_wakeup_tx,
1946 recovery_sender,
1947 module_recoveries,
1948 module_recovery_progress_receivers,
1949 module_kinds,
1950 )
1951 .await;
1952 });
1953 }
1954
1955 async fn run_module_recoveries_task(
1956 db: Database,
1957 log_ordering_wakeup_tx: watch::Sender<()>,
1958 recovery_sender: watch::Sender<BTreeMap<ModuleInstanceId, RecoveryProgress>>,
1959 module_recoveries: BTreeMap<ModuleInstanceId, ModuleRecoveryFuture>,
1960 module_recovery_progress_receivers: BTreeMap<
1961 ModuleInstanceId,
1962 watch::Receiver<RecoveryProgress>,
1963 >,
1964 module_kinds: BTreeMap<ModuleInstanceId, ModuleKind>,
1965 ) {
1966 debug!(target: LOG_CLIENT_RECOVERY, num_modules=%module_recovery_progress_receivers.len(), "Staring module recoveries");
1967
1968 enum RecoveryUpdate {
1972 Progress(RecoveryProgress),
1973 Completed(Option<Amount>),
1974 }
1975
1976 let mut completed_stream = Vec::new();
1977 let progress_stream = futures::stream::FuturesUnordered::new();
1978
1979 for (module_instance_id, f) in module_recoveries {
1980 completed_stream.push(futures::stream::once(Box::pin(async move {
1981 match f.await {
1982 Ok(amount) => (module_instance_id, RecoveryUpdate::Completed(amount)),
1983 Err(err) => {
1984 warn!(
1985 target: LOG_CLIENT,
1986 err = %err.fmt_compact_anyhow(), module_instance_id, "Module recovery failed"
1987 );
1988 futures::future::pending::<()>().await;
1992 unreachable!()
1993 }
1994 }
1995 })));
1996 }
1997
1998 for (module_instance_id, rx) in module_recovery_progress_receivers {
1999 progress_stream.push(
2000 tokio_stream::wrappers::WatchStream::new(rx)
2001 .fuse()
2002 .map(move |progress| (module_instance_id, RecoveryUpdate::Progress(progress))),
2003 );
2004 }
2005
2006 let mut futures = futures::stream::select(
2007 futures::stream::select_all(progress_stream),
2008 futures::stream::select_all(completed_stream),
2009 );
2010
2011 while let Some((module_instance_id, update)) = futures.next().await {
2012 let mut dbtx = db.begin_transaction().await;
2013
2014 let prev_progress = *recovery_sender
2015 .borrow()
2016 .get(&module_instance_id)
2017 .expect("existing progress must be present");
2018
2019 let (progress, recovered_amount) = if prev_progress.is_done() {
2023 (prev_progress, None)
2025 } else {
2026 match update {
2027 RecoveryUpdate::Progress(progress) => (progress, None),
2028 RecoveryUpdate::Completed(amount) => (prev_progress.to_complete(), amount),
2029 }
2030 };
2031
2032 if !prev_progress.is_done() && progress.is_done() {
2033 info!(
2034 target: LOG_CLIENT,
2035 module_instance_id,
2036 progress = format!("{}/{}", progress.complete, progress.total),
2037 amount = ?recovered_amount,
2038 "Recovery complete"
2039 );
2040 dbtx.log_event(
2041 log_ordering_wakeup_tx.clone(),
2042 None,
2043 ModuleRecoveryCompleted {
2044 module_id: module_instance_id,
2045 kind: module_kinds.get(&module_instance_id).cloned(),
2046 amount: recovered_amount,
2047 },
2048 )
2049 .await;
2050 } else {
2051 info!(
2052 target: LOG_CLIENT,
2053 module_instance_id,
2054 kind = ?module_kinds.get(&module_instance_id),
2055 progress = format!("{}/{}", progress.complete, progress.total),
2056 "Recovery progress"
2057 );
2058 }
2059
2060 dbtx.insert_entry(
2061 &ClientModuleRecovery { module_instance_id },
2062 &ClientModuleRecoveryState { progress },
2063 )
2064 .await;
2065 dbtx.commit_tx().await;
2066
2067 recovery_sender.send_modify(|v| {
2068 v.insert(module_instance_id, progress);
2069 });
2070 }
2071 debug!(target: LOG_CLIENT_RECOVERY, "Recovery executor stopped");
2072 }
2073
2074 async fn load_peers_last_api_versions(
2075 db: &Database,
2076 num_peers: NumPeers,
2077 ) -> BTreeMap<PeerId, SupportedApiVersionsSummary> {
2078 let mut peer_api_version_sets = BTreeMap::new();
2079
2080 let mut dbtx = db.begin_transaction_nc().await;
2081 for peer_id in num_peers.peer_ids() {
2082 if let Some(v) = dbtx
2083 .get_value(&PeerLastApiVersionsSummaryKey(peer_id))
2084 .await
2085 {
2086 peer_api_version_sets.insert(peer_id, v.0);
2087 }
2088 }
2089 drop(dbtx);
2090 peer_api_version_sets
2091 }
2092
2093 pub async fn get_peer_url_announcements(&self) -> BTreeMap<PeerId, SignedApiAnnouncement> {
2096 self.db()
2097 .begin_transaction_nc()
2098 .await
2099 .find_by_prefix(&ApiAnnouncementPrefix)
2100 .await
2101 .map(|(announcement_key, announcement)| (announcement_key.0, announcement))
2102 .collect()
2103 .await
2104 }
2105
2106 pub async fn get_guardian_metadata(
2108 &self,
2109 ) -> BTreeMap<PeerId, fedimint_core::net::guardian_metadata::SignedGuardianMetadata> {
2110 self.db()
2111 .begin_transaction_nc()
2112 .await
2113 .find_by_prefix(&crate::guardian_metadata::GuardianMetadataPrefix)
2114 .await
2115 .map(|(key, metadata)| (key.0, metadata))
2116 .collect()
2117 .await
2118 }
2119
2120 pub async fn get_peer_urls(&self) -> BTreeMap<PeerId, SafeUrl> {
2122 get_api_urls(&self.db, &self.config().await, self.iroh_enable_next).await
2123 }
2124
2125 pub async fn invite_code(&self, peer: PeerId) -> Option<InviteCode> {
2128 self.get_peer_urls()
2129 .await
2130 .into_iter()
2131 .find_map(|(peer_id, url)| (peer == peer_id).then_some(url))
2132 .map(|peer_url| {
2133 InviteCode::new(
2134 peer_url.clone(),
2135 peer,
2136 self.federation_id(),
2137 self.api_secret.clone(),
2138 )
2139 })
2140 }
2141
2142 pub async fn get_guardian_public_keys_blocking(
2146 &self,
2147 ) -> BTreeMap<PeerId, fedimint_core::secp256k1::PublicKey> {
2148 self.db
2149 .autocommit(
2150 |dbtx, _| {
2151 Box::pin(async move {
2152 let config = self.config().await;
2153
2154 let guardian_pub_keys = self
2155 .get_or_backfill_broadcast_public_keys(dbtx, config)
2156 .await;
2157
2158 Result::<_, ()>::Ok(guardian_pub_keys)
2159 })
2160 },
2161 None,
2162 )
2163 .await
2164 .expect("Will retry forever")
2165 }
2166
2167 async fn get_or_backfill_broadcast_public_keys(
2168 &self,
2169 dbtx: &mut DatabaseTransaction<'_>,
2170 config: ClientConfig,
2171 ) -> BTreeMap<PeerId, PublicKey> {
2172 match config.global.broadcast_public_keys {
2173 Some(guardian_pub_keys) => guardian_pub_keys,
2174 _ => {
2175 let (guardian_pub_keys, new_config) = self.fetch_and_update_config(config).await;
2176
2177 dbtx.insert_entry(&ClientConfigKey, &new_config).await;
2178 *(self.config.write().await) = new_config;
2179 guardian_pub_keys
2180 }
2181 }
2182 }
2183
2184 pub async fn fetch_session_count(&self) -> FederationResult<u64> {
2185 self.api.session_count().await
2186 }
2187
2188 async fn fetch_and_update_config(
2189 &self,
2190 config: ClientConfig,
2191 ) -> (BTreeMap<PeerId, PublicKey>, ClientConfig) {
2192 let fetched_config = retry(
2193 "Fetching guardian public keys",
2194 backoff_util::background_backoff(),
2195 || async {
2196 Ok(self
2197 .api
2198 .request_current_consensus::<ClientConfig>(
2199 CLIENT_CONFIG_ENDPOINT.to_owned(),
2200 ApiRequestErased::default(),
2201 )
2202 .await?)
2203 },
2204 )
2205 .await
2206 .expect("Will never return on error");
2207
2208 let Some(guardian_pub_keys) = fetched_config.global.broadcast_public_keys else {
2209 warn!(
2210 target: LOG_CLIENT,
2211 "Guardian public keys not found in fetched config, server not updated to 0.4 yet"
2212 );
2213 pending::<()>().await;
2214 unreachable!("Pending will never return");
2215 };
2216
2217 let new_config = ClientConfig {
2218 global: GlobalClientConfig {
2219 broadcast_public_keys: Some(guardian_pub_keys.clone()),
2220 ..config.global
2221 },
2222 modules: config.modules,
2223 };
2224 (guardian_pub_keys, new_config)
2225 }
2226
2227 pub fn handle_global_rpc(
2228 &self,
2229 method: String,
2230 params: serde_json::Value,
2231 ) -> BoxStream<'_, anyhow::Result<serde_json::Value>> {
2232 Box::pin(try_stream! {
2233 match method.as_str() {
2234 "get_balance" => {
2235 let balance = self.get_balance_for_btc().await.unwrap_or_default();
2236 yield serde_json::to_value(balance)?;
2237 }
2238 "subscribe_balance_changes" => {
2239 let req: GetBalanceChangesRequest= serde_json::from_value(params)?;
2240 let mut stream = self.subscribe_balance_changes(req.unit).await;
2241 while let Some(balance) = stream.next().await {
2242 yield serde_json::to_value(balance)?;
2243 }
2244 }
2245 "get_config" => {
2246 let config = self.config().await;
2247 yield serde_json::to_value(config)?;
2248 }
2249 "get_federation_id" => {
2250 let federation_id = self.federation_id();
2251 yield serde_json::to_value(federation_id)?;
2252 }
2253 "get_invite_code" => {
2254 let req: GetInviteCodeRequest = serde_json::from_value(params)?;
2255 let invite_code = self.invite_code(req.peer).await;
2256 yield serde_json::to_value(invite_code)?;
2257 }
2258 "get_operation" => {
2259 let req: GetOperationIdRequest = serde_json::from_value(params)?;
2260 let operation = self.operation_log().get_operation(req.operation_id).await;
2261 yield serde_json::to_value(operation)?;
2262 }
2263 "list_operations" => {
2264 let req: ListOperationsParams = serde_json::from_value(params)?;
2265 let limit = if req.limit.is_none() && req.last_seen.is_none() {
2266 usize::MAX
2267 } else {
2268 req.limit.unwrap_or(usize::MAX)
2269 };
2270 let operations = self.operation_log()
2271 .paginate_operations_rev(limit, req.last_seen)
2272 .await;
2273 yield serde_json::to_value(operations)?;
2274 }
2275 "get_event_log" => {
2276 let req: GetEventLogRequest = serde_json::from_value(params)?;
2277 let limit = req
2278 .limit
2279 .unwrap_or(DEFAULT_EVENT_LOG_PAGE_SIZE)
2280 .min(MAX_EVENT_LOG_PAGE_SIZE);
2281 let events = self.get_event_log(req.pos, limit).await;
2282 yield serde_json::to_value(events)?;
2283 }
2284 "session_count" => {
2285 let count = self.fetch_session_count().await?;
2286 yield serde_json::to_value(count)?;
2287 }
2288 "has_pending_recoveries" => {
2289 let has_pending = self.has_pending_recoveries();
2290 yield serde_json::to_value(has_pending)?;
2291 }
2292 "wait_for_all_recoveries" => {
2293 self.wait_for_all_recoveries().await?;
2294 yield serde_json::Value::Null;
2295 }
2296 "subscribe_to_recovery_progress" => {
2297 let mut stream = self.subscribe_to_recovery_progress();
2298 while let Some((module_id, progress)) = stream.next().await {
2299 yield serde_json::json!({
2300 "module_id": module_id,
2301 "progress": progress
2302 });
2303 }
2304 }
2305 #[allow(deprecated)]
2306 "backup_to_federation" => {
2307 let metadata = if params.is_null() {
2308 Metadata::from_json_serialized(serde_json::json!({}))
2309 } else {
2310 Metadata::from_json_serialized(params)
2311 };
2312 self.backup_to_federation(metadata).await?;
2313 yield serde_json::Value::Null;
2314 }
2315 _ => {
2316 Err(anyhow::format_err!("Unknown method: {}", method))?;
2317 unreachable!()
2318 },
2319 }
2320 })
2321 }
2322
2323 pub async fn log_event<E>(&self, module_id: Option<ModuleInstanceId>, event: E)
2324 where
2325 E: Event + Send,
2326 {
2327 let mut dbtx = self.db.begin_transaction().await;
2328 self.log_event_dbtx(&mut dbtx, module_id, event).await;
2329 dbtx.commit_tx().await;
2330 }
2331
2332 pub async fn log_event_dbtx<E, Cap>(
2333 &self,
2334 dbtx: &mut DatabaseTransaction<'_, Cap>,
2335 module_id: Option<ModuleInstanceId>,
2336 event: E,
2337 ) where
2338 E: Event + Send,
2339 Cap: Send,
2340 {
2341 dbtx.log_event(self.log_ordering_wakeup_tx.clone(), module_id, event)
2342 .await;
2343 }
2344
2345 pub async fn log_event_raw_dbtx<Cap>(
2346 &self,
2347 dbtx: &mut DatabaseTransaction<'_, Cap>,
2348 kind: EventKind,
2349 module: Option<(ModuleKind, ModuleInstanceId)>,
2350 payload: Vec<u8>,
2351 persist: EventPersistence,
2352 ) where
2353 Cap: Send,
2354 {
2355 let module_id = module.as_ref().map(|m| m.1);
2356 let module_kind = module.map(|m| m.0);
2357 dbtx.log_event_raw(
2358 self.log_ordering_wakeup_tx.clone(),
2359 kind,
2360 module_kind,
2361 module_id,
2362 payload,
2363 persist,
2364 )
2365 .await;
2366 }
2367
2368 pub fn built_in_application_event_log_tracker(&self) -> DynEventLogTrimableTracker {
2380 struct BuiltInApplicationEventLogTracker;
2381
2382 #[apply(async_trait_maybe_send!)]
2383 impl EventLogTrimableTracker for BuiltInApplicationEventLogTracker {
2384 async fn store(
2386 &mut self,
2387 dbtx: &mut DatabaseTransaction<NonCommittable>,
2388 pos: EventLogTrimableId,
2389 ) -> anyhow::Result<()> {
2390 dbtx.insert_entry(&DefaultApplicationEventLogKey, &pos)
2391 .await;
2392 Ok(())
2393 }
2394
2395 async fn load(
2397 &mut self,
2398 dbtx: &mut DatabaseTransaction<NonCommittable>,
2399 ) -> anyhow::Result<Option<EventLogTrimableId>> {
2400 Ok(dbtx.get_value(&DefaultApplicationEventLogKey).await)
2401 }
2402 }
2403 Box::new(BuiltInApplicationEventLogTracker)
2404 }
2405
2406 pub async fn handle_historical_events<F, R>(
2414 &self,
2415 tracker: fedimint_eventlog::DynEventLogTracker,
2416 handler_fn: F,
2417 ) -> anyhow::Result<()>
2418 where
2419 F: Fn(&mut DatabaseTransaction<NonCommittable>, EventLogEntry) -> R,
2420 R: Future<Output = anyhow::Result<()>>,
2421 {
2422 fedimint_eventlog::handle_events(
2423 self.db.clone(),
2424 tracker,
2425 self.log_event_added_rx.clone(),
2426 handler_fn,
2427 )
2428 .await
2429 }
2430
2431 pub async fn handle_events<F, R>(
2450 &self,
2451 tracker: fedimint_eventlog::DynEventLogTrimableTracker,
2452 handler_fn: F,
2453 ) -> anyhow::Result<()>
2454 where
2455 F: Fn(&mut DatabaseTransaction<NonCommittable>, EventLogEntry) -> R,
2456 R: Future<Output = anyhow::Result<()>>,
2457 {
2458 fedimint_eventlog::handle_trimable_events(
2459 self.db.clone(),
2460 tracker,
2461 self.log_event_added_rx.clone(),
2462 handler_fn,
2463 )
2464 .await
2465 }
2466
2467 pub async fn get_event_log(
2468 &self,
2469 pos: Option<EventLogId>,
2470 limit: u64,
2471 ) -> Vec<PersistedLogEntry> {
2472 self.get_event_log_dbtx(&mut self.db.begin_transaction_nc().await, pos, limit)
2473 .await
2474 }
2475
2476 pub async fn get_next_event_log_id(&self) -> EventLogId {
2479 self.db
2480 .begin_transaction_nc()
2481 .await
2482 .get_next_event_log_id()
2483 .await
2484 }
2485
2486 pub async fn get_event_log_trimable(
2487 &self,
2488 pos: Option<EventLogTrimableId>,
2489 limit: u64,
2490 ) -> Vec<PersistedLogEntry> {
2491 self.get_event_log_trimable_dbtx(&mut self.db.begin_transaction_nc().await, pos, limit)
2492 .await
2493 }
2494
2495 pub async fn get_event_log_dbtx<Cap>(
2496 &self,
2497 dbtx: &mut DatabaseTransaction<'_, Cap>,
2498 pos: Option<EventLogId>,
2499 limit: u64,
2500 ) -> Vec<PersistedLogEntry>
2501 where
2502 Cap: Send,
2503 {
2504 dbtx.get_event_log(pos, limit).await
2505 }
2506
2507 pub async fn get_event_log_trimable_dbtx<Cap>(
2508 &self,
2509 dbtx: &mut DatabaseTransaction<'_, Cap>,
2510 pos: Option<EventLogTrimableId>,
2511 limit: u64,
2512 ) -> Vec<PersistedLogEntry>
2513 where
2514 Cap: Send,
2515 {
2516 dbtx.get_event_log_trimable(pos, limit).await
2517 }
2518
2519 pub fn get_event_log_transient_receiver(&self) -> broadcast::Receiver<EventLogEntry> {
2521 self.log_event_added_transient_tx.subscribe()
2522 }
2523
2524 pub fn log_event_added_rx(&self) -> watch::Receiver<()> {
2526 self.log_event_added_rx.clone()
2527 }
2528
2529 pub fn iroh_enable_dht(&self) -> bool {
2530 self.iroh_enable_dht
2531 }
2532
2533 pub fn iroh_enable_next(&self) -> bool {
2536 self.iroh_enable_next
2537 }
2538
2539 pub(crate) async fn run_core_migrations(
2540 db_no_decoders: &Database,
2541 ) -> Result<(), anyhow::Error> {
2542 let mut dbtx = db_no_decoders.begin_transaction().await;
2543 apply_migrations_core_client_dbtx(&mut dbtx.to_ref_nc(), "fedimint-client".to_string())
2544 .await?;
2545 if is_running_in_test_env() {
2546 verify_client_db_integrity_dbtx(&mut dbtx.to_ref_nc()).await;
2547 }
2548 dbtx.commit_tx_result().await?;
2549 Ok(())
2550 }
2551
2552 fn primary_modules_for_unit(
2554 &self,
2555 unit: AmountUnit,
2556 ) -> impl Iterator<Item = (ModuleInstanceId, &DynClientModule)> {
2557 self.primary_modules
2558 .iter()
2559 .flat_map(move |(_prio, candidates)| {
2560 candidates
2561 .specific
2562 .get(&unit)
2563 .into_iter()
2564 .flatten()
2565 .copied()
2566 .chain(candidates.wildcard.iter().copied())
2568 })
2569 .map(|id| (id, self.modules.get_expect(id)))
2570 }
2571
2572 pub fn primary_module_for_unit(
2576 &self,
2577 unit: AmountUnit,
2578 ) -> Option<(ModuleInstanceId, &DynClientModule)> {
2579 self.primary_modules_for_unit(unit).next()
2580 }
2581
2582 pub fn primary_module_for_btc(&self) -> (ModuleInstanceId, &DynClientModule) {
2584 self.primary_module_for_unit(AmountUnit::BITCOIN)
2585 .expect("No primary module for Bitcoin")
2586 }
2587}
2588
2589#[apply(async_trait_maybe_send!)]
2590impl ClientContextIface for Client {
2591 fn get_module(&self, instance: ModuleInstanceId) -> &maybe_add_send_sync!(dyn IClientModule) {
2592 Client::get_module(self, instance)
2593 }
2594
2595 fn api_clone(&self) -> DynGlobalApi {
2596 Client::api_clone(self)
2597 }
2598 fn decoders(&self) -> &ModuleDecoderRegistry {
2599 Client::decoders(self)
2600 }
2601
2602 async fn finalize_and_submit_transaction(
2603 &self,
2604 operation_id: OperationId,
2605 operation_type: &str,
2606 operation_meta_gen: Box<maybe_add_send_sync!(dyn Fn(OutPointRange) -> serde_json::Value)>,
2607 tx_builder: TransactionBuilder,
2608 ) -> anyhow::Result<OutPointRange> {
2609 Client::finalize_and_submit_transaction(
2610 self,
2611 operation_id,
2612 operation_type,
2613 &operation_meta_gen,
2615 tx_builder,
2616 )
2617 .await
2618 }
2619
2620 async fn finalize_and_submit_transaction_dbtx(
2621 &self,
2622 dbtx: &mut DatabaseTransaction<'_>,
2623 operation_id: OperationId,
2624 operation_type: &str,
2625 operation_meta_gen: Box<maybe_add_send_sync!(dyn Fn(OutPointRange) -> serde_json::Value)>,
2626 tx_builder: TransactionBuilder,
2627 ) -> anyhow::Result<OutPointRange> {
2628 Client::finalize_and_submit_transaction_dbtx(
2629 self,
2630 dbtx,
2631 operation_id,
2632 operation_type,
2633 &operation_meta_gen,
2634 tx_builder,
2635 )
2636 .await
2637 }
2638
2639 async fn finalize_and_submit_transaction_inner(
2640 &self,
2641 dbtx: &mut DatabaseTransaction<'_>,
2642 operation_id: OperationId,
2643 tx_builder: TransactionBuilder,
2644 ) -> anyhow::Result<OutPointRange> {
2645 Client::finalize_and_submit_transaction_inner(self, dbtx, operation_id, tx_builder).await
2646 }
2647
2648 async fn fee_quote(
2649 &self,
2650 operation_id: OperationId,
2651 request: FeeQuoteRequest,
2652 ) -> anyhow::Result<FeeQuote> {
2653 Client::fee_quote(self, operation_id, request).await
2654 }
2655
2656 async fn get_balance_for_unit(&self, unit: AmountUnit) -> anyhow::Result<Amount> {
2657 Client::get_balance_for_unit(self, unit).await
2658 }
2659
2660 async fn transaction_updates(&self, operation_id: OperationId) -> TransactionUpdates {
2661 Client::transaction_updates(self, operation_id).await
2662 }
2663
2664 async fn await_primary_module_outputs(
2665 &self,
2666 operation_id: OperationId,
2667 outputs: Vec<OutPoint>,
2669 ) -> anyhow::Result<()> {
2670 Client::await_primary_bitcoin_module_outputs(self, operation_id, outputs).await
2671 }
2672
2673 fn operation_log(&self) -> &dyn IOperationLog {
2674 Client::operation_log(self)
2675 }
2676
2677 async fn has_active_states(&self, operation_id: OperationId) -> bool {
2678 Client::has_active_states(self, operation_id).await
2679 }
2680
2681 async fn operation_exists(&self, operation_id: OperationId) -> bool {
2682 Client::operation_exists(self, operation_id).await
2683 }
2684
2685 async fn config(&self) -> ClientConfig {
2686 Client::config(self).await
2687 }
2688
2689 fn db(&self) -> &Database {
2690 Client::db(self)
2691 }
2692
2693 fn executor(&self) -> &(maybe_add_send_sync!(dyn IExecutor + 'static)) {
2694 Client::executor(self)
2695 }
2696
2697 async fn invite_code(&self, peer: PeerId) -> Option<InviteCode> {
2698 Client::invite_code(self, peer).await
2699 }
2700
2701 fn get_internal_payment_markers(&self) -> anyhow::Result<(PublicKey, u64)> {
2702 Client::get_internal_payment_markers(self)
2703 }
2704
2705 async fn log_event_json(
2706 &self,
2707 dbtx: &mut DatabaseTransaction<'_, NonCommittable>,
2708 module_kind: Option<ModuleKind>,
2709 module_id: ModuleInstanceId,
2710 kind: EventKind,
2711 payload: serde_json::Value,
2712 persist: EventPersistence,
2713 ) {
2714 dbtx.ensure_global()
2715 .expect("Must be called with global dbtx");
2716 self.log_event_raw_dbtx(
2717 dbtx,
2718 kind,
2719 module_kind.map(|kind| (kind, module_id)),
2720 serde_json::to_vec(&payload).expect("Serialization can't fail"),
2721 persist,
2722 )
2723 .await;
2724 }
2725
2726 async fn read_operation_active_states<'dbtx>(
2727 &self,
2728 operation_id: OperationId,
2729 module_id: ModuleInstanceId,
2730 dbtx: &'dbtx mut DatabaseTransaction<'_>,
2731 ) -> Pin<Box<maybe_add_send!(dyn Stream<Item = (ActiveStateKey, ActiveStateMeta)> + 'dbtx)>>
2732 {
2733 Box::pin(
2734 dbtx.find_by_prefix(&ActiveModuleOperationStateKeyPrefix {
2735 operation_id,
2736 module_instance: module_id,
2737 })
2738 .await
2739 .map(move |(k, v)| (k.0, v)),
2740 )
2741 }
2742 async fn read_operation_inactive_states<'dbtx>(
2743 &self,
2744 operation_id: OperationId,
2745 module_id: ModuleInstanceId,
2746 dbtx: &'dbtx mut DatabaseTransaction<'_>,
2747 ) -> Pin<Box<maybe_add_send!(dyn Stream<Item = (InactiveStateKey, InactiveStateMeta)> + 'dbtx)>>
2748 {
2749 Box::pin(
2750 dbtx.find_by_prefix(&InactiveModuleOperationStateKeyPrefix {
2751 operation_id,
2752 module_instance: module_id,
2753 })
2754 .await
2755 .map(move |(k, v)| (k.0, v)),
2756 )
2757 }
2758}
2759
2760impl fmt::Debug for Client {
2762 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2763 write!(f, "Client")
2764 }
2765}
2766
2767pub fn client_decoders<'a>(
2768 registry: &ModuleInitRegistry<DynClientModuleInit>,
2769 module_kinds: impl Iterator<Item = (ModuleInstanceId, &'a ModuleKind)>,
2770) -> ModuleDecoderRegistry {
2771 let mut modules = BTreeMap::new();
2772 for (id, kind) in module_kinds {
2773 let Some(init) = registry.get(kind) else {
2774 debug!("Detected configuration for unsupported module id: {id}, kind: {kind}");
2775 continue;
2776 };
2777
2778 modules.insert(
2779 id,
2780 (
2781 kind.clone(),
2782 IClientModuleInit::decoder(AsRef::<dyn IClientModuleInit + 'static>::as_ref(init)),
2783 ),
2784 );
2785 }
2786 ModuleDecoderRegistry::from(modules)
2787}