1use std::collections::{BTreeMap, HashSet};
2use std::convert::Infallible;
3use std::fmt::{self, Formatter};
4use std::future::{Future, pending};
5use std::ops::Range;
6use std::pin::Pin;
7use std::sync::Arc;
8use std::time::{Duration, SystemTime, UNIX_EPOCH};
9
10use async_stream::try_stream;
11use bitcoin::key::Secp256k1;
12use bitcoin::key::rand::thread_rng;
13use bitcoin::secp256k1::{self, PublicKey};
14use fedimint_api_client::api::global_api::with_request_hook::ApiRequestHook;
15use fedimint_api_client::api::{
16 ApiVersionSet, DynGlobalApi, FederationApiExt as _, FederationError, FederationResult,
17 IGlobalFederationApi,
18};
19use fedimint_bitcoind::DynBitcoindRpc;
20use fedimint_client_module::module::recovery::RecoveryProgress;
21use fedimint_client_module::module::{
22 ClientContextIface, ClientModule, ClientModuleRegistry, DynClientModule, FinalClientIface,
23 IClientModule, IdxRange, OutPointRange, PrimaryModulePriority,
24};
25use fedimint_client_module::oplog::IOperationLog;
26use fedimint_client_module::secret::{PlainRootSecretStrategy, RootSecretStrategy as _};
27use fedimint_client_module::sm::executor::{ActiveStateKey, IExecutor, InactiveStateKey};
28use fedimint_client_module::sm::{ActiveStateMeta, DynState, InactiveStateMeta};
29use fedimint_client_module::transaction::{
30 FeeQuote, FeeQuoteRequest, TRANSACTION_SUBMISSION_MODULE_INSTANCE, TransactionBuilder,
31 TxSubmissionStates, TxSubmissionStatesSM,
32};
33use fedimint_client_module::{
34 AddStateMachinesResult, ClientModuleInstance, GetInviteCodeRequest, ModuleGlobalContextGen,
35 ModuleRecoveryCompleted, TransactionUpdates, TxCreatedEvent,
36};
37use fedimint_connectors::{ConnectorRegistry, PeerStatus};
38use fedimint_core::config::{
39 ClientConfig, FederationId, GlobalClientConfig, JsonClientConfig, ModuleInitRegistry,
40};
41use fedimint_core::core::{DynInput, DynOutput, ModuleInstanceId, ModuleKind, OperationId};
42use fedimint_core::db::{
43 AutocommitError, Database, DatabaseRecord, DatabaseTransaction, DbMigrationError,
44 IDatabaseTransactionOpsCore as _, IDatabaseTransactionOpsCoreTyped as _, NonCommittable,
45};
46use fedimint_core::encoding::{Decodable, Encodable};
47use fedimint_core::endpoint_constants::{CLIENT_CONFIG_ENDPOINT, VERSION_ENDPOINT};
48use fedimint_core::envs::is_running_in_test_env;
49use fedimint_core::invite_code::InviteCode;
50use fedimint_core::module::registry::{ModuleDecoderRegistry, ModuleRegistry};
51use fedimint_core::module::{
52 AmountUnit, Amounts, ApiRequestErased, ApiVersion, MultiApiVersion,
53 SupportedApiVersionsSummary, SupportedCoreApiVersions, SupportedModuleApiVersions,
54};
55use fedimint_core::net::api_announcement::SignedApiAnnouncement;
56use fedimint_core::runtime::sleep;
57use fedimint_core::task::{
58 Elapsed, MaybeSend, MaybeSync, ShuttingDownError, TaskGroup, TaskHandle,
59};
60use fedimint_core::transaction::Transaction;
61use fedimint_core::util::backoff_util::custom_backoff;
62use fedimint_core::util::{BoxStream, FmtCompact as _, SafeUrl, backoff_util, retry};
63use fedimint_core::{
64 Amount, ChainId, NumPeers, OutPoint, PeerId, apply, async_trait_maybe_send, maybe_add_send,
65 maybe_add_send_sync, runtime,
66};
67use fedimint_derive_secret::DerivableSecret;
68use fedimint_eventlog::{
69 DBTransactionEventLogExt as _, DynEventLogTrimableTracker, Event, EventHandlerError, EventKind,
70 EventLogEntry, EventLogId, EventLogTrackerError, EventLogTrimableId, EventLogTrimableTracker,
71 EventPersistence, PersistedLogEntry,
72};
73use fedimint_logging::{LOG_CLIENT, LOG_CLIENT_NET_API, LOG_CLIENT_RECOVERY};
74use futures::stream::FuturesUnordered;
75use futures::{Stream, StreamExt as _};
76use global_ctx::ModuleGlobalClientContext;
77use serde::{Deserialize, Serialize};
78use tokio::sync::{broadcast, oneshot, watch};
79use tokio_stream::wrappers::WatchStream;
80use tracing::{Span, debug, info, warn};
81
82use crate::ClientBuilder;
83use crate::api_announcements::{ApiAnnouncementPrefix, get_api_urls};
84use crate::backup::Metadata;
85use crate::client::event_log::DefaultApplicationEventLogKey;
86use crate::db::{
87 ApiSecretKey, CachedApiVersionSet, CachedApiVersionSetKey, ChainIdKey,
88 ChronologicalOperationLogKey, ClientConfigKey, ClientMetadataKey, ClientModuleRecovery,
89 ClientModuleRecoveryState, EncodedClientSecretKey, OperationLogKey, PeerLastApiVersionsSummary,
90 PeerLastApiVersionsSummaryKey, PendingClientConfigKey, TransactionFeesKey,
91 apply_migrations_core_client_dbtx, verify_client_db_integrity_dbtx,
92};
93use crate::error::{
94 ApiVersionDiscoveryError, ClientModuleError, ClientSecretError, GlobalRpcError,
95 ModuleLookupError, OperationAlreadyExistsError, OperationNotFoundError, RecoveryError,
96 TransactionSubmitError,
97};
98use crate::meta::MetaService;
99use crate::module_init::{ClientModuleInitRegistry, DynClientModuleInit, IClientModuleInit};
100use crate::oplog::OperationLog;
101use crate::sm::executor::{
102 ActiveModuleOperationStateKeyPrefix, ActiveOperationStateKeyPrefix, Executor,
103 InactiveModuleOperationStateKeyPrefix, InactiveOperationStateKeyPrefix,
104};
105
106pub(crate) mod builder;
107pub(crate) mod event_log;
108pub(crate) mod global_ctx;
109pub(crate) mod handle;
110
111#[cfg(test)]
112mod tests;
113
114const SUPPORTED_CORE_API_VERSIONS: &[fedimint_core::module::ApiVersion] =
118 &[ApiVersion { major: 0, minor: 0 }];
119
120struct FinalizedTransaction {
121 transaction: Transaction,
122 states: Vec<DynState>,
123 change_range: Range<u64>,
124 fees: Amounts,
125}
126
127#[derive(Default)]
129pub(crate) struct PrimaryModuleCandidates {
130 specific: BTreeMap<AmountUnit, Vec<ModuleInstanceId>>,
132 wildcard: Vec<ModuleInstanceId>,
134}
135
136pub(crate) type ModuleRecoveryFuture =
139 Pin<Box<maybe_add_send!(dyn Future<Output = Result<Option<Amount>, ClientModuleError>>)>>;
140
141#[derive(Clone, Debug)]
166pub(crate) enum RecoveryStatus {
167 InProgress(RecoveryProgress),
171 Failed {
175 last_progress: RecoveryProgress,
176 error: Arc<ClientModuleError>,
177 },
178}
179
180impl RecoveryStatus {
181 pub(crate) fn is_successfully_done(&self) -> bool {
186 match self {
187 Self::InProgress(progress) => progress.is_done(),
188 Self::Failed { .. } => false,
189 }
190 }
191
192 pub(crate) fn progress(&self) -> RecoveryProgress {
195 match self {
196 Self::InProgress(progress)
197 | Self::Failed {
198 last_progress: progress,
199 ..
200 } => *progress,
201 }
202 }
203}
204
205pub struct Client {
219 final_client: FinalClientIface,
220 config: tokio::sync::RwLock<ClientConfig>,
221 api_secret: Option<String>,
222 decoders: ModuleDecoderRegistry,
223 connectors: ConnectorRegistry,
224 db: Database,
225 federation_id: FederationId,
226 federation_config_meta: BTreeMap<String, String>,
227 primary_modules: BTreeMap<PrimaryModulePriority, PrimaryModuleCandidates>,
228 pub(crate) modules: ClientModuleRegistry,
229 module_inits: ClientModuleInitRegistry,
230 executor: Executor,
231 pub(crate) api: DynGlobalApi,
232 root_secret: DerivableSecret,
233 operation_log: OperationLog,
234 secp_ctx: Secp256k1<secp256k1::All>,
235 meta_service: Arc<MetaService>,
236
237 task_group: TaskGroup,
238
239 client_span: Span,
243
244 client_recovery_status_receiver: watch::Receiver<BTreeMap<ModuleInstanceId, RecoveryStatus>>,
257
258 log_ordering_wakeup_tx: watch::Sender<()>,
261 log_event_added_rx: watch::Receiver<()>,
263 log_event_added_transient_tx: broadcast::Sender<EventLogEntry>,
264 request_hook: ApiRequestHook,
265 iroh_enable_dht: bool,
266 iroh_enable_next: bool,
267 #[allow(dead_code)]
272 user_bitcoind_rpc: Option<DynBitcoindRpc>,
273 pub(crate) user_bitcoind_rpc_no_chain_id:
278 Option<fedimint_client_module::module::init::BitcoindRpcNoChainIdFactory>,
279}
280
281#[derive(Debug, Serialize, Deserialize)]
282struct ListOperationsParams {
283 limit: Option<usize>,
284 last_seen: Option<ChronologicalOperationLogKey>,
285}
286
287pub const DEFAULT_EVENT_LOG_PAGE_SIZE: u64 = 100;
288pub const MAX_EVENT_LOG_PAGE_SIZE: u64 = 10_000;
289
290#[derive(Debug, Clone, Serialize, Deserialize)]
291struct GetEventLogRequest {
292 pos: Option<EventLogId>,
293 limit: Option<u64>,
294}
295
296#[derive(Debug, Clone, Serialize, Deserialize)]
297pub struct GetOperationIdRequest {
298 operation_id: OperationId,
299}
300
301#[derive(Debug, Clone, Serialize, Deserialize)]
302pub struct GetBalanceChangesRequest {
303 #[serde(default = "AmountUnit::bitcoin")]
304 unit: AmountUnit,
305}
306
307impl Client {
308 pub async fn builder() -> ClientBuilder {
313 ClientBuilder::new()
314 }
315
316 pub fn api(&self) -> &(dyn IGlobalFederationApi + 'static) {
317 self.api.as_ref()
318 }
319
320 pub fn api_clone(&self) -> DynGlobalApi {
321 self.api.clone()
322 }
323
324 pub fn connection_status_stream(&self) -> impl Stream<Item = BTreeMap<PeerId, PeerStatus>> {
327 self.api.connection_status_stream()
328 }
329
330 pub fn federation_reconnect(&self) {
338 let peers: Vec<PeerId> = self.api.all_peers().iter().copied().collect();
339
340 for peer_id in peers {
341 let api = self.api.clone();
342 self.spawn_cancellable(format!("federation-reconnect-once-{peer_id}"), async move {
343 if let Err(e) = api.get_peer_connection(peer_id).await {
344 debug!(
345 target: LOG_CLIENT_NET_API,
346 %peer_id,
347 err = %e.fmt_compact(),
348 "Failed to connect to peer"
349 );
350 }
351 });
352 }
353 }
354
355 pub fn spawn_federation_reconnect(&self) {
377 let peers: Vec<PeerId> = self.api.all_peers().iter().copied().collect();
378
379 for peer_id in peers {
380 let api = self.api.clone();
381 self.spawn_cancellable(format!("federation-reconnect-{peer_id}"), async move {
382 loop {
383 match api.get_peer_connection(peer_id).await {
384 Ok(conn) => {
385 conn.await_disconnection().await;
386 }
387 Err(e) => {
388 debug!(
391 target: LOG_CLIENT_NET_API,
392 %peer_id,
393 err = %e.fmt_compact(),
394 "Failed to connect to peer, will retry"
395 );
396 }
397 }
398 }
399 });
400 }
401 }
402
403 pub fn task_group(&self) -> &TaskGroup {
405 &self.task_group
406 }
407
408 pub(crate) fn make_client_span(federation_id: FederationId) -> Span {
415 tracing::info_span!(
416 target: LOG_CLIENT,
417 parent: None,
418 "client",
419 fed_id = %federation_id.to_prefix(),
420 )
421 }
422
423 pub(crate) fn spawn_cancellable<R>(
426 &self,
427 name: impl Into<String>,
428 future: impl Future<Output = R> + MaybeSend + 'static,
429 ) -> oneshot::Receiver<Result<R, ShuttingDownError>>
430 where
431 R: MaybeSend + 'static,
432 {
433 self.task_group
434 .spawn_cancellable_with_span(self.client_span.clone(), name, future)
435 }
436
437 pub(crate) fn spawn<Fut, R>(
441 &self,
442 name: impl Into<String>,
443 f: impl FnOnce(TaskHandle) -> Fut + MaybeSend + 'static,
444 ) -> oneshot::Receiver<R>
445 where
446 Fut: Future<Output = R> + MaybeSend + 'static,
447 R: MaybeSend + 'static,
448 {
449 self.task_group
450 .spawn_with_span(self.client_span.clone(), name, f)
451 }
452
453 pub fn get_metrics() -> Result<String, fedimint_metrics::prometheus::Error> {
458 fedimint_metrics::get_metrics()
459 }
460
461 #[doc(hidden)]
463 pub fn executor(&self) -> &Executor {
464 &self.executor
465 }
466
467 pub async fn get_config_from_db(db: &Database) -> Option<ClientConfig> {
468 let mut dbtx = db.begin_transaction_nc().await;
469 dbtx.get_value(&ClientConfigKey).await
470 }
471
472 pub async fn get_pending_config_from_db(db: &Database) -> Option<ClientConfig> {
473 let mut dbtx = db.begin_transaction_nc().await;
474 dbtx.get_value(&PendingClientConfigKey).await
475 }
476
477 pub async fn get_api_secret_from_db(db: &Database) -> Option<String> {
478 let mut dbtx = db.begin_transaction_nc().await;
479 dbtx.get_value(&ApiSecretKey).await
480 }
481
482 pub async fn store_encodable_client_secret<T: Encodable>(
483 db: &Database,
484 secret: T,
485 ) -> Result<(), ClientSecretError> {
486 let mut dbtx = db.begin_transaction().await;
487
488 if dbtx.get_value(&EncodedClientSecretKey).await.is_some() {
490 return Err(ClientSecretError::AlreadyExists);
491 }
492
493 let encoded_secret = T::consensus_encode_to_vec(&secret);
494 dbtx.insert_entry(&EncodedClientSecretKey, &encoded_secret)
495 .await;
496 dbtx.commit_tx().await;
497 Ok(())
498 }
499
500 pub async fn load_decodable_client_secret<T: Decodable>(
501 db: &Database,
502 ) -> Result<T, ClientSecretError> {
503 let Some(secret) = Self::load_decodable_client_secret_opt(db).await? else {
504 return Err(ClientSecretError::NotPresent);
505 };
506
507 Ok(secret)
508 }
509
510 pub async fn load_decodable_client_secret_opt<T: Decodable>(
511 db: &Database,
512 ) -> Result<Option<T>, ClientSecretError> {
513 let mut dbtx = db.begin_transaction_nc().await;
514
515 let client_secret = dbtx.get_value(&EncodedClientSecretKey).await;
516
517 Ok(match client_secret {
518 Some(client_secret) => Some(T::consensus_decode_whole(
519 &client_secret,
520 &ModuleRegistry::default(),
521 )?),
522 None => None,
523 })
524 }
525
526 pub async fn load_or_generate_client_secret(db: &Database) -> [u8; 64] {
527 match Self::load_decodable_client_secret::<[u8; 64]>(db).await {
528 Ok(secret) => secret,
529 _ => {
530 let secret = PlainRootSecretStrategy::random(&mut thread_rng());
531 Self::store_encodable_client_secret(db, secret)
532 .await
533 .expect("Storing client secret must work");
534 secret
535 }
536 }
537 }
538
539 pub async fn is_initialized(db: &Database) -> bool {
540 let mut dbtx = db.begin_transaction_nc().await;
541 dbtx.raw_get_bytes(&[ClientConfigKey::DB_PREFIX])
542 .await
543 .expect("Unrecoverable error occurred while reading and entry from the database")
544 .is_some()
545 }
546
547 pub fn start_executor(self: &Arc<Self>) {
548 self.client_span.in_scope(|| {
549 debug!(
550 target: LOG_CLIENT,
551 "Starting fedimint client executor",
552 );
553 });
554 self.executor
555 .start_executor(self.context_gen(), self.client_span.clone());
556 }
557
558 pub fn federation_id(&self) -> FederationId {
559 self.federation_id
560 }
561
562 fn context_gen(self: &Arc<Self>) -> ModuleGlobalContextGen {
563 let client_inner = Arc::downgrade(self);
564 Arc::new(move |module_instance, operation| {
565 ModuleGlobalClientContext {
566 client: client_inner
567 .clone()
568 .upgrade()
569 .expect("ModuleGlobalContextGen called after client was dropped"),
570 module_instance_id: module_instance,
571 operation,
572 }
573 .into()
574 })
575 }
576
577 pub async fn config(&self) -> ClientConfig {
578 self.config.read().await.clone()
579 }
580
581 pub fn api_secret(&self) -> &Option<String> {
583 &self.api_secret
584 }
585
586 pub async fn core_api_version(&self) -> ApiVersion {
592 self.db
595 .begin_transaction_nc()
596 .await
597 .get_value(&CachedApiVersionSetKey)
598 .await
599 .map(|cached: CachedApiVersionSet| cached.0.core)
600 .unwrap_or(ApiVersion { major: 0, minor: 0 })
601 }
602
603 pub async fn chain_id(&self) -> Result<ChainId, FederationError> {
610 if let Some(chain_id) = self
612 .db
613 .begin_transaction_nc()
614 .await
615 .get_value(&ChainIdKey)
616 .await
617 {
618 return Ok(chain_id);
619 }
620
621 let chain_id = self.api.chain_id().await?;
623
624 let mut dbtx = self.db.begin_transaction().await;
626 dbtx.insert_entry(&ChainIdKey, &chain_id).await;
627 dbtx.commit_tx().await;
628
629 Ok(chain_id)
630 }
631
632 pub fn decoders(&self) -> &ModuleDecoderRegistry {
633 &self.decoders
634 }
635
636 fn get_module(&self, instance: ModuleInstanceId) -> &maybe_add_send_sync!(dyn IClientModule) {
638 self.try_get_module(instance)
639 .expect("Module instance not found")
640 }
641
642 fn try_get_module(
643 &self,
644 instance: ModuleInstanceId,
645 ) -> Option<&maybe_add_send_sync!(dyn IClientModule)> {
646 Some(self.modules.get(instance)?.as_ref())
647 }
648
649 pub fn has_module(&self, instance: ModuleInstanceId) -> bool {
650 self.modules.get(instance).is_some()
651 }
652
653 fn transaction_builder_get_balance(&self, builder: &TransactionBuilder) -> (Amounts, Amounts) {
659 let mut in_amounts = Amounts::ZERO;
661 let mut out_amounts = Amounts::ZERO;
662 let mut fee_amounts = Amounts::ZERO;
663
664 for input in builder.inputs() {
665 let module = self.get_module(input.input.module_instance_id());
666
667 let item_fees = module.input_fee(&input.amounts, &input.input).expect(
668 "We only build transactions with input versions that are supported by the module",
669 );
670
671 in_amounts.checked_add_mut(&input.amounts);
672 fee_amounts.checked_add_mut(&item_fees);
673 }
674
675 for output in builder.outputs() {
676 let module = self.get_module(output.output.module_instance_id());
677
678 let item_fees = module.output_fee(&output.amounts, &output.output).expect(
679 "We only build transactions with output versions that are supported by the module",
680 );
681
682 out_amounts.checked_add_mut(&output.amounts);
683 fee_amounts.checked_add_mut(&item_fees);
684 }
685
686 out_amounts.checked_add_mut(&fee_amounts);
687 (in_amounts, out_amounts)
688 }
689
690 pub fn get_internal_payment_markers(
691 &self,
692 ) -> Result<(PublicKey, u64), bitcoin::secp256k1::Error> {
693 Ok((self.federation_id().to_fake_ln_pub_key(&self.secp_ctx)?, 0))
694 }
695
696 pub fn get_config_meta(&self, key: &str) -> Option<String> {
698 self.federation_config_meta.get(key).cloned()
699 }
700
701 pub(crate) fn root_secret(&self) -> DerivableSecret {
702 self.root_secret.clone()
703 }
704
705 pub async fn add_state_machines(
706 &self,
707 dbtx: &mut DatabaseTransaction<'_>,
708 states: Vec<DynState>,
709 ) -> AddStateMachinesResult {
710 self.executor.add_state_machines_dbtx(dbtx, states).await
711 }
712
713 pub async fn get_active_operations(&self) -> HashSet<OperationId> {
715 let active_states = self.executor.get_active_states().await;
716 let mut active_operations = HashSet::with_capacity(active_states.len());
717 let mut dbtx = self.db().begin_transaction_nc().await;
718 for (state, _) in active_states {
719 let operation_id = state.operation_id();
720 if dbtx
721 .get_value(&OperationLogKey { operation_id })
722 .await
723 .is_some()
724 {
725 active_operations.insert(operation_id);
726 }
727 }
728 active_operations
729 }
730
731 pub fn operation_log(&self) -> &OperationLog {
732 &self.operation_log
733 }
734
735 pub fn meta_service(&self) -> &Arc<MetaService> {
737 &self.meta_service
738 }
739
740 pub async fn get_meta_expiration_timestamp(&self) -> Option<SystemTime> {
742 let meta_service = self.meta_service();
743 let ts = meta_service
744 .get_field::<u64>(self.db(), "federation_expiry_timestamp")
745 .await
746 .and_then(|v| v.value)?;
747 Some(UNIX_EPOCH + Duration::from_secs(ts))
748 }
749
750 async fn finalize_transaction(
752 &self,
753 dbtx: &mut DatabaseTransaction<'_>,
754 operation_id: OperationId,
755 mut partial_transaction: TransactionBuilder,
756 ) -> Result<FinalizedTransaction, TransactionSubmitError> {
757 let (in_amounts, out_amounts) = self.transaction_builder_get_balance(&partial_transaction);
758
759 let mut added_inputs_bundles = vec![];
760 let mut added_outputs_bundles = vec![];
761
762 for unit in in_amounts.units().union(&out_amounts.units()) {
773 let input_amount = in_amounts.get(unit).copied().unwrap_or_default();
774 let output_amount = out_amounts.get(unit).copied().unwrap_or_default();
775 if input_amount == output_amount {
776 continue;
777 }
778
779 let Some((module_id, module)) = self.primary_module_for_unit(*unit) else {
780 return Err(TransactionSubmitError::NoPrimaryModule { unit: *unit });
781 };
782
783 let (added_input_bundle, added_output_bundle) = module
784 .create_final_inputs_and_outputs(
785 module_id,
786 dbtx,
787 operation_id,
788 *unit,
789 input_amount,
790 output_amount,
791 )
792 .await?;
793
794 added_inputs_bundles.push(added_input_bundle);
795 added_outputs_bundles.push(added_output_bundle);
796 }
797
798 let change_range = Range {
802 start: partial_transaction.outputs().count() as u64,
803 end: (partial_transaction.outputs().count() as u64
804 + added_outputs_bundles
805 .iter()
806 .map(|output| output.outputs().len() as u64)
807 .sum::<u64>()),
808 };
809
810 for added_inputs in added_inputs_bundles {
811 partial_transaction = partial_transaction.with_inputs(added_inputs);
812 }
813
814 for added_outputs in added_outputs_bundles {
815 partial_transaction = partial_transaction.with_outputs(added_outputs);
816 }
817
818 let (input_amounts, output_amounts) =
819 self.transaction_builder_get_balance(&partial_transaction);
820
821 for (unit, output_amount) in output_amounts {
822 let input_amount = input_amounts.get(&unit).copied().unwrap_or_default();
823
824 assert!(input_amount >= output_amount, "Transaction is underfunded");
825 }
826
827 let fees = {
831 let mut input_total = Amounts::ZERO;
832 for input in partial_transaction.inputs() {
833 input_total
834 .checked_add_mut(&input.amounts)
835 .expect("Own transaction amounts don't overflow");
836 }
837 let mut output_total = Amounts::ZERO;
838 for output in partial_transaction.outputs() {
839 output_total
840 .checked_add_mut(&output.amounts)
841 .expect("Own transaction amounts don't overflow");
842 }
843 input_total
844 .checked_sub(&output_total)
845 .expect("Inputs >= outputs for own transactions")
846 };
847
848 let (transaction, states) = partial_transaction.build(&self.secp_ctx, thread_rng());
849
850 Ok(FinalizedTransaction {
851 transaction,
852 states,
853 change_range,
854 fees,
855 })
856 }
857
858 pub async fn fee_quote(
878 &self,
879 operation_id: OperationId,
880 request: FeeQuoteRequest,
881 ) -> Result<FeeQuote, TransactionSubmitError> {
882 let FeeQuoteRequest {
883 input_amount,
884 output_amount,
885 input_fee,
886 output_fee,
887 } = request;
888
889 let mut gross_input = input_amount.clone();
893 let mut gross_output = output_amount.clone();
894 let mut input_fees = input_fee.clone();
895 let mut output_fees = output_fee.clone();
896
897 let balance_input = input_amount;
903 let balance_output = output_amount
904 .checked_add(&input_fee)
905 .and_then(|amounts| amounts.checked_add(&output_fee))
906 .expect("explicit amounts and fees cannot overflow an Amounts");
907
908 let mut dbtx = self.db.begin_transaction_nc().await;
912
913 for unit in balance_input.units().union(&balance_output.units()) {
916 let balance_input_amount = balance_input.get(unit).copied().unwrap_or_default();
917 let balance_output_amount = balance_output.get(unit).copied().unwrap_or_default();
918 if balance_input_amount == balance_output_amount {
919 continue;
920 }
921
922 let Some((module_id, module)) = self.primary_module_for_unit(*unit) else {
923 return Err(TransactionSubmitError::NoPrimaryModule { unit: *unit });
924 };
925
926 let (change_input, change_output) = module
927 .create_final_inputs_and_outputs(
928 module_id,
929 &mut dbtx.to_ref_nc(),
930 operation_id,
931 *unit,
932 balance_input_amount,
933 balance_output_amount,
934 )
935 .await?;
936
937 for input in change_input.inputs() {
943 let module = self.get_module(input.input.module_instance_id());
944 let fee = module
945 .input_fee(&input.amounts, &input.input)
946 .expect("Primary module must know its own change input fees");
947 gross_input.checked_add_mut(&input.amounts);
948 input_fees.checked_add_mut(&fee);
949 }
950
951 for output in change_output.outputs() {
952 let module = self.get_module(output.output.module_instance_id());
953 let fee = module
954 .output_fee(&output.amounts, &output.output)
955 .expect("Primary module must know its own change output fees");
956 gross_output.checked_add_mut(&output.amounts);
957 output_fees.checked_add_mut(&fee);
958 }
959 }
960
961 dbtx.ignore_uncommitted();
964
965 let mut dust = Amounts::ZERO;
970 for unit in gross_input.units().union(&gross_output.units()) {
971 let total = gross_input
972 .get(unit)
973 .copied()
974 .unwrap_or_default()
975 .saturating_sub(gross_output.get(unit).copied().unwrap_or_default());
976 let fees = input_fees.get(unit).copied().unwrap_or_default()
977 + output_fees.get(unit).copied().unwrap_or_default();
978 dust = dust
979 .checked_add_unit(total.saturating_sub(fees), *unit)
980 .expect("dust cannot overflow an Amounts");
981 }
982
983 Ok(FeeQuote {
984 input: input_fees,
985 output: output_fees,
986 dust,
987 })
988 }
989
990 pub async fn finalize_and_submit_transaction<F, M>(
1024 &self,
1025 operation_id: OperationId,
1026 operation_type: &str,
1027 operation_meta_gen: F,
1028 tx_builder: TransactionBuilder,
1029 ) -> Result<OutPointRange, TransactionSubmitError>
1030 where
1031 F: Fn(OutPointRange) -> M + Clone + MaybeSend + MaybeSync,
1032 M: serde::Serialize + MaybeSend,
1033 {
1034 let operation_type = operation_type.to_owned();
1035
1036 let autocommit_res = self
1037 .db
1038 .autocommit(
1039 |dbtx, _| {
1040 let operation_type = operation_type.clone();
1041 let tx_builder = tx_builder.clone();
1042 let operation_meta_gen = operation_meta_gen.clone();
1043 Box::pin(async move {
1044 self.finalize_and_submit_transaction_dbtx(
1045 dbtx,
1046 operation_id,
1047 &operation_type,
1048 operation_meta_gen,
1049 tx_builder,
1050 )
1051 .await
1052 })
1053 },
1054 Some(100), )
1056 .await;
1057
1058 match autocommit_res {
1059 Ok(txid) => Ok(txid),
1060 Err(AutocommitError::ClosureError { error, .. }) => Err(error),
1061 Err(AutocommitError::CommitFailed { last_error, .. }) => {
1062 Err(TransactionSubmitError::Database(last_error))
1063 }
1064 }
1065 }
1066
1067 pub async fn finalize_and_submit_transaction_dbtx<F, M>(
1076 &self,
1077 dbtx: &mut DatabaseTransaction<'_>,
1078 operation_id: OperationId,
1079 operation_type: &str,
1080 operation_meta_gen: F,
1081 tx_builder: TransactionBuilder,
1082 ) -> Result<OutPointRange, TransactionSubmitError>
1083 where
1084 F: FnOnce(OutPointRange) -> M + MaybeSend,
1085 M: serde::Serialize + MaybeSend,
1086 {
1087 if Client::operation_exists_dbtx(dbtx, operation_id).await {
1088 return Err(OperationAlreadyExistsError { operation_id }.into());
1089 }
1090
1091 let out_point_range = self
1092 .finalize_and_submit_transaction_inner(dbtx, operation_id, tx_builder)
1093 .await?;
1094
1095 self.operation_log()
1096 .add_operation_log_entry_dbtx(
1097 dbtx,
1098 operation_id,
1099 operation_type,
1100 operation_meta_gen(out_point_range),
1101 )
1102 .await;
1103
1104 Ok(out_point_range)
1105 }
1106
1107 async fn finalize_and_submit_transaction_inner(
1108 &self,
1109 dbtx: &mut DatabaseTransaction<'_>,
1110 operation_id: OperationId,
1111 tx_builder: TransactionBuilder,
1112 ) -> Result<OutPointRange, TransactionSubmitError> {
1113 let FinalizedTransaction {
1114 transaction,
1115 mut states,
1116 change_range,
1117 fees,
1118 } = self
1119 .finalize_transaction(&mut dbtx.to_ref_nc(), operation_id, tx_builder)
1120 .await?;
1121
1122 if transaction.consensus_encode_to_vec().len() > Transaction::MAX_TX_SIZE {
1123 let inputs = transaction
1124 .inputs
1125 .iter()
1126 .map(DynInput::module_instance_id)
1127 .collect::<Vec<_>>();
1128 let outputs = transaction
1129 .outputs
1130 .iter()
1131 .map(DynOutput::module_instance_id)
1132 .collect::<Vec<_>>();
1133 warn!(
1134 target: LOG_CLIENT_NET_API,
1135 size=%transaction.consensus_encode_to_vec().len(),
1136 ?inputs,
1137 ?outputs,
1138 "Transaction too large",
1139 );
1140 debug!(target: LOG_CLIENT_NET_API, ?transaction, "transaction details");
1141 return Err(TransactionSubmitError::TransactionTooLarge {
1142 size: transaction.consensus_encode_to_vec().len(),
1143 max: Transaction::MAX_TX_SIZE,
1144 });
1145 }
1146
1147 let txid = transaction.tx_hash();
1148
1149 debug!(
1150 target: LOG_CLIENT_NET_API,
1151 %txid,
1152 operation_id = %operation_id.fmt_short(),
1153 ?transaction,
1154 "Finalized and submitting transaction",
1155 );
1156
1157 let tx_submission_sm = DynState::from_typed(
1158 TRANSACTION_SUBMISSION_MODULE_INSTANCE,
1159 TxSubmissionStatesSM {
1160 operation_id,
1161 state: TxSubmissionStates::Created(transaction),
1162 },
1163 );
1164 states.push(tx_submission_sm);
1165
1166 self.executor.add_state_machines_dbtx(dbtx, states).await?;
1167
1168 dbtx.insert_new_entry(&TransactionFeesKey(txid), &fees)
1169 .await;
1170
1171 self.log_event_dbtx(dbtx, None, TxCreatedEvent { txid, operation_id })
1172 .await;
1173
1174 Ok(OutPointRange::new(txid, IdxRange::from(change_range)))
1175 }
1176
1177 async fn transaction_update_stream(
1178 &self,
1179 operation_id: OperationId,
1180 ) -> BoxStream<'static, TxSubmissionStatesSM> {
1181 self.executor
1182 .notifier()
1183 .module_notifier::<TxSubmissionStatesSM>(
1184 TRANSACTION_SUBMISSION_MODULE_INSTANCE,
1185 self.final_client.clone(),
1186 )
1187 .subscribe(operation_id)
1188 .await
1189 }
1190
1191 pub async fn operation_exists(&self, operation_id: OperationId) -> bool {
1192 let mut dbtx = self.db().begin_transaction_nc().await;
1193
1194 Client::operation_exists_dbtx(&mut dbtx, operation_id).await
1195 }
1196
1197 pub async fn operation_exists_dbtx(
1198 dbtx: &mut DatabaseTransaction<'_>,
1199 operation_id: OperationId,
1200 ) -> bool {
1201 let active_state_exists = dbtx
1202 .find_by_prefix(&ActiveOperationStateKeyPrefix { operation_id })
1203 .await
1204 .next()
1205 .await
1206 .is_some();
1207
1208 let inactive_state_exists = dbtx
1209 .find_by_prefix(&InactiveOperationStateKeyPrefix { operation_id })
1210 .await
1211 .next()
1212 .await
1213 .is_some();
1214
1215 active_state_exists || inactive_state_exists
1216 }
1217
1218 pub async fn has_active_states(&self, operation_id: OperationId) -> bool {
1220 self.executor.has_active_states(operation_id).await
1221 }
1222
1223 pub async fn await_no_active_states(&self, operation_id: OperationId) {
1243 self.executor.await_no_active_states(operation_id).await;
1244 }
1245
1246 pub async fn get_operation_fees(
1265 &self,
1266 operation_id: OperationId,
1267 ) -> Result<Option<Amounts>, OperationNotFoundError> {
1268 if !self.operation_exists(operation_id).await {
1269 return Err(OperationNotFoundError { operation_id });
1270 }
1271
1272 let (active_states, inactive_states) =
1273 self.executor().get_operation_states(operation_id).await;
1274
1275 let states = active_states
1276 .into_iter()
1277 .map(|(state, _)| state)
1278 .chain(inactive_states.into_iter().map(|(state, _)| state));
1279
1280 let accepted_transactions = states
1281 .filter_map(|state| {
1282 let tx_state = state.as_any().downcast_ref::<TxSubmissionStatesSM>()?;
1283
1284 match &tx_state.state {
1285 TxSubmissionStates::Accepted(transaction_id) => Some(*transaction_id),
1286 _ => None,
1287 }
1288 })
1289 .collect::<HashSet<_>>();
1290
1291 let mut dbtx = self.db.begin_transaction_nc().await;
1293 let mut total_fees = Amounts::ZERO;
1294 for txid in &accepted_transactions {
1295 let Some(fees) = dbtx.get_value(&TransactionFeesKey(*txid)).await else {
1296 return Ok(None);
1297 };
1298 total_fees = total_fees
1299 .checked_add(&fees)
1300 .expect("Fee amounts don't overflow in practice");
1301 }
1302
1303 Ok(Some(total_fees))
1304 }
1305
1306 pub async fn await_primary_bitcoin_module_output(
1309 &self,
1310 operation_id: OperationId,
1311 out_point: OutPoint,
1312 ) -> Result<(), TransactionSubmitError> {
1313 self.primary_module_for_unit(AmountUnit::BITCOIN)
1314 .ok_or(TransactionSubmitError::NoPrimaryModule {
1315 unit: AmountUnit::BITCOIN,
1316 })?
1317 .1
1318 .await_primary_module_output(operation_id, out_point)
1319 .await
1320 .map_err(TransactionSubmitError::PrimaryModule)
1321 }
1322
1323 pub fn get_first_module<M: ClientModule>(
1325 &'_ self,
1326 ) -> Result<ClientModuleInstance<'_, M>, ModuleLookupError> {
1327 let module_kind = M::kind();
1328 let id = self.get_first_instance(&module_kind).ok_or_else(|| {
1329 ModuleLookupError::NoModuleOfKind {
1330 kind: module_kind.clone(),
1331 }
1332 })?;
1333 let module: &M = self
1334 .try_get_module(id)
1335 .ok_or(ModuleLookupError::UnknownInstance { instance_id: id })?
1336 .as_any()
1337 .downcast_ref::<M>()
1338 .ok_or(ModuleLookupError::WrongModuleType {
1339 instance_id: id,
1340 expected: std::any::type_name::<M>(),
1341 })?;
1342 let (db, _) = self.db().with_prefix_module_id(id);
1343 Ok(ClientModuleInstance {
1344 id,
1345 db,
1346 api: self.api().with_module(id),
1347 module,
1348 })
1349 }
1350
1351 pub fn get_module_client_dyn(
1352 &self,
1353 instance_id: ModuleInstanceId,
1354 ) -> Result<&maybe_add_send_sync!(dyn IClientModule), ModuleLookupError> {
1355 self.try_get_module(instance_id)
1356 .ok_or(ModuleLookupError::UnknownInstance { instance_id })
1357 }
1358
1359 pub fn db(&self) -> &Database {
1360 &self.db
1361 }
1362
1363 pub fn endpoints(&self) -> &ConnectorRegistry {
1364 &self.connectors
1365 }
1366
1367 pub async fn transaction_updates(&self, operation_id: OperationId) -> TransactionUpdates {
1370 TransactionUpdates {
1371 update_stream: self.transaction_update_stream(operation_id).await,
1372 }
1373 }
1374
1375 pub fn get_first_instance(&self, module_kind: &ModuleKind) -> Option<ModuleInstanceId> {
1377 self.modules
1378 .iter_modules()
1379 .find(|(_, kind, _module)| *kind == module_kind)
1380 .map(|(instance_id, _, _)| instance_id)
1381 }
1382
1383 pub async fn await_primary_bitcoin_module_outputs(
1386 &self,
1387 operation_id: OperationId,
1388 outputs: Vec<OutPoint>,
1389 ) -> Result<(), TransactionSubmitError> {
1390 for out_point in outputs {
1391 self.await_primary_bitcoin_module_output(operation_id, out_point)
1392 .await?;
1393 }
1394
1395 Ok(())
1396 }
1397
1398 pub async fn get_config_json(&self) -> JsonClientConfig {
1404 self.config().await.to_json()
1405 }
1406
1407 #[doc(hidden)]
1410 pub async fn get_balance_for_btc(&self) -> Result<Amount, ModuleLookupError> {
1413 self.get_balance_for_unit(AmountUnit::BITCOIN).await
1414 }
1415
1416 pub async fn get_balance_for_unit(
1417 &self,
1418 unit: AmountUnit,
1419 ) -> Result<Amount, ModuleLookupError> {
1420 let (id, module) = self
1421 .primary_module_for_unit(unit)
1422 .ok_or(ModuleLookupError::NoPrimaryModule { unit })?;
1423 Ok(module
1424 .get_balance(id, &mut self.db().begin_transaction_nc().await, unit)
1425 .await)
1426 }
1427
1428 pub async fn subscribe_balance_changes(&self, unit: AmountUnit) -> BoxStream<'static, Amount> {
1431 let primary_module_things =
1432 if let Some((primary_module_id, primary_module)) = self.primary_module_for_unit(unit) {
1433 let balance_changes = primary_module.subscribe_balance_changes().await;
1434 let initial_balance = self
1435 .get_balance_for_unit(unit)
1436 .await
1437 .expect("Primary is present");
1438
1439 Some((
1440 primary_module_id,
1441 primary_module.clone(),
1442 balance_changes,
1443 initial_balance,
1444 ))
1445 } else {
1446 None
1447 };
1448 let db = self.db().clone();
1449
1450 Box::pin(async_stream::stream! {
1451 let Some((primary_module_id, primary_module, mut balance_changes, initial_balance)) = primary_module_things else {
1452 pending().await
1455 };
1456
1457
1458 yield initial_balance;
1459 let mut prev_balance = initial_balance;
1460 while let Some(()) = balance_changes.next().await {
1461 let mut dbtx = db.begin_transaction_nc().await;
1462 let balance = primary_module
1463 .get_balance(primary_module_id, &mut dbtx, unit)
1464 .await;
1465
1466 if balance != prev_balance {
1468 prev_balance = balance;
1469 yield balance;
1470 }
1471 }
1472 })
1473 }
1474
1475 async fn make_api_version_request(
1480 delay: Duration,
1481 peer_id: PeerId,
1482 api: &DynGlobalApi,
1483 ) -> (
1484 PeerId,
1485 Result<SupportedApiVersionsSummary, fedimint_connectors::error::ServerError>,
1486 ) {
1487 runtime::sleep(delay).await;
1488 (
1489 peer_id,
1490 api.request_single_peer::<SupportedApiVersionsSummary>(
1491 VERSION_ENDPOINT.to_owned(),
1492 ApiRequestErased::default(),
1493 peer_id,
1494 )
1495 .await,
1496 )
1497 }
1498
1499 fn create_api_version_backoff() -> impl Iterator<Item = Duration> {
1505 custom_backoff(Duration::from_millis(200), Duration::from_secs(600), None)
1506 }
1507
1508 pub async fn fetch_common_api_versions_from_all_peers(
1511 num_peers: NumPeers,
1512 api: DynGlobalApi,
1513 db: Database,
1514 num_responses_sender: watch::Sender<usize>,
1515 ) {
1516 let mut backoff = Self::create_api_version_backoff();
1517
1518 let mut requests = FuturesUnordered::new();
1521
1522 for peer_id in num_peers.peer_ids() {
1523 requests.push(Self::make_api_version_request(
1524 Duration::ZERO,
1525 peer_id,
1526 &api,
1527 ));
1528 }
1529
1530 let mut num_responses = 0;
1531
1532 while let Some((peer_id, response)) = requests.next().await {
1533 let retry = match response {
1534 Err(err) => {
1535 let has_previous_response = db
1536 .begin_transaction_nc()
1537 .await
1538 .get_value(&PeerLastApiVersionsSummaryKey(peer_id))
1539 .await
1540 .is_some();
1541 debug!(
1542 target: LOG_CLIENT,
1543 %peer_id,
1544 err = %err.fmt_compact(),
1545 %has_previous_response,
1546 "Failed to refresh API versions of a peer"
1547 );
1548
1549 !has_previous_response
1550 }
1551 Ok(o) => {
1552 let mut dbtx = db.begin_transaction().await;
1555 dbtx.insert_entry(
1556 &PeerLastApiVersionsSummaryKey(peer_id),
1557 &PeerLastApiVersionsSummary(o),
1558 )
1559 .await;
1560 dbtx.commit_tx().await;
1561 false
1562 }
1563 };
1564
1565 if retry {
1566 requests.push(Self::make_api_version_request(
1567 backoff.next().expect("Keeps retrying"),
1568 peer_id,
1569 &api,
1570 ));
1571 } else {
1572 num_responses += 1;
1573 num_responses_sender.send_replace(num_responses);
1574 }
1575 }
1576 }
1577
1578 pub async fn fetch_peers_api_versions_from_threshold_of_peers(
1582 num_peers: NumPeers,
1583 api: DynGlobalApi,
1584 ) -> BTreeMap<PeerId, SupportedApiVersionsSummary> {
1585 let mut backoff = Self::create_api_version_backoff();
1586
1587 let mut requests = FuturesUnordered::new();
1590
1591 for peer_id in num_peers.peer_ids() {
1592 requests.push(Self::make_api_version_request(
1593 Duration::ZERO,
1594 peer_id,
1595 &api,
1596 ));
1597 }
1598
1599 let mut successful_responses = BTreeMap::new();
1600
1601 while successful_responses.len() < num_peers.threshold()
1602 && let Some((peer_id, response)) = requests.next().await
1603 {
1604 let retry = match response {
1605 Err(err) => {
1606 debug!(
1607 target: LOG_CLIENT,
1608 %peer_id,
1609 err = %err.fmt_compact(),
1610 "Failed to fetch API versions from peer"
1611 );
1612 true
1613 }
1614 Ok(response) => {
1615 successful_responses.insert(peer_id, response);
1616 false
1617 }
1618 };
1619
1620 if retry {
1621 requests.push(Self::make_api_version_request(
1622 backoff.next().expect("Keeps retrying"),
1623 peer_id,
1624 &api,
1625 ));
1626 }
1627 }
1628
1629 successful_responses
1630 }
1631
1632 pub async fn fetch_common_api_versions(
1634 config: &ClientConfig,
1635 api: &DynGlobalApi,
1636 ) -> BTreeMap<PeerId, SupportedApiVersionsSummary> {
1637 debug!(
1638 target: LOG_CLIENT,
1639 "Fetching common api versions"
1640 );
1641
1642 let num_peers = NumPeers::from(config.global.api_endpoints.len());
1643
1644 Self::fetch_peers_api_versions_from_threshold_of_peers(num_peers, api.clone()).await
1645 }
1646
1647 pub async fn write_api_version_cache(
1651 dbtx: &mut DatabaseTransaction<'_>,
1652 api_version_set: ApiVersionSet,
1653 ) {
1654 debug!(
1655 target: LOG_CLIENT,
1656 value = ?api_version_set,
1657 "Writing API version set to cache"
1658 );
1659
1660 dbtx.insert_entry(
1661 &CachedApiVersionSetKey,
1662 &CachedApiVersionSet(api_version_set),
1663 )
1664 .await;
1665 }
1666
1667 pub async fn store_prefetched_api_versions(
1672 db: &Database,
1673 config: &ClientConfig,
1674 client_module_init: &ClientModuleInitRegistry,
1675 peer_api_versions: &BTreeMap<PeerId, SupportedApiVersionsSummary>,
1676 ) {
1677 debug!(
1678 target: LOG_CLIENT,
1679 "Storing {} prefetched peer API version responses and calculating common version set",
1680 peer_api_versions.len()
1681 );
1682
1683 let mut dbtx = db.begin_transaction().await;
1684 let client_supported_versions =
1686 Self::supported_api_versions_summary_static(config, client_module_init);
1687 match fedimint_client_module::api_version_discovery::discover_common_api_versions_set(
1688 &client_supported_versions,
1689 peer_api_versions,
1690 ) {
1691 Ok(common_api_versions) => {
1692 Self::write_api_version_cache(&mut dbtx.to_ref_nc(), common_api_versions).await;
1694 debug!(target: LOG_CLIENT, "Calculated and stored common API version set");
1695 }
1696 Err(err) => {
1697 debug!(target: LOG_CLIENT, err = %err.fmt_compact(), "Failed to calculate common API versions from prefetched data");
1698 }
1699 }
1700
1701 for (peer_id, peer_api_versions) in peer_api_versions {
1703 dbtx.insert_entry(
1704 &PeerLastApiVersionsSummaryKey(*peer_id),
1705 &PeerLastApiVersionsSummary(peer_api_versions.clone()),
1706 )
1707 .await;
1708 }
1709 dbtx.commit_tx().await;
1710 debug!(target: LOG_CLIENT, "Stored individual peer API version responses");
1711 }
1712
1713 pub fn supported_api_versions_summary_static(
1715 config: &ClientConfig,
1716 client_module_init: &ClientModuleInitRegistry,
1717 ) -> SupportedApiVersionsSummary {
1718 SupportedApiVersionsSummary {
1719 core: SupportedCoreApiVersions {
1720 core_consensus: config.global.consensus_version,
1721 api: MultiApiVersion::try_from_iter(SUPPORTED_CORE_API_VERSIONS.to_owned())
1722 .expect("must not have conflicting versions"),
1723 },
1724 modules: config
1725 .modules
1726 .iter()
1727 .filter_map(|(&module_instance_id, module_config)| {
1728 client_module_init
1729 .get(module_config.kind())
1730 .map(|module_init| {
1731 (
1732 module_instance_id,
1733 SupportedModuleApiVersions {
1734 core_consensus: config.global.consensus_version,
1735 module_consensus: module_config.version,
1736 api: module_init.supported_api_versions(),
1737 },
1738 )
1739 })
1740 })
1741 .collect(),
1742 }
1743 }
1744
1745 pub async fn load_and_refresh_common_api_version(
1746 &self,
1747 ) -> Result<ApiVersionSet, ApiVersionDiscoveryError> {
1748 Self::load_and_refresh_common_api_version_static(
1749 &self.config().await,
1750 &self.module_inits,
1751 self.connectors.clone(),
1752 &self.api,
1753 &self.db,
1754 &self.task_group,
1755 &self.client_span,
1756 )
1757 .await
1758 }
1759
1760 pub async fn refresh_api_versions(&self) -> Result<ApiVersionSet, ApiVersionDiscoveryError> {
1766 Self::refresh_common_api_version_static(
1767 &self.config().await,
1768 &self.module_inits,
1769 &self.api,
1770 &self.db,
1771 self.task_group.clone(),
1772 &self.client_span,
1773 true,
1774 )
1775 .await
1776 }
1777
1778 pub(crate) async fn load_and_refresh_common_api_version_static(
1784 config: &ClientConfig,
1785 module_init: &ClientModuleInitRegistry,
1786 connectors: ConnectorRegistry,
1787 api: &DynGlobalApi,
1788 db: &Database,
1789 task_group: &TaskGroup,
1790 client_span: &Span,
1791 ) -> Result<ApiVersionSet, ApiVersionDiscoveryError> {
1792 if let Some(v) = db
1793 .begin_transaction_nc()
1794 .await
1795 .get_value(&CachedApiVersionSetKey)
1796 .await
1797 {
1798 client_span.in_scope(|| {
1799 debug!(
1800 target: LOG_CLIENT,
1801 "Found existing cached common api versions"
1802 );
1803 });
1804 let config = config.clone();
1805 let client_module_init = module_init.clone();
1806 let api = api.clone();
1807 let db = db.clone();
1808 let task_group = task_group.clone();
1809 let client_span_owned = client_span.clone();
1810 task_group.clone().spawn_cancellable_with_span(
1813 client_span.clone(),
1814 "refresh_common_api_version_static",
1815 async move {
1816 connectors.wait_for_initialized_connections().await;
1817
1818 if let Err(error) = Self::refresh_common_api_version_static(
1819 &config,
1820 &client_module_init,
1821 &api,
1822 &db,
1823 task_group,
1824 &client_span_owned,
1825 false,
1826 )
1827 .await
1828 {
1829 warn!(
1830 target: LOG_CLIENT,
1831 err = %error.fmt_compact(), "Failed to discover common api versions"
1832 );
1833 }
1834 },
1835 );
1836
1837 return Ok(v.0);
1838 }
1839
1840 info!(
1841 target: LOG_CLIENT,
1842 "Fetching initial API versions "
1843 );
1844 Self::refresh_common_api_version_static(
1845 config,
1846 module_init,
1847 api,
1848 db,
1849 task_group.clone(),
1850 client_span,
1851 true,
1852 )
1853 .await
1854 }
1855
1856 async fn refresh_common_api_version_static(
1857 config: &ClientConfig,
1858 client_module_init: &ClientModuleInitRegistry,
1859 api: &DynGlobalApi,
1860 db: &Database,
1861 task_group: TaskGroup,
1862 client_span: &Span,
1863 block_until_ok: bool,
1864 ) -> Result<ApiVersionSet, ApiVersionDiscoveryError> {
1865 debug!(
1866 target: LOG_CLIENT,
1867 "Refreshing common api versions"
1868 );
1869
1870 let (num_responses_sender, mut num_responses_receiver) = tokio::sync::watch::channel(0);
1871 let num_peers = NumPeers::from(config.global.api_endpoints.len());
1872
1873 task_group.spawn_cancellable_with_span(
1874 client_span.clone(),
1875 "refresh peers api versions",
1876 Client::fetch_common_api_versions_from_all_peers(
1877 num_peers,
1878 api.clone(),
1879 db.clone(),
1880 num_responses_sender,
1881 ),
1882 );
1883
1884 let common_api_versions = loop {
1885 let _: Result<_, Elapsed> = runtime::timeout(
1893 Duration::from_secs(30),
1894 num_responses_receiver.wait_for(|num| num_peers.threshold() <= *num),
1895 )
1896 .await;
1897
1898 let peer_api_version_sets = Self::load_peers_last_api_versions(db, num_peers).await;
1899
1900 match fedimint_client_module::api_version_discovery::discover_common_api_versions_set(
1901 &Self::supported_api_versions_summary_static(config, client_module_init),
1902 &peer_api_version_sets,
1903 ) {
1904 Ok(o) => break o,
1905 Err(err) if block_until_ok => {
1906 warn!(
1907 target: LOG_CLIENT,
1908 err = %err.fmt_compact(),
1909 "Failed to discover API version to use. Retrying..."
1910 );
1911 continue;
1912 }
1913 Err(e) => return Err(e),
1914 }
1915 };
1916
1917 debug!(
1918 target: LOG_CLIENT,
1919 value = ?common_api_versions,
1920 "Updating the cached common api versions"
1921 );
1922 let mut dbtx = db.begin_transaction().await;
1923 let _ = dbtx
1924 .insert_entry(
1925 &CachedApiVersionSetKey,
1926 &CachedApiVersionSet(common_api_versions.clone()),
1927 )
1928 .await;
1929
1930 dbtx.commit_tx().await;
1931
1932 Ok(common_api_versions)
1933 }
1934
1935 pub async fn get_metadata(&self) -> Metadata {
1937 self.db
1938 .begin_transaction_nc()
1939 .await
1940 .get_value(&ClientMetadataKey)
1941 .await
1942 .unwrap_or_else(|| {
1943 warn!(
1944 target: LOG_CLIENT,
1945 "Missing existing metadata. This key should have been set on Client init"
1946 );
1947 Metadata::empty()
1948 })
1949 }
1950
1951 pub async fn set_metadata(&self, metadata: &Metadata) {
1953 self.db
1954 .autocommit::<_, _, Infallible>(
1955 |dbtx, _| {
1956 Box::pin(async {
1957 Self::set_metadata_dbtx(dbtx, metadata).await;
1958 Ok(())
1959 })
1960 },
1961 None,
1962 )
1963 .await
1964 .expect("Failed to autocommit metadata");
1965 }
1966
1967 pub fn has_pending_recoveries(&self) -> bool {
1968 !self
1969 .client_recovery_status_receiver
1970 .borrow()
1971 .values()
1972 .all(RecoveryStatus::is_successfully_done)
1973 }
1974
1975 pub fn all_modules_usable(&self) -> bool {
1986 self.client_recovery_status_receiver
1987 .borrow()
1988 .keys()
1989 .all(|module_instance_id| self.modules.get(*module_instance_id).is_some())
1990 }
1991
1992 pub async fn wait_for_all_recoveries(&self) -> Result<(), RecoveryError> {
2006 Self::wait_for_recoveries(
2007 self.client_recovery_status_receiver.clone(),
2008 |_module_instance_id| true,
2009 )
2010 .await
2011 }
2012
2013 async fn wait_for_recoveries(
2022 mut status_receiver: watch::Receiver<BTreeMap<ModuleInstanceId, RecoveryStatus>>,
2023 module_filter: impl Fn(ModuleInstanceId) -> bool,
2024 ) -> Result<(), RecoveryError> {
2025 let failure = status_receiver
2026 .wait_for(|statuses| {
2027 let matching = || {
2028 statuses
2029 .iter()
2030 .filter(|(module_instance_id, _status)| module_filter(**module_instance_id))
2031 .map(|(_module_instance_id, status)| status)
2032 };
2033
2034 matching().any(|status| matches!(status, RecoveryStatus::Failed { .. }))
2038 || matching().all(RecoveryStatus::is_successfully_done)
2039 })
2040 .await
2041 .map_err(|_closed| RecoveryError::ClientStopped)?
2042 .iter()
2045 .find_map(|(module_instance_id, status)| match status {
2046 RecoveryStatus::Failed { error, .. } if module_filter(*module_instance_id) => {
2047 Some((*module_instance_id, error.clone()))
2048 }
2049 _ => None,
2050 });
2051
2052 match failure {
2053 Some((module_instance_id, source)) => Err(RecoveryError::Failed {
2054 module_instance_id,
2055 source,
2056 }),
2057 None => Ok(()),
2058 }
2059 }
2060
2061 pub fn subscribe_to_recovery_progress(
2071 &self,
2072 ) -> impl Stream<Item = (ModuleInstanceId, RecoveryProgress)> + use<> {
2073 WatchStream::new(self.client_recovery_status_receiver.clone()).flat_map(|statuses| {
2074 futures::stream::iter(
2075 statuses
2076 .into_iter()
2077 .map(|(module_instance_id, status)| (module_instance_id, status.progress())),
2078 )
2079 })
2080 }
2081
2082 pub async fn wait_for_module_kind_recovery(
2091 &self,
2092 module_kind: ModuleKind,
2093 ) -> Result<(), RecoveryError> {
2094 let config = self.config().await;
2095 Self::wait_for_recoveries(
2096 self.client_recovery_status_receiver.clone(),
2097 move |module_instance_id| {
2098 config
2099 .modules
2100 .get(&module_instance_id)
2101 .is_some_and(|module| module.kind == module_kind)
2102 },
2103 )
2104 .await
2105 }
2106
2107 pub async fn wait_for_all_active_state_machines(&self) {
2108 loop {
2109 if self.executor.get_active_states().await.is_empty() {
2110 break;
2111 }
2112 sleep(Duration::from_millis(100)).await;
2113 }
2114 }
2115
2116 pub async fn set_metadata_dbtx(dbtx: &mut DatabaseTransaction<'_>, metadata: &Metadata) {
2118 dbtx.insert_new_entry(&ClientMetadataKey, metadata).await;
2119 }
2120
2121 fn spawn_module_recoveries_task(
2122 &self,
2123 recovery_sender: watch::Sender<BTreeMap<ModuleInstanceId, RecoveryStatus>>,
2124 module_recoveries: BTreeMap<ModuleInstanceId, ModuleRecoveryFuture>,
2125 module_recovery_progress_receivers: BTreeMap<
2126 ModuleInstanceId,
2127 watch::Receiver<RecoveryProgress>,
2128 >,
2129 module_kinds: BTreeMap<ModuleInstanceId, ModuleKind>,
2132 ) {
2133 let db = self.db.clone();
2134 let log_ordering_wakeup_tx = self.log_ordering_wakeup_tx.clone();
2135 self.spawn("module recoveries", |_task_handle| async {
2140 Self::run_module_recoveries_task(
2141 db,
2142 log_ordering_wakeup_tx,
2143 recovery_sender,
2144 module_recoveries,
2145 module_recovery_progress_receivers,
2146 module_kinds,
2147 )
2148 .await;
2149 });
2150 }
2151
2152 async fn run_module_recoveries_task(
2153 db: Database,
2154 log_ordering_wakeup_tx: watch::Sender<()>,
2155 recovery_sender: watch::Sender<BTreeMap<ModuleInstanceId, RecoveryStatus>>,
2156 module_recoveries: BTreeMap<ModuleInstanceId, ModuleRecoveryFuture>,
2157 module_recovery_progress_receivers: BTreeMap<
2158 ModuleInstanceId,
2159 watch::Receiver<RecoveryProgress>,
2160 >,
2161 module_kinds: BTreeMap<ModuleInstanceId, ModuleKind>,
2162 ) {
2163 debug!(target: LOG_CLIENT_RECOVERY, num_modules=%module_recovery_progress_receivers.len(), "Staring module recoveries");
2164
2165 enum RecoveryUpdate {
2169 Progress(RecoveryProgress),
2170 Completed(Option<Amount>),
2171 }
2172
2173 let mut completed_stream = Vec::new();
2174 let progress_stream = futures::stream::FuturesUnordered::new();
2175
2176 for (module_instance_id, f) in module_recoveries {
2177 let recovery_sender = recovery_sender.clone();
2178 completed_stream.push(futures::stream::once(Box::pin(async move {
2179 match f.await {
2180 Ok(amount) => (module_instance_id, RecoveryUpdate::Completed(amount)),
2181 Err(err) => {
2182 warn!(
2183 target: LOG_CLIENT,
2184 err = %err.fmt_compact(), module_instance_id, "Module recovery failed"
2185 );
2186 let error = Arc::new(err);
2187 recovery_sender.send_modify(|statuses| {
2194 let last_progress = statuses
2195 .get(&module_instance_id)
2196 .expect("existing status must be present")
2197 .progress();
2198 statuses.insert(
2199 module_instance_id,
2200 RecoveryStatus::Failed {
2201 last_progress,
2202 error,
2203 },
2204 );
2205 });
2206 futures::future::pending::<()>().await;
2216 unreachable!()
2217 }
2218 }
2219 })));
2220 }
2221
2222 for (module_instance_id, rx) in module_recovery_progress_receivers {
2223 progress_stream.push(
2224 tokio_stream::wrappers::WatchStream::new(rx)
2225 .fuse()
2226 .map(move |progress| (module_instance_id, RecoveryUpdate::Progress(progress))),
2227 );
2228 }
2229
2230 let mut futures = futures::stream::select(
2231 futures::stream::select_all(progress_stream),
2232 futures::stream::select_all(completed_stream),
2233 );
2234
2235 while let Some((module_instance_id, update)) = futures.next().await {
2236 let prev_status = recovery_sender
2240 .borrow()
2241 .get(&module_instance_id)
2242 .expect("existing status must be present")
2243 .clone();
2244
2245 if matches!(prev_status, RecoveryStatus::Failed { .. }) {
2255 debug!(
2256 target: LOG_CLIENT_RECOVERY,
2257 module_instance_id,
2258 "Ignoring a recovery update of a module whose recovery already failed"
2259 );
2260 continue;
2261 }
2262
2263 let prev_progress = prev_status.progress();
2264
2265 if let RecoveryUpdate::Progress(progress) = &update {
2271 if progress.is_done() {
2272 warn!(
2273 target: LOG_CLIENT_RECOVERY,
2274 module_instance_id,
2275 "Module bypassed the sanctioned recovery progress reporting API and reported a completed recovery progress. Ignoring"
2276 );
2277 continue;
2278 }
2279
2280 if progress.is_none() && !prev_progress.is_none() && !prev_progress.is_done() {
2287 warn!(
2288 target: LOG_CLIENT_RECOVERY,
2289 module_instance_id,
2290 "Module bypassed the sanctioned recovery progress reporting API and reported a none recovery progress, regressing its previous one. Ignoring"
2291 );
2292 continue;
2293 }
2294 }
2295
2296 let mut dbtx = db.begin_transaction().await;
2297
2298 let (progress, recovered_amount) = if prev_progress.is_done() {
2304 (prev_progress, None)
2306 } else {
2307 match update {
2308 RecoveryUpdate::Progress(progress) => (progress, None),
2309 RecoveryUpdate::Completed(amount) => (prev_progress.to_complete(), amount),
2310 }
2311 };
2312
2313 if !prev_progress.is_done() && progress.is_done() {
2314 info!(
2315 target: LOG_CLIENT,
2316 module_instance_id,
2317 progress = format!("{}/{}", progress.complete, progress.total),
2318 amount = ?recovered_amount,
2319 "Recovery complete"
2320 );
2321 dbtx.log_event(
2322 log_ordering_wakeup_tx.clone(),
2323 None,
2324 ModuleRecoveryCompleted {
2325 module_id: module_instance_id,
2326 kind: module_kinds.get(&module_instance_id).cloned(),
2327 amount: recovered_amount,
2328 },
2329 )
2330 .await;
2331 } else {
2332 info!(
2333 target: LOG_CLIENT,
2334 module_instance_id,
2335 kind = ?module_kinds.get(&module_instance_id),
2336 progress = format!("{}/{}", progress.complete, progress.total),
2337 "Recovery progress"
2338 );
2339 }
2340
2341 dbtx.insert_entry(
2342 &ClientModuleRecovery { module_instance_id },
2343 &ClientModuleRecoveryState { progress },
2344 )
2345 .await;
2346 dbtx.commit_tx().await;
2347
2348 recovery_sender.send_modify(|statuses| {
2349 statuses.insert(module_instance_id, RecoveryStatus::InProgress(progress));
2350 });
2351 }
2352 debug!(target: LOG_CLIENT_RECOVERY, "Recovery executor stopped");
2353 }
2354
2355 async fn load_peers_last_api_versions(
2356 db: &Database,
2357 num_peers: NumPeers,
2358 ) -> BTreeMap<PeerId, SupportedApiVersionsSummary> {
2359 let mut peer_api_version_sets = BTreeMap::new();
2360
2361 let mut dbtx = db.begin_transaction_nc().await;
2362 for peer_id in num_peers.peer_ids() {
2363 if let Some(v) = dbtx
2364 .get_value(&PeerLastApiVersionsSummaryKey(peer_id))
2365 .await
2366 {
2367 peer_api_version_sets.insert(peer_id, v.0);
2368 }
2369 }
2370 drop(dbtx);
2371 peer_api_version_sets
2372 }
2373
2374 pub async fn get_peer_url_announcements(&self) -> BTreeMap<PeerId, SignedApiAnnouncement> {
2377 self.db()
2378 .begin_transaction_nc()
2379 .await
2380 .find_by_prefix(&ApiAnnouncementPrefix)
2381 .await
2382 .map(|(announcement_key, announcement)| (announcement_key.0, announcement))
2383 .collect()
2384 .await
2385 }
2386
2387 pub async fn get_guardian_metadata(
2389 &self,
2390 ) -> BTreeMap<PeerId, fedimint_core::net::guardian_metadata::SignedGuardianMetadata> {
2391 self.db()
2392 .begin_transaction_nc()
2393 .await
2394 .find_by_prefix(&crate::guardian_metadata::GuardianMetadataPrefix)
2395 .await
2396 .map(|(key, metadata)| (key.0, metadata))
2397 .collect()
2398 .await
2399 }
2400
2401 pub async fn get_peer_urls(&self) -> BTreeMap<PeerId, SafeUrl> {
2403 get_api_urls(&self.db, &self.config().await, self.iroh_enable_next).await
2404 }
2405
2406 pub async fn invite_code(&self, peer: PeerId) -> Option<InviteCode> {
2409 self.get_peer_urls()
2410 .await
2411 .into_iter()
2412 .find_map(|(peer_id, url)| (peer == peer_id).then_some(url))
2413 .map(|peer_url| {
2414 InviteCode::new(
2415 peer_url.clone(),
2416 peer,
2417 self.federation_id(),
2418 self.api_secret.clone(),
2419 )
2420 })
2421 }
2422
2423 pub async fn get_guardian_public_keys_blocking(
2427 &self,
2428 ) -> BTreeMap<PeerId, fedimint_core::secp256k1::PublicKey> {
2429 self.db
2430 .autocommit(
2431 |dbtx, _| {
2432 Box::pin(async move {
2433 let config = self.config().await;
2434
2435 let guardian_pub_keys = self
2436 .get_or_backfill_broadcast_public_keys(dbtx, config)
2437 .await;
2438
2439 Result::<_, ()>::Ok(guardian_pub_keys)
2440 })
2441 },
2442 None,
2443 )
2444 .await
2445 .expect("Will retry forever")
2446 }
2447
2448 async fn get_or_backfill_broadcast_public_keys(
2449 &self,
2450 dbtx: &mut DatabaseTransaction<'_>,
2451 config: ClientConfig,
2452 ) -> BTreeMap<PeerId, PublicKey> {
2453 match config.global.broadcast_public_keys {
2454 Some(guardian_pub_keys) => guardian_pub_keys,
2455 _ => {
2456 let (guardian_pub_keys, new_config) = self.fetch_and_update_config(config).await;
2457
2458 dbtx.insert_entry(&ClientConfigKey, &new_config).await;
2459 *(self.config.write().await) = new_config;
2460 guardian_pub_keys
2461 }
2462 }
2463 }
2464
2465 pub async fn fetch_session_count(&self) -> FederationResult<u64> {
2466 self.api.session_count().await
2467 }
2468
2469 async fn fetch_and_update_config(
2470 &self,
2471 config: ClientConfig,
2472 ) -> (BTreeMap<PeerId, PublicKey>, ClientConfig) {
2473 let fetched_config = retry(
2474 "Fetching guardian public keys",
2475 backoff_util::background_backoff(),
2476 || async {
2477 self.api
2478 .request_current_consensus::<ClientConfig>(
2479 CLIENT_CONFIG_ENDPOINT.to_owned(),
2480 ApiRequestErased::default(),
2481 )
2482 .await
2483 },
2484 )
2485 .await
2486 .expect("Will never return on error");
2487
2488 let Some(guardian_pub_keys) = fetched_config.global.broadcast_public_keys else {
2489 warn!(
2490 target: LOG_CLIENT,
2491 "Guardian public keys not found in fetched config, server not updated to 0.4 yet"
2492 );
2493 pending::<()>().await;
2494 unreachable!("Pending will never return");
2495 };
2496
2497 let new_config = ClientConfig {
2498 global: GlobalClientConfig {
2499 broadcast_public_keys: Some(guardian_pub_keys.clone()),
2500 ..config.global
2501 },
2502 modules: config.modules,
2503 };
2504 (guardian_pub_keys, new_config)
2505 }
2506
2507 pub fn handle_global_rpc(
2514 &self,
2515 method: String,
2516 params: serde_json::Value,
2517 ) -> BoxStream<'_, Result<serde_json::Value, GlobalRpcError>> {
2518 Box::pin(try_stream! {
2519 match method.as_str() {
2520 "get_balance" => {
2521 let balance = self.get_balance_for_btc().await.unwrap_or_default();
2522 yield serde_json::to_value(balance)?;
2523 }
2524 "subscribe_balance_changes" => {
2525 let req: GetBalanceChangesRequest= serde_json::from_value(params)?;
2526 let mut stream = self.subscribe_balance_changes(req.unit).await;
2527 while let Some(balance) = stream.next().await {
2528 yield serde_json::to_value(balance)?;
2529 }
2530 }
2531 "get_config" => {
2532 let config = self.config().await;
2533 yield serde_json::to_value(config)?;
2534 }
2535 "get_federation_id" => {
2536 let federation_id = self.federation_id();
2537 yield serde_json::to_value(federation_id)?;
2538 }
2539 "get_invite_code" => {
2540 let req: GetInviteCodeRequest = serde_json::from_value(params)?;
2541 let invite_code = self.invite_code(req.peer).await;
2542 yield serde_json::to_value(invite_code)?;
2543 }
2544 "get_operation" => {
2545 let req: GetOperationIdRequest = serde_json::from_value(params)?;
2546 let operation = self.operation_log().get_operation(req.operation_id).await;
2547 yield serde_json::to_value(operation)?;
2548 }
2549 "list_operations" => {
2550 let req: ListOperationsParams = serde_json::from_value(params)?;
2551 let limit = if req.limit.is_none() && req.last_seen.is_none() {
2552 usize::MAX
2553 } else {
2554 req.limit.unwrap_or(usize::MAX)
2555 };
2556 let operations = self.operation_log()
2557 .paginate_operations_rev(limit, req.last_seen)
2558 .await;
2559 yield serde_json::to_value(operations)?;
2560 }
2561 "get_event_log" => {
2562 let req: GetEventLogRequest = serde_json::from_value(params)?;
2563 let limit = req
2564 .limit
2565 .unwrap_or(DEFAULT_EVENT_LOG_PAGE_SIZE)
2566 .min(MAX_EVENT_LOG_PAGE_SIZE);
2567 let events = self.get_event_log(req.pos, limit).await;
2568 yield serde_json::to_value(events)?;
2569 }
2570 "session_count" => {
2571 let count = self.fetch_session_count().await?;
2572 yield serde_json::to_value(count)?;
2573 }
2574 "has_pending_recoveries" => {
2575 let has_pending = self.has_pending_recoveries();
2576 yield serde_json::to_value(has_pending)?;
2577 }
2578 "wait_for_all_recoveries" => {
2579 self.wait_for_all_recoveries().await?;
2580 yield serde_json::Value::Null;
2581 }
2582 "subscribe_to_recovery_progress" => {
2583 let mut stream = self.subscribe_to_recovery_progress();
2584 while let Some((module_id, progress)) = stream.next().await {
2585 yield serde_json::json!({
2586 "module_id": module_id,
2587 "progress": progress
2588 });
2589 }
2590 }
2591 #[allow(deprecated)]
2592 "backup_to_federation" => {
2593 let metadata = if params.is_null() {
2594 Metadata::from_json_serialized(serde_json::json!({}))
2595 } else {
2596 Metadata::from_json_serialized(params)
2597 };
2598 self.backup_to_federation(metadata).await?;
2599 yield serde_json::Value::Null;
2600 }
2601 _ => {
2602 Err(GlobalRpcError::UnknownMethod { method: method.clone() })?;
2603 unreachable!()
2604 },
2605 }
2606 })
2607 }
2608
2609 pub async fn log_event<E>(&self, module_id: Option<ModuleInstanceId>, event: E)
2610 where
2611 E: Event + Send,
2612 {
2613 let mut dbtx = self.db.begin_transaction().await;
2614 self.log_event_dbtx(&mut dbtx, module_id, event).await;
2615 dbtx.commit_tx().await;
2616 }
2617
2618 pub async fn log_event_dbtx<E, Cap>(
2619 &self,
2620 dbtx: &mut DatabaseTransaction<'_, Cap>,
2621 module_id: Option<ModuleInstanceId>,
2622 event: E,
2623 ) where
2624 E: Event + Send,
2625 Cap: Send,
2626 {
2627 dbtx.log_event(self.log_ordering_wakeup_tx.clone(), module_id, event)
2628 .await;
2629 }
2630
2631 pub async fn log_event_raw_dbtx<Cap>(
2632 &self,
2633 dbtx: &mut DatabaseTransaction<'_, Cap>,
2634 kind: EventKind,
2635 module: Option<(ModuleKind, ModuleInstanceId)>,
2636 payload: Vec<u8>,
2637 persist: EventPersistence,
2638 ) where
2639 Cap: Send,
2640 {
2641 let module_id = module.as_ref().map(|m| m.1);
2642 let module_kind = module.map(|m| m.0);
2643 dbtx.log_event_raw(
2644 self.log_ordering_wakeup_tx.clone(),
2645 kind,
2646 module_kind,
2647 module_id,
2648 payload,
2649 persist,
2650 )
2651 .await;
2652 }
2653
2654 pub fn built_in_application_event_log_tracker(&self) -> DynEventLogTrimableTracker {
2666 struct BuiltInApplicationEventLogTracker;
2667
2668 #[apply(async_trait_maybe_send!)]
2669 impl EventLogTrimableTracker for BuiltInApplicationEventLogTracker {
2670 async fn store(
2672 &mut self,
2673 dbtx: &mut DatabaseTransaction<NonCommittable>,
2674 pos: EventLogTrimableId,
2675 ) -> Result<(), EventLogTrackerError> {
2676 dbtx.insert_entry(&DefaultApplicationEventLogKey, &pos)
2677 .await;
2678 Ok(())
2679 }
2680
2681 async fn load(
2683 &mut self,
2684 dbtx: &mut DatabaseTransaction<NonCommittable>,
2685 ) -> Result<Option<EventLogTrimableId>, EventLogTrackerError> {
2686 Ok(dbtx.get_value(&DefaultApplicationEventLogKey).await)
2687 }
2688 }
2689 Box::new(BuiltInApplicationEventLogTracker)
2690 }
2691
2692 pub async fn handle_historical_events<F, R, E>(
2700 &self,
2701 tracker: fedimint_eventlog::DynEventLogTracker,
2702 handler_fn: F,
2703 ) -> Result<(), EventHandlerError<E>>
2704 where
2705 F: Fn(&mut DatabaseTransaction<NonCommittable>, EventLogEntry) -> R,
2706 R: Future<Output = Result<(), E>>,
2707 E: std::error::Error + 'static,
2708 {
2709 fedimint_eventlog::handle_events(
2710 self.db.clone(),
2711 tracker,
2712 self.log_event_added_rx.clone(),
2713 handler_fn,
2714 )
2715 .await
2716 }
2717
2718 pub async fn handle_events<F, R, E>(
2737 &self,
2738 tracker: fedimint_eventlog::DynEventLogTrimableTracker,
2739 handler_fn: F,
2740 ) -> Result<(), EventHandlerError<E>>
2741 where
2742 F: Fn(&mut DatabaseTransaction<NonCommittable>, EventLogEntry) -> R,
2743 R: Future<Output = Result<(), E>>,
2744 E: std::error::Error + 'static,
2745 {
2746 fedimint_eventlog::handle_trimable_events(
2747 self.db.clone(),
2748 tracker,
2749 self.log_event_added_rx.clone(),
2750 handler_fn,
2751 )
2752 .await
2753 }
2754
2755 pub async fn get_event_log(
2756 &self,
2757 pos: Option<EventLogId>,
2758 limit: u64,
2759 ) -> Vec<PersistedLogEntry> {
2760 self.get_event_log_dbtx(&mut self.db.begin_transaction_nc().await, pos, limit)
2761 .await
2762 }
2763
2764 pub async fn get_next_event_log_id(&self) -> EventLogId {
2767 self.db
2768 .begin_transaction_nc()
2769 .await
2770 .get_next_event_log_id()
2771 .await
2772 }
2773
2774 pub async fn get_event_log_trimable(
2775 &self,
2776 pos: Option<EventLogTrimableId>,
2777 limit: u64,
2778 ) -> Vec<PersistedLogEntry> {
2779 self.get_event_log_trimable_dbtx(&mut self.db.begin_transaction_nc().await, pos, limit)
2780 .await
2781 }
2782
2783 pub async fn get_event_log_dbtx<Cap>(
2784 &self,
2785 dbtx: &mut DatabaseTransaction<'_, Cap>,
2786 pos: Option<EventLogId>,
2787 limit: u64,
2788 ) -> Vec<PersistedLogEntry>
2789 where
2790 Cap: Send,
2791 {
2792 dbtx.get_event_log(pos, limit).await
2793 }
2794
2795 pub async fn get_event_log_trimable_dbtx<Cap>(
2796 &self,
2797 dbtx: &mut DatabaseTransaction<'_, Cap>,
2798 pos: Option<EventLogTrimableId>,
2799 limit: u64,
2800 ) -> Vec<PersistedLogEntry>
2801 where
2802 Cap: Send,
2803 {
2804 dbtx.get_event_log_trimable(pos, limit).await
2805 }
2806
2807 pub fn get_event_log_transient_receiver(&self) -> broadcast::Receiver<EventLogEntry> {
2809 self.log_event_added_transient_tx.subscribe()
2810 }
2811
2812 pub fn log_event_added_rx(&self) -> watch::Receiver<()> {
2814 self.log_event_added_rx.clone()
2815 }
2816
2817 pub fn iroh_enable_dht(&self) -> bool {
2818 self.iroh_enable_dht
2819 }
2820
2821 pub fn iroh_enable_next(&self) -> bool {
2824 self.iroh_enable_next
2825 }
2826
2827 pub(crate) async fn run_core_migrations(
2828 db_no_decoders: &Database,
2829 ) -> Result<(), DbMigrationError> {
2830 let mut dbtx = db_no_decoders.begin_transaction().await;
2831 apply_migrations_core_client_dbtx(&mut dbtx.to_ref_nc(), "fedimint-client".to_string())
2832 .await?;
2833 if is_running_in_test_env() {
2834 verify_client_db_integrity_dbtx(&mut dbtx.to_ref_nc()).await;
2835 }
2836 dbtx.commit_tx_result().await?;
2837 Ok(())
2838 }
2839
2840 fn primary_modules_for_unit(
2842 &self,
2843 unit: AmountUnit,
2844 ) -> impl Iterator<Item = (ModuleInstanceId, &DynClientModule)> {
2845 self.primary_modules
2846 .iter()
2847 .flat_map(move |(_prio, candidates)| {
2848 candidates
2849 .specific
2850 .get(&unit)
2851 .into_iter()
2852 .flatten()
2853 .copied()
2854 .chain(candidates.wildcard.iter().copied())
2856 })
2857 .map(|id| (id, self.modules.get_expect(id)))
2858 }
2859
2860 pub fn primary_module_for_unit(
2864 &self,
2865 unit: AmountUnit,
2866 ) -> Option<(ModuleInstanceId, &DynClientModule)> {
2867 self.primary_modules_for_unit(unit).next()
2868 }
2869
2870 pub fn primary_module_for_btc(&self) -> (ModuleInstanceId, &DynClientModule) {
2872 self.primary_module_for_unit(AmountUnit::BITCOIN)
2873 .expect("No primary module for Bitcoin")
2874 }
2875
2876 pub fn get_primary_module_for_unit<M: ClientModule>(
2889 &self,
2890 unit: AmountUnit,
2891 ) -> Result<&M, ModuleLookupError> {
2892 let mut modules = self.primary_modules_for_unit(unit).peekable();
2893 if modules.peek().is_none() {
2894 return Err(ModuleLookupError::NoPrimaryModule { unit });
2895 }
2896 modules
2897 .find_map(|(_, module)| module.as_any().downcast_ref::<M>())
2898 .ok_or_else(|| ModuleLookupError::NoPrimaryModuleOfKind {
2899 kind: M::kind(),
2900 unit,
2901 })
2902 }
2903}
2904
2905#[apply(async_trait_maybe_send!)]
2906impl ClientContextIface for Client {
2907 fn get_module(&self, instance: ModuleInstanceId) -> &maybe_add_send_sync!(dyn IClientModule) {
2908 Client::get_module(self, instance)
2909 }
2910
2911 fn api_clone(&self) -> DynGlobalApi {
2912 Client::api_clone(self)
2913 }
2914 fn decoders(&self) -> &ModuleDecoderRegistry {
2915 Client::decoders(self)
2916 }
2917
2918 async fn finalize_and_submit_transaction(
2919 &self,
2920 operation_id: OperationId,
2921 operation_type: &str,
2922 operation_meta_gen: Box<maybe_add_send_sync!(dyn Fn(OutPointRange) -> serde_json::Value)>,
2923 tx_builder: TransactionBuilder,
2924 ) -> Result<OutPointRange, TransactionSubmitError> {
2925 Client::finalize_and_submit_transaction(
2926 self,
2927 operation_id,
2928 operation_type,
2929 &operation_meta_gen,
2931 tx_builder,
2932 )
2933 .await
2934 }
2935
2936 async fn finalize_and_submit_transaction_dbtx(
2937 &self,
2938 dbtx: &mut DatabaseTransaction<'_>,
2939 operation_id: OperationId,
2940 operation_type: &str,
2941 operation_meta_gen: Box<maybe_add_send_sync!(dyn Fn(OutPointRange) -> serde_json::Value)>,
2942 tx_builder: TransactionBuilder,
2943 ) -> Result<OutPointRange, TransactionSubmitError> {
2944 Client::finalize_and_submit_transaction_dbtx(
2945 self,
2946 dbtx,
2947 operation_id,
2948 operation_type,
2949 &operation_meta_gen,
2950 tx_builder,
2951 )
2952 .await
2953 }
2954
2955 async fn finalize_and_submit_transaction_inner(
2956 &self,
2957 dbtx: &mut DatabaseTransaction<'_>,
2958 operation_id: OperationId,
2959 tx_builder: TransactionBuilder,
2960 ) -> Result<OutPointRange, TransactionSubmitError> {
2961 Client::finalize_and_submit_transaction_inner(self, dbtx, operation_id, tx_builder).await
2962 }
2963
2964 async fn fee_quote(
2965 &self,
2966 operation_id: OperationId,
2967 request: FeeQuoteRequest,
2968 ) -> Result<FeeQuote, TransactionSubmitError> {
2969 Client::fee_quote(self, operation_id, request).await
2970 }
2971
2972 async fn get_balance_for_unit(&self, unit: AmountUnit) -> Result<Amount, ModuleLookupError> {
2973 Client::get_balance_for_unit(self, unit).await
2974 }
2975
2976 async fn transaction_updates(&self, operation_id: OperationId) -> TransactionUpdates {
2977 Client::transaction_updates(self, operation_id).await
2978 }
2979
2980 async fn await_primary_module_outputs(
2981 &self,
2982 operation_id: OperationId,
2983 outputs: Vec<OutPoint>,
2985 ) -> Result<(), TransactionSubmitError> {
2986 Client::await_primary_bitcoin_module_outputs(self, operation_id, outputs).await
2987 }
2988
2989 fn operation_log(&self) -> &dyn IOperationLog {
2990 Client::operation_log(self)
2991 }
2992
2993 async fn has_active_states(&self, operation_id: OperationId) -> bool {
2994 Client::has_active_states(self, operation_id).await
2995 }
2996
2997 async fn await_no_active_states(&self, operation_id: OperationId) {
2998 Client::await_no_active_states(self, operation_id).await;
2999 }
3000
3001 async fn operation_exists(&self, operation_id: OperationId) -> bool {
3002 Client::operation_exists(self, operation_id).await
3003 }
3004
3005 async fn config(&self) -> ClientConfig {
3006 Client::config(self).await
3007 }
3008
3009 fn db(&self) -> &Database {
3010 Client::db(self)
3011 }
3012
3013 fn executor(&self) -> &(maybe_add_send_sync!(dyn IExecutor + 'static)) {
3014 Client::executor(self)
3015 }
3016
3017 async fn invite_code(&self, peer: PeerId) -> Option<InviteCode> {
3018 Client::invite_code(self, peer).await
3019 }
3020
3021 fn get_internal_payment_markers(&self) -> Result<(PublicKey, u64), bitcoin::secp256k1::Error> {
3022 Client::get_internal_payment_markers(self)
3023 }
3024
3025 async fn log_event_json(
3026 &self,
3027 dbtx: &mut DatabaseTransaction<'_, NonCommittable>,
3028 module_kind: Option<ModuleKind>,
3029 module_id: ModuleInstanceId,
3030 kind: EventKind,
3031 payload: serde_json::Value,
3032 persist: EventPersistence,
3033 ) {
3034 dbtx.ensure_global()
3035 .expect("Must be called with global dbtx");
3036 self.log_event_raw_dbtx(
3037 dbtx,
3038 kind,
3039 module_kind.map(|kind| (kind, module_id)),
3040 serde_json::to_vec(&payload).expect("Serialization can't fail"),
3041 persist,
3042 )
3043 .await;
3044 }
3045
3046 async fn read_operation_active_states<'dbtx>(
3047 &self,
3048 operation_id: OperationId,
3049 module_id: ModuleInstanceId,
3050 dbtx: &'dbtx mut DatabaseTransaction<'_>,
3051 ) -> Pin<Box<maybe_add_send!(dyn Stream<Item = (ActiveStateKey, ActiveStateMeta)> + 'dbtx)>>
3052 {
3053 Box::pin(
3054 dbtx.find_by_prefix(&ActiveModuleOperationStateKeyPrefix {
3055 operation_id,
3056 module_instance: module_id,
3057 })
3058 .await
3059 .map(move |(k, v)| (k.0, v)),
3060 )
3061 }
3062 async fn read_operation_inactive_states<'dbtx>(
3063 &self,
3064 operation_id: OperationId,
3065 module_id: ModuleInstanceId,
3066 dbtx: &'dbtx mut DatabaseTransaction<'_>,
3067 ) -> Pin<Box<maybe_add_send!(dyn Stream<Item = (InactiveStateKey, InactiveStateMeta)> + 'dbtx)>>
3068 {
3069 Box::pin(
3070 dbtx.find_by_prefix(&InactiveModuleOperationStateKeyPrefix {
3071 operation_id,
3072 module_instance: module_id,
3073 })
3074 .await
3075 .map(move |(k, v)| (k.0, v)),
3076 )
3077 }
3078}
3079
3080impl fmt::Debug for Client {
3082 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3083 write!(f, "Client")
3084 }
3085}
3086
3087pub fn client_decoders<'a>(
3088 registry: &ModuleInitRegistry<DynClientModuleInit>,
3089 module_kinds: impl Iterator<Item = (ModuleInstanceId, &'a ModuleKind)>,
3090) -> ModuleDecoderRegistry {
3091 let mut modules = BTreeMap::new();
3092 for (id, kind) in module_kinds {
3093 let Some(init) = registry.get(kind) else {
3094 debug!("Detected configuration for unsupported module id: {id}, kind: {kind}");
3095 continue;
3096 };
3097
3098 modules.insert(
3099 id,
3100 (
3101 kind.clone(),
3102 IClientModuleInit::decoder(AsRef::<dyn IClientModuleInit + 'static>::as_ref(init)),
3103 ),
3104 );
3105 }
3106 ModuleDecoderRegistry::from(modules)
3107}