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 {
1219 self.db
1220 .begin_transaction_nc()
1221 .await
1222 .find_by_prefix(&ActiveOperationStateKeyPrefix { operation_id })
1223 .await
1224 .next()
1225 .await
1226 .is_some()
1227 }
1228
1229 pub async fn get_operation_fees(
1248 &self,
1249 operation_id: OperationId,
1250 ) -> Result<Option<Amounts>, OperationNotFoundError> {
1251 if !self.operation_exists(operation_id).await {
1252 return Err(OperationNotFoundError { operation_id });
1253 }
1254
1255 let (active_states, inactive_states) =
1256 self.executor().get_operation_states(operation_id).await;
1257
1258 let states = active_states
1259 .into_iter()
1260 .map(|(state, _)| state)
1261 .chain(inactive_states.into_iter().map(|(state, _)| state));
1262
1263 let accepted_transactions = states
1264 .filter_map(|state| {
1265 let tx_state = state.as_any().downcast_ref::<TxSubmissionStatesSM>()?;
1266
1267 match &tx_state.state {
1268 TxSubmissionStates::Accepted(transaction_id) => Some(*transaction_id),
1269 _ => None,
1270 }
1271 })
1272 .collect::<HashSet<_>>();
1273
1274 let mut dbtx = self.db.begin_transaction_nc().await;
1276 let mut total_fees = Amounts::ZERO;
1277 for txid in &accepted_transactions {
1278 let Some(fees) = dbtx.get_value(&TransactionFeesKey(*txid)).await else {
1279 return Ok(None);
1280 };
1281 total_fees = total_fees
1282 .checked_add(&fees)
1283 .expect("Fee amounts don't overflow in practice");
1284 }
1285
1286 Ok(Some(total_fees))
1287 }
1288
1289 pub async fn await_primary_bitcoin_module_output(
1292 &self,
1293 operation_id: OperationId,
1294 out_point: OutPoint,
1295 ) -> Result<(), TransactionSubmitError> {
1296 self.primary_module_for_unit(AmountUnit::BITCOIN)
1297 .ok_or(TransactionSubmitError::NoPrimaryModule {
1298 unit: AmountUnit::BITCOIN,
1299 })?
1300 .1
1301 .await_primary_module_output(operation_id, out_point)
1302 .await
1303 .map_err(TransactionSubmitError::PrimaryModule)
1304 }
1305
1306 pub fn get_first_module<M: ClientModule>(
1308 &'_ self,
1309 ) -> Result<ClientModuleInstance<'_, M>, ModuleLookupError> {
1310 let module_kind = M::kind();
1311 let id = self.get_first_instance(&module_kind).ok_or_else(|| {
1312 ModuleLookupError::NoModuleOfKind {
1313 kind: module_kind.clone(),
1314 }
1315 })?;
1316 let module: &M = self
1317 .try_get_module(id)
1318 .ok_or(ModuleLookupError::UnknownInstance { instance_id: id })?
1319 .as_any()
1320 .downcast_ref::<M>()
1321 .ok_or(ModuleLookupError::WrongModuleType {
1322 instance_id: id,
1323 expected: std::any::type_name::<M>(),
1324 })?;
1325 let (db, _) = self.db().with_prefix_module_id(id);
1326 Ok(ClientModuleInstance {
1327 id,
1328 db,
1329 api: self.api().with_module(id),
1330 module,
1331 })
1332 }
1333
1334 #[cfg(not(target_family = "wasm"))]
1339 pub fn get_first_module_arc<M: ClientModule>(&self) -> Result<Arc<M>, ModuleLookupError> {
1340 let module_kind = M::kind();
1341 let id = self.get_first_instance(&module_kind).ok_or_else(|| {
1342 ModuleLookupError::NoModuleOfKind {
1343 kind: module_kind.clone(),
1344 }
1345 })?;
1346 let dyn_module = self
1347 .modules
1348 .get(id)
1349 .ok_or(ModuleLookupError::UnknownInstance { instance_id: id })?;
1350 dyn_module
1351 .as_any_arc()
1352 .downcast::<M>()
1353 .map_err(|_| ModuleLookupError::WrongModuleType {
1354 instance_id: id,
1355 expected: std::any::type_name::<M>(),
1356 })
1357 }
1358
1359 pub fn get_module_client_dyn(
1360 &self,
1361 instance_id: ModuleInstanceId,
1362 ) -> Result<&maybe_add_send_sync!(dyn IClientModule), ModuleLookupError> {
1363 self.try_get_module(instance_id)
1364 .ok_or(ModuleLookupError::UnknownInstance { instance_id })
1365 }
1366
1367 pub fn db(&self) -> &Database {
1368 &self.db
1369 }
1370
1371 pub fn endpoints(&self) -> &ConnectorRegistry {
1372 &self.connectors
1373 }
1374
1375 pub async fn transaction_updates(&self, operation_id: OperationId) -> TransactionUpdates {
1378 TransactionUpdates {
1379 update_stream: self.transaction_update_stream(operation_id).await,
1380 }
1381 }
1382
1383 pub fn get_first_instance(&self, module_kind: &ModuleKind) -> Option<ModuleInstanceId> {
1385 self.modules
1386 .iter_modules()
1387 .find(|(_, kind, _module)| *kind == module_kind)
1388 .map(|(instance_id, _, _)| instance_id)
1389 }
1390
1391 pub async fn await_primary_bitcoin_module_outputs(
1394 &self,
1395 operation_id: OperationId,
1396 outputs: Vec<OutPoint>,
1397 ) -> Result<(), TransactionSubmitError> {
1398 for out_point in outputs {
1399 self.await_primary_bitcoin_module_output(operation_id, out_point)
1400 .await?;
1401 }
1402
1403 Ok(())
1404 }
1405
1406 pub async fn get_config_json(&self) -> JsonClientConfig {
1412 self.config().await.to_json()
1413 }
1414
1415 #[doc(hidden)]
1418 pub async fn get_balance_for_btc(&self) -> Result<Amount, ModuleLookupError> {
1421 self.get_balance_for_unit(AmountUnit::BITCOIN).await
1422 }
1423
1424 pub async fn get_balance_for_unit(
1425 &self,
1426 unit: AmountUnit,
1427 ) -> Result<Amount, ModuleLookupError> {
1428 let (id, module) = self
1429 .primary_module_for_unit(unit)
1430 .ok_or(ModuleLookupError::NoPrimaryModule { unit })?;
1431 Ok(module
1432 .get_balance(id, &mut self.db().begin_transaction_nc().await, unit)
1433 .await)
1434 }
1435
1436 pub async fn subscribe_balance_changes(&self, unit: AmountUnit) -> BoxStream<'static, Amount> {
1439 let primary_module_things =
1440 if let Some((primary_module_id, primary_module)) = self.primary_module_for_unit(unit) {
1441 let balance_changes = primary_module.subscribe_balance_changes().await;
1442 let initial_balance = self
1443 .get_balance_for_unit(unit)
1444 .await
1445 .expect("Primary is present");
1446
1447 Some((
1448 primary_module_id,
1449 primary_module.clone(),
1450 balance_changes,
1451 initial_balance,
1452 ))
1453 } else {
1454 None
1455 };
1456 let db = self.db().clone();
1457
1458 Box::pin(async_stream::stream! {
1459 let Some((primary_module_id, primary_module, mut balance_changes, initial_balance)) = primary_module_things else {
1460 pending().await
1463 };
1464
1465
1466 yield initial_balance;
1467 let mut prev_balance = initial_balance;
1468 while let Some(()) = balance_changes.next().await {
1469 let mut dbtx = db.begin_transaction_nc().await;
1470 let balance = primary_module
1471 .get_balance(primary_module_id, &mut dbtx, unit)
1472 .await;
1473
1474 if balance != prev_balance {
1476 prev_balance = balance;
1477 yield balance;
1478 }
1479 }
1480 })
1481 }
1482
1483 async fn make_api_version_request(
1488 delay: Duration,
1489 peer_id: PeerId,
1490 api: &DynGlobalApi,
1491 ) -> (
1492 PeerId,
1493 Result<SupportedApiVersionsSummary, fedimint_connectors::error::ServerError>,
1494 ) {
1495 runtime::sleep(delay).await;
1496 (
1497 peer_id,
1498 api.request_single_peer::<SupportedApiVersionsSummary>(
1499 VERSION_ENDPOINT.to_owned(),
1500 ApiRequestErased::default(),
1501 peer_id,
1502 )
1503 .await,
1504 )
1505 }
1506
1507 fn create_api_version_backoff() -> impl Iterator<Item = Duration> {
1513 custom_backoff(Duration::from_millis(200), Duration::from_secs(600), None)
1514 }
1515
1516 pub async fn fetch_common_api_versions_from_all_peers(
1519 num_peers: NumPeers,
1520 api: DynGlobalApi,
1521 db: Database,
1522 num_responses_sender: watch::Sender<usize>,
1523 ) {
1524 let mut backoff = Self::create_api_version_backoff();
1525
1526 let mut requests = FuturesUnordered::new();
1529
1530 for peer_id in num_peers.peer_ids() {
1531 requests.push(Self::make_api_version_request(
1532 Duration::ZERO,
1533 peer_id,
1534 &api,
1535 ));
1536 }
1537
1538 let mut num_responses = 0;
1539
1540 while let Some((peer_id, response)) = requests.next().await {
1541 let retry = match response {
1542 Err(err) => {
1543 let has_previous_response = db
1544 .begin_transaction_nc()
1545 .await
1546 .get_value(&PeerLastApiVersionsSummaryKey(peer_id))
1547 .await
1548 .is_some();
1549 debug!(
1550 target: LOG_CLIENT,
1551 %peer_id,
1552 err = %err.fmt_compact(),
1553 %has_previous_response,
1554 "Failed to refresh API versions of a peer"
1555 );
1556
1557 !has_previous_response
1558 }
1559 Ok(o) => {
1560 let mut dbtx = db.begin_transaction().await;
1563 dbtx.insert_entry(
1564 &PeerLastApiVersionsSummaryKey(peer_id),
1565 &PeerLastApiVersionsSummary(o),
1566 )
1567 .await;
1568 dbtx.commit_tx().await;
1569 false
1570 }
1571 };
1572
1573 if retry {
1574 requests.push(Self::make_api_version_request(
1575 backoff.next().expect("Keeps retrying"),
1576 peer_id,
1577 &api,
1578 ));
1579 } else {
1580 num_responses += 1;
1581 num_responses_sender.send_replace(num_responses);
1582 }
1583 }
1584 }
1585
1586 pub async fn fetch_peers_api_versions_from_threshold_of_peers(
1590 num_peers: NumPeers,
1591 api: DynGlobalApi,
1592 ) -> BTreeMap<PeerId, SupportedApiVersionsSummary> {
1593 let mut backoff = Self::create_api_version_backoff();
1594
1595 let mut requests = FuturesUnordered::new();
1598
1599 for peer_id in num_peers.peer_ids() {
1600 requests.push(Self::make_api_version_request(
1601 Duration::ZERO,
1602 peer_id,
1603 &api,
1604 ));
1605 }
1606
1607 let mut successful_responses = BTreeMap::new();
1608
1609 while successful_responses.len() < num_peers.threshold()
1610 && let Some((peer_id, response)) = requests.next().await
1611 {
1612 let retry = match response {
1613 Err(err) => {
1614 debug!(
1615 target: LOG_CLIENT,
1616 %peer_id,
1617 err = %err.fmt_compact(),
1618 "Failed to fetch API versions from peer"
1619 );
1620 true
1621 }
1622 Ok(response) => {
1623 successful_responses.insert(peer_id, response);
1624 false
1625 }
1626 };
1627
1628 if retry {
1629 requests.push(Self::make_api_version_request(
1630 backoff.next().expect("Keeps retrying"),
1631 peer_id,
1632 &api,
1633 ));
1634 }
1635 }
1636
1637 successful_responses
1638 }
1639
1640 pub async fn fetch_common_api_versions(
1642 config: &ClientConfig,
1643 api: &DynGlobalApi,
1644 ) -> BTreeMap<PeerId, SupportedApiVersionsSummary> {
1645 debug!(
1646 target: LOG_CLIENT,
1647 "Fetching common api versions"
1648 );
1649
1650 let num_peers = NumPeers::from(config.global.api_endpoints.len());
1651
1652 Self::fetch_peers_api_versions_from_threshold_of_peers(num_peers, api.clone()).await
1653 }
1654
1655 pub async fn write_api_version_cache(
1659 dbtx: &mut DatabaseTransaction<'_>,
1660 api_version_set: ApiVersionSet,
1661 ) {
1662 debug!(
1663 target: LOG_CLIENT,
1664 value = ?api_version_set,
1665 "Writing API version set to cache"
1666 );
1667
1668 dbtx.insert_entry(
1669 &CachedApiVersionSetKey,
1670 &CachedApiVersionSet(api_version_set),
1671 )
1672 .await;
1673 }
1674
1675 pub async fn store_prefetched_api_versions(
1680 db: &Database,
1681 config: &ClientConfig,
1682 client_module_init: &ClientModuleInitRegistry,
1683 peer_api_versions: &BTreeMap<PeerId, SupportedApiVersionsSummary>,
1684 ) {
1685 debug!(
1686 target: LOG_CLIENT,
1687 "Storing {} prefetched peer API version responses and calculating common version set",
1688 peer_api_versions.len()
1689 );
1690
1691 let mut dbtx = db.begin_transaction().await;
1692 let client_supported_versions =
1694 Self::supported_api_versions_summary_static(config, client_module_init);
1695 match fedimint_client_module::api_version_discovery::discover_common_api_versions_set(
1696 &client_supported_versions,
1697 peer_api_versions,
1698 ) {
1699 Ok(common_api_versions) => {
1700 Self::write_api_version_cache(&mut dbtx.to_ref_nc(), common_api_versions).await;
1702 debug!(target: LOG_CLIENT, "Calculated and stored common API version set");
1703 }
1704 Err(err) => {
1705 debug!(target: LOG_CLIENT, err = %err.fmt_compact(), "Failed to calculate common API versions from prefetched data");
1706 }
1707 }
1708
1709 for (peer_id, peer_api_versions) in peer_api_versions {
1711 dbtx.insert_entry(
1712 &PeerLastApiVersionsSummaryKey(*peer_id),
1713 &PeerLastApiVersionsSummary(peer_api_versions.clone()),
1714 )
1715 .await;
1716 }
1717 dbtx.commit_tx().await;
1718 debug!(target: LOG_CLIENT, "Stored individual peer API version responses");
1719 }
1720
1721 pub fn supported_api_versions_summary_static(
1723 config: &ClientConfig,
1724 client_module_init: &ClientModuleInitRegistry,
1725 ) -> SupportedApiVersionsSummary {
1726 SupportedApiVersionsSummary {
1727 core: SupportedCoreApiVersions {
1728 core_consensus: config.global.consensus_version,
1729 api: MultiApiVersion::try_from_iter(SUPPORTED_CORE_API_VERSIONS.to_owned())
1730 .expect("must not have conflicting versions"),
1731 },
1732 modules: config
1733 .modules
1734 .iter()
1735 .filter_map(|(&module_instance_id, module_config)| {
1736 client_module_init
1737 .get(module_config.kind())
1738 .map(|module_init| {
1739 (
1740 module_instance_id,
1741 SupportedModuleApiVersions {
1742 core_consensus: config.global.consensus_version,
1743 module_consensus: module_config.version,
1744 api: module_init.supported_api_versions(),
1745 },
1746 )
1747 })
1748 })
1749 .collect(),
1750 }
1751 }
1752
1753 pub async fn load_and_refresh_common_api_version(
1754 &self,
1755 ) -> Result<ApiVersionSet, ApiVersionDiscoveryError> {
1756 Self::load_and_refresh_common_api_version_static(
1757 &self.config().await,
1758 &self.module_inits,
1759 self.connectors.clone(),
1760 &self.api,
1761 &self.db,
1762 &self.task_group,
1763 &self.client_span,
1764 )
1765 .await
1766 }
1767
1768 pub async fn refresh_api_versions(&self) -> Result<ApiVersionSet, ApiVersionDiscoveryError> {
1774 Self::refresh_common_api_version_static(
1775 &self.config().await,
1776 &self.module_inits,
1777 &self.api,
1778 &self.db,
1779 self.task_group.clone(),
1780 &self.client_span,
1781 true,
1782 )
1783 .await
1784 }
1785
1786 pub(crate) async fn load_and_refresh_common_api_version_static(
1792 config: &ClientConfig,
1793 module_init: &ClientModuleInitRegistry,
1794 connectors: ConnectorRegistry,
1795 api: &DynGlobalApi,
1796 db: &Database,
1797 task_group: &TaskGroup,
1798 client_span: &Span,
1799 ) -> Result<ApiVersionSet, ApiVersionDiscoveryError> {
1800 if let Some(v) = db
1801 .begin_transaction_nc()
1802 .await
1803 .get_value(&CachedApiVersionSetKey)
1804 .await
1805 {
1806 client_span.in_scope(|| {
1807 debug!(
1808 target: LOG_CLIENT,
1809 "Found existing cached common api versions"
1810 );
1811 });
1812 let config = config.clone();
1813 let client_module_init = module_init.clone();
1814 let api = api.clone();
1815 let db = db.clone();
1816 let task_group = task_group.clone();
1817 let client_span_owned = client_span.clone();
1818 task_group.clone().spawn_cancellable_with_span(
1821 client_span.clone(),
1822 "refresh_common_api_version_static",
1823 async move {
1824 connectors.wait_for_initialized_connections().await;
1825
1826 if let Err(error) = Self::refresh_common_api_version_static(
1827 &config,
1828 &client_module_init,
1829 &api,
1830 &db,
1831 task_group,
1832 &client_span_owned,
1833 false,
1834 )
1835 .await
1836 {
1837 warn!(
1838 target: LOG_CLIENT,
1839 err = %error.fmt_compact(), "Failed to discover common api versions"
1840 );
1841 }
1842 },
1843 );
1844
1845 return Ok(v.0);
1846 }
1847
1848 info!(
1849 target: LOG_CLIENT,
1850 "Fetching initial API versions "
1851 );
1852 Self::refresh_common_api_version_static(
1853 config,
1854 module_init,
1855 api,
1856 db,
1857 task_group.clone(),
1858 client_span,
1859 true,
1860 )
1861 .await
1862 }
1863
1864 async fn refresh_common_api_version_static(
1865 config: &ClientConfig,
1866 client_module_init: &ClientModuleInitRegistry,
1867 api: &DynGlobalApi,
1868 db: &Database,
1869 task_group: TaskGroup,
1870 client_span: &Span,
1871 block_until_ok: bool,
1872 ) -> Result<ApiVersionSet, ApiVersionDiscoveryError> {
1873 debug!(
1874 target: LOG_CLIENT,
1875 "Refreshing common api versions"
1876 );
1877
1878 let (num_responses_sender, mut num_responses_receiver) = tokio::sync::watch::channel(0);
1879 let num_peers = NumPeers::from(config.global.api_endpoints.len());
1880
1881 task_group.spawn_cancellable_with_span(
1882 client_span.clone(),
1883 "refresh peers api versions",
1884 Client::fetch_common_api_versions_from_all_peers(
1885 num_peers,
1886 api.clone(),
1887 db.clone(),
1888 num_responses_sender,
1889 ),
1890 );
1891
1892 let common_api_versions = loop {
1893 let _: Result<_, Elapsed> = runtime::timeout(
1901 Duration::from_secs(30),
1902 num_responses_receiver.wait_for(|num| num_peers.threshold() <= *num),
1903 )
1904 .await;
1905
1906 let peer_api_version_sets = Self::load_peers_last_api_versions(db, num_peers).await;
1907
1908 match fedimint_client_module::api_version_discovery::discover_common_api_versions_set(
1909 &Self::supported_api_versions_summary_static(config, client_module_init),
1910 &peer_api_version_sets,
1911 ) {
1912 Ok(o) => break o,
1913 Err(err) if block_until_ok => {
1914 warn!(
1915 target: LOG_CLIENT,
1916 err = %err.fmt_compact(),
1917 "Failed to discover API version to use. Retrying..."
1918 );
1919 continue;
1920 }
1921 Err(e) => return Err(e),
1922 }
1923 };
1924
1925 debug!(
1926 target: LOG_CLIENT,
1927 value = ?common_api_versions,
1928 "Updating the cached common api versions"
1929 );
1930 let mut dbtx = db.begin_transaction().await;
1931 let _ = dbtx
1932 .insert_entry(
1933 &CachedApiVersionSetKey,
1934 &CachedApiVersionSet(common_api_versions.clone()),
1935 )
1936 .await;
1937
1938 dbtx.commit_tx().await;
1939
1940 Ok(common_api_versions)
1941 }
1942
1943 pub async fn get_metadata(&self) -> Metadata {
1945 self.db
1946 .begin_transaction_nc()
1947 .await
1948 .get_value(&ClientMetadataKey)
1949 .await
1950 .unwrap_or_else(|| {
1951 warn!(
1952 target: LOG_CLIENT,
1953 "Missing existing metadata. This key should have been set on Client init"
1954 );
1955 Metadata::empty()
1956 })
1957 }
1958
1959 pub async fn set_metadata(&self, metadata: &Metadata) {
1961 self.db
1962 .autocommit::<_, _, Infallible>(
1963 |dbtx, _| {
1964 Box::pin(async {
1965 Self::set_metadata_dbtx(dbtx, metadata).await;
1966 Ok(())
1967 })
1968 },
1969 None,
1970 )
1971 .await
1972 .expect("Failed to autocommit metadata");
1973 }
1974
1975 pub fn has_pending_recoveries(&self) -> bool {
1976 !self
1977 .client_recovery_status_receiver
1978 .borrow()
1979 .values()
1980 .all(RecoveryStatus::is_successfully_done)
1981 }
1982
1983 pub fn all_modules_usable(&self) -> bool {
1994 self.client_recovery_status_receiver
1995 .borrow()
1996 .keys()
1997 .all(|module_instance_id| self.modules.get(*module_instance_id).is_some())
1998 }
1999
2000 pub async fn wait_for_all_recoveries(&self) -> Result<(), RecoveryError> {
2014 Self::wait_for_recoveries(
2015 self.client_recovery_status_receiver.clone(),
2016 |_module_instance_id| true,
2017 )
2018 .await
2019 }
2020
2021 async fn wait_for_recoveries(
2030 mut status_receiver: watch::Receiver<BTreeMap<ModuleInstanceId, RecoveryStatus>>,
2031 module_filter: impl Fn(ModuleInstanceId) -> bool,
2032 ) -> Result<(), RecoveryError> {
2033 let failure = status_receiver
2034 .wait_for(|statuses| {
2035 let matching = || {
2036 statuses
2037 .iter()
2038 .filter(|(module_instance_id, _status)| module_filter(**module_instance_id))
2039 .map(|(_module_instance_id, status)| status)
2040 };
2041
2042 matching().any(|status| matches!(status, RecoveryStatus::Failed { .. }))
2046 || matching().all(RecoveryStatus::is_successfully_done)
2047 })
2048 .await
2049 .map_err(|_closed| RecoveryError::ClientStopped)?
2050 .iter()
2053 .find_map(|(module_instance_id, status)| match status {
2054 RecoveryStatus::Failed { error, .. } if module_filter(*module_instance_id) => {
2055 Some((*module_instance_id, error.clone()))
2056 }
2057 _ => None,
2058 });
2059
2060 match failure {
2061 Some((module_instance_id, source)) => Err(RecoveryError::Failed {
2062 module_instance_id,
2063 source,
2064 }),
2065 None => Ok(()),
2066 }
2067 }
2068
2069 pub fn subscribe_to_recovery_progress(
2079 &self,
2080 ) -> impl Stream<Item = (ModuleInstanceId, RecoveryProgress)> + use<> {
2081 WatchStream::new(self.client_recovery_status_receiver.clone()).flat_map(|statuses| {
2082 futures::stream::iter(
2083 statuses
2084 .into_iter()
2085 .map(|(module_instance_id, status)| (module_instance_id, status.progress())),
2086 )
2087 })
2088 }
2089
2090 pub async fn wait_for_module_kind_recovery(
2099 &self,
2100 module_kind: ModuleKind,
2101 ) -> Result<(), RecoveryError> {
2102 let config = self.config().await;
2103 Self::wait_for_recoveries(
2104 self.client_recovery_status_receiver.clone(),
2105 move |module_instance_id| {
2106 config
2107 .modules
2108 .get(&module_instance_id)
2109 .is_some_and(|module| module.kind == module_kind)
2110 },
2111 )
2112 .await
2113 }
2114
2115 pub async fn wait_for_all_active_state_machines(&self) {
2116 loop {
2117 if self.executor.get_active_states().await.is_empty() {
2118 break;
2119 }
2120 sleep(Duration::from_millis(100)).await;
2121 }
2122 }
2123
2124 pub async fn set_metadata_dbtx(dbtx: &mut DatabaseTransaction<'_>, metadata: &Metadata) {
2126 dbtx.insert_new_entry(&ClientMetadataKey, metadata).await;
2127 }
2128
2129 fn spawn_module_recoveries_task(
2130 &self,
2131 recovery_sender: watch::Sender<BTreeMap<ModuleInstanceId, RecoveryStatus>>,
2132 module_recoveries: BTreeMap<ModuleInstanceId, ModuleRecoveryFuture>,
2133 module_recovery_progress_receivers: BTreeMap<
2134 ModuleInstanceId,
2135 watch::Receiver<RecoveryProgress>,
2136 >,
2137 module_kinds: BTreeMap<ModuleInstanceId, ModuleKind>,
2140 ) {
2141 let db = self.db.clone();
2142 let log_ordering_wakeup_tx = self.log_ordering_wakeup_tx.clone();
2143 self.spawn("module recoveries", |_task_handle| async {
2148 Self::run_module_recoveries_task(
2149 db,
2150 log_ordering_wakeup_tx,
2151 recovery_sender,
2152 module_recoveries,
2153 module_recovery_progress_receivers,
2154 module_kinds,
2155 )
2156 .await;
2157 });
2158 }
2159
2160 async fn run_module_recoveries_task(
2161 db: Database,
2162 log_ordering_wakeup_tx: watch::Sender<()>,
2163 recovery_sender: watch::Sender<BTreeMap<ModuleInstanceId, RecoveryStatus>>,
2164 module_recoveries: BTreeMap<ModuleInstanceId, ModuleRecoveryFuture>,
2165 module_recovery_progress_receivers: BTreeMap<
2166 ModuleInstanceId,
2167 watch::Receiver<RecoveryProgress>,
2168 >,
2169 module_kinds: BTreeMap<ModuleInstanceId, ModuleKind>,
2170 ) {
2171 debug!(target: LOG_CLIENT_RECOVERY, num_modules=%module_recovery_progress_receivers.len(), "Staring module recoveries");
2172
2173 enum RecoveryUpdate {
2177 Progress(RecoveryProgress),
2178 Completed(Option<Amount>),
2179 }
2180
2181 let mut completed_stream = Vec::new();
2182 let progress_stream = futures::stream::FuturesUnordered::new();
2183
2184 for (module_instance_id, f) in module_recoveries {
2185 let recovery_sender = recovery_sender.clone();
2186 completed_stream.push(futures::stream::once(Box::pin(async move {
2187 match f.await {
2188 Ok(amount) => (module_instance_id, RecoveryUpdate::Completed(amount)),
2189 Err(err) => {
2190 warn!(
2191 target: LOG_CLIENT,
2192 err = %err.fmt_compact(), module_instance_id, "Module recovery failed"
2193 );
2194 let error = Arc::new(err);
2195 recovery_sender.send_modify(|statuses| {
2202 let last_progress = statuses
2203 .get(&module_instance_id)
2204 .expect("existing status must be present")
2205 .progress();
2206 statuses.insert(
2207 module_instance_id,
2208 RecoveryStatus::Failed {
2209 last_progress,
2210 error,
2211 },
2212 );
2213 });
2214 futures::future::pending::<()>().await;
2224 unreachable!()
2225 }
2226 }
2227 })));
2228 }
2229
2230 for (module_instance_id, rx) in module_recovery_progress_receivers {
2231 progress_stream.push(
2232 tokio_stream::wrappers::WatchStream::new(rx)
2233 .fuse()
2234 .map(move |progress| (module_instance_id, RecoveryUpdate::Progress(progress))),
2235 );
2236 }
2237
2238 let mut futures = futures::stream::select(
2239 futures::stream::select_all(progress_stream),
2240 futures::stream::select_all(completed_stream),
2241 );
2242
2243 while let Some((module_instance_id, update)) = futures.next().await {
2244 let prev_status = recovery_sender
2248 .borrow()
2249 .get(&module_instance_id)
2250 .expect("existing status must be present")
2251 .clone();
2252
2253 if matches!(prev_status, RecoveryStatus::Failed { .. }) {
2263 debug!(
2264 target: LOG_CLIENT_RECOVERY,
2265 module_instance_id,
2266 "Ignoring a recovery update of a module whose recovery already failed"
2267 );
2268 continue;
2269 }
2270
2271 let prev_progress = prev_status.progress();
2272
2273 if let RecoveryUpdate::Progress(progress) = &update {
2279 if progress.is_done() {
2280 warn!(
2281 target: LOG_CLIENT_RECOVERY,
2282 module_instance_id,
2283 "Module bypassed the sanctioned recovery progress reporting API and reported a completed recovery progress. Ignoring"
2284 );
2285 continue;
2286 }
2287
2288 if progress.is_none() && !prev_progress.is_none() && !prev_progress.is_done() {
2295 warn!(
2296 target: LOG_CLIENT_RECOVERY,
2297 module_instance_id,
2298 "Module bypassed the sanctioned recovery progress reporting API and reported a none recovery progress, regressing its previous one. Ignoring"
2299 );
2300 continue;
2301 }
2302 }
2303
2304 let mut dbtx = db.begin_transaction().await;
2305
2306 let (progress, recovered_amount) = if prev_progress.is_done() {
2312 (prev_progress, None)
2314 } else {
2315 match update {
2316 RecoveryUpdate::Progress(progress) => (progress, None),
2317 RecoveryUpdate::Completed(amount) => (prev_progress.to_complete(), amount),
2318 }
2319 };
2320
2321 if !prev_progress.is_done() && progress.is_done() {
2322 info!(
2323 target: LOG_CLIENT,
2324 module_instance_id,
2325 progress = format!("{}/{}", progress.complete, progress.total),
2326 amount = ?recovered_amount,
2327 "Recovery complete"
2328 );
2329 dbtx.log_event(
2330 log_ordering_wakeup_tx.clone(),
2331 None,
2332 ModuleRecoveryCompleted {
2333 module_id: module_instance_id,
2334 kind: module_kinds.get(&module_instance_id).cloned(),
2335 amount: recovered_amount,
2336 },
2337 )
2338 .await;
2339 } else {
2340 info!(
2341 target: LOG_CLIENT,
2342 module_instance_id,
2343 kind = ?module_kinds.get(&module_instance_id),
2344 progress = format!("{}/{}", progress.complete, progress.total),
2345 "Recovery progress"
2346 );
2347 }
2348
2349 dbtx.insert_entry(
2350 &ClientModuleRecovery { module_instance_id },
2351 &ClientModuleRecoveryState { progress },
2352 )
2353 .await;
2354 dbtx.commit_tx().await;
2355
2356 recovery_sender.send_modify(|statuses| {
2357 statuses.insert(module_instance_id, RecoveryStatus::InProgress(progress));
2358 });
2359 }
2360 debug!(target: LOG_CLIENT_RECOVERY, "Recovery executor stopped");
2361 }
2362
2363 async fn load_peers_last_api_versions(
2364 db: &Database,
2365 num_peers: NumPeers,
2366 ) -> BTreeMap<PeerId, SupportedApiVersionsSummary> {
2367 let mut peer_api_version_sets = BTreeMap::new();
2368
2369 let mut dbtx = db.begin_transaction_nc().await;
2370 for peer_id in num_peers.peer_ids() {
2371 if let Some(v) = dbtx
2372 .get_value(&PeerLastApiVersionsSummaryKey(peer_id))
2373 .await
2374 {
2375 peer_api_version_sets.insert(peer_id, v.0);
2376 }
2377 }
2378 drop(dbtx);
2379 peer_api_version_sets
2380 }
2381
2382 pub async fn get_peer_url_announcements(&self) -> BTreeMap<PeerId, SignedApiAnnouncement> {
2385 self.db()
2386 .begin_transaction_nc()
2387 .await
2388 .find_by_prefix(&ApiAnnouncementPrefix)
2389 .await
2390 .map(|(announcement_key, announcement)| (announcement_key.0, announcement))
2391 .collect()
2392 .await
2393 }
2394
2395 pub async fn get_guardian_metadata(
2397 &self,
2398 ) -> BTreeMap<PeerId, fedimint_core::net::guardian_metadata::SignedGuardianMetadata> {
2399 self.db()
2400 .begin_transaction_nc()
2401 .await
2402 .find_by_prefix(&crate::guardian_metadata::GuardianMetadataPrefix)
2403 .await
2404 .map(|(key, metadata)| (key.0, metadata))
2405 .collect()
2406 .await
2407 }
2408
2409 pub async fn get_peer_urls(&self) -> BTreeMap<PeerId, SafeUrl> {
2411 get_api_urls(&self.db, &self.config().await, self.iroh_enable_next).await
2412 }
2413
2414 pub async fn invite_code(&self, peer: PeerId) -> Option<InviteCode> {
2417 self.get_peer_urls()
2418 .await
2419 .into_iter()
2420 .find_map(|(peer_id, url)| (peer == peer_id).then_some(url))
2421 .map(|peer_url| {
2422 InviteCode::new(
2423 peer_url.clone(),
2424 peer,
2425 self.federation_id(),
2426 self.api_secret.clone(),
2427 )
2428 })
2429 }
2430
2431 pub async fn get_guardian_public_keys_blocking(
2435 &self,
2436 ) -> BTreeMap<PeerId, fedimint_core::secp256k1::PublicKey> {
2437 self.db
2438 .autocommit(
2439 |dbtx, _| {
2440 Box::pin(async move {
2441 let config = self.config().await;
2442
2443 let guardian_pub_keys = self
2444 .get_or_backfill_broadcast_public_keys(dbtx, config)
2445 .await;
2446
2447 Result::<_, ()>::Ok(guardian_pub_keys)
2448 })
2449 },
2450 None,
2451 )
2452 .await
2453 .expect("Will retry forever")
2454 }
2455
2456 async fn get_or_backfill_broadcast_public_keys(
2457 &self,
2458 dbtx: &mut DatabaseTransaction<'_>,
2459 config: ClientConfig,
2460 ) -> BTreeMap<PeerId, PublicKey> {
2461 match config.global.broadcast_public_keys {
2462 Some(guardian_pub_keys) => guardian_pub_keys,
2463 _ => {
2464 let (guardian_pub_keys, new_config) = self.fetch_and_update_config(config).await;
2465
2466 dbtx.insert_entry(&ClientConfigKey, &new_config).await;
2467 *(self.config.write().await) = new_config;
2468 guardian_pub_keys
2469 }
2470 }
2471 }
2472
2473 pub async fn fetch_session_count(&self) -> FederationResult<u64> {
2474 self.api.session_count().await
2475 }
2476
2477 async fn fetch_and_update_config(
2478 &self,
2479 config: ClientConfig,
2480 ) -> (BTreeMap<PeerId, PublicKey>, ClientConfig) {
2481 let fetched_config = retry(
2482 "Fetching guardian public keys",
2483 backoff_util::background_backoff(),
2484 || async {
2485 self.api
2486 .request_current_consensus::<ClientConfig>(
2487 CLIENT_CONFIG_ENDPOINT.to_owned(),
2488 ApiRequestErased::default(),
2489 )
2490 .await
2491 },
2492 )
2493 .await
2494 .expect("Will never return on error");
2495
2496 let Some(guardian_pub_keys) = fetched_config.global.broadcast_public_keys else {
2497 warn!(
2498 target: LOG_CLIENT,
2499 "Guardian public keys not found in fetched config, server not updated to 0.4 yet"
2500 );
2501 pending::<()>().await;
2502 unreachable!("Pending will never return");
2503 };
2504
2505 let new_config = ClientConfig {
2506 global: GlobalClientConfig {
2507 broadcast_public_keys: Some(guardian_pub_keys.clone()),
2508 ..config.global
2509 },
2510 modules: config.modules,
2511 };
2512 (guardian_pub_keys, new_config)
2513 }
2514
2515 pub fn handle_global_rpc(
2522 &self,
2523 method: String,
2524 params: serde_json::Value,
2525 ) -> BoxStream<'_, Result<serde_json::Value, GlobalRpcError>> {
2526 Box::pin(try_stream! {
2527 match method.as_str() {
2528 "get_balance" => {
2529 let balance = self.get_balance_for_btc().await.unwrap_or_default();
2530 yield serde_json::to_value(balance)?;
2531 }
2532 "subscribe_balance_changes" => {
2533 let req: GetBalanceChangesRequest= serde_json::from_value(params)?;
2534 let mut stream = self.subscribe_balance_changes(req.unit).await;
2535 while let Some(balance) = stream.next().await {
2536 yield serde_json::to_value(balance)?;
2537 }
2538 }
2539 "get_config" => {
2540 let config = self.config().await;
2541 yield serde_json::to_value(config)?;
2542 }
2543 "get_federation_id" => {
2544 let federation_id = self.federation_id();
2545 yield serde_json::to_value(federation_id)?;
2546 }
2547 "get_invite_code" => {
2548 let req: GetInviteCodeRequest = serde_json::from_value(params)?;
2549 let invite_code = self.invite_code(req.peer).await;
2550 yield serde_json::to_value(invite_code)?;
2551 }
2552 "get_operation" => {
2553 let req: GetOperationIdRequest = serde_json::from_value(params)?;
2554 let operation = self.operation_log().get_operation(req.operation_id).await;
2555 yield serde_json::to_value(operation)?;
2556 }
2557 "list_operations" => {
2558 let req: ListOperationsParams = serde_json::from_value(params)?;
2559 let limit = if req.limit.is_none() && req.last_seen.is_none() {
2560 usize::MAX
2561 } else {
2562 req.limit.unwrap_or(usize::MAX)
2563 };
2564 let operations = self.operation_log()
2565 .paginate_operations_rev(limit, req.last_seen)
2566 .await;
2567 yield serde_json::to_value(operations)?;
2568 }
2569 "get_event_log" => {
2570 let req: GetEventLogRequest = serde_json::from_value(params)?;
2571 let limit = req
2572 .limit
2573 .unwrap_or(DEFAULT_EVENT_LOG_PAGE_SIZE)
2574 .min(MAX_EVENT_LOG_PAGE_SIZE);
2575 let events = self.get_event_log(req.pos, limit).await;
2576 yield serde_json::to_value(events)?;
2577 }
2578 "session_count" => {
2579 let count = self.fetch_session_count().await?;
2580 yield serde_json::to_value(count)?;
2581 }
2582 "has_pending_recoveries" => {
2583 let has_pending = self.has_pending_recoveries();
2584 yield serde_json::to_value(has_pending)?;
2585 }
2586 "wait_for_all_recoveries" => {
2587 self.wait_for_all_recoveries().await?;
2588 yield serde_json::Value::Null;
2589 }
2590 "subscribe_to_recovery_progress" => {
2591 let mut stream = self.subscribe_to_recovery_progress();
2592 while let Some((module_id, progress)) = stream.next().await {
2593 yield serde_json::json!({
2594 "module_id": module_id,
2595 "progress": progress
2596 });
2597 }
2598 }
2599 #[allow(deprecated)]
2600 "backup_to_federation" => {
2601 let metadata = if params.is_null() {
2602 Metadata::from_json_serialized(serde_json::json!({}))
2603 } else {
2604 Metadata::from_json_serialized(params)
2605 };
2606 self.backup_to_federation(metadata).await?;
2607 yield serde_json::Value::Null;
2608 }
2609 _ => {
2610 Err(GlobalRpcError::UnknownMethod { method: method.clone() })?;
2611 unreachable!()
2612 },
2613 }
2614 })
2615 }
2616
2617 pub async fn log_event<E>(&self, module_id: Option<ModuleInstanceId>, event: E)
2618 where
2619 E: Event + Send,
2620 {
2621 let mut dbtx = self.db.begin_transaction().await;
2622 self.log_event_dbtx(&mut dbtx, module_id, event).await;
2623 dbtx.commit_tx().await;
2624 }
2625
2626 pub async fn log_event_dbtx<E, Cap>(
2627 &self,
2628 dbtx: &mut DatabaseTransaction<'_, Cap>,
2629 module_id: Option<ModuleInstanceId>,
2630 event: E,
2631 ) where
2632 E: Event + Send,
2633 Cap: Send,
2634 {
2635 dbtx.log_event(self.log_ordering_wakeup_tx.clone(), module_id, event)
2636 .await;
2637 }
2638
2639 pub async fn log_event_raw_dbtx<Cap>(
2640 &self,
2641 dbtx: &mut DatabaseTransaction<'_, Cap>,
2642 kind: EventKind,
2643 module: Option<(ModuleKind, ModuleInstanceId)>,
2644 payload: Vec<u8>,
2645 persist: EventPersistence,
2646 ) where
2647 Cap: Send,
2648 {
2649 let module_id = module.as_ref().map(|m| m.1);
2650 let module_kind = module.map(|m| m.0);
2651 dbtx.log_event_raw(
2652 self.log_ordering_wakeup_tx.clone(),
2653 kind,
2654 module_kind,
2655 module_id,
2656 payload,
2657 persist,
2658 )
2659 .await;
2660 }
2661
2662 pub fn built_in_application_event_log_tracker(&self) -> DynEventLogTrimableTracker {
2674 struct BuiltInApplicationEventLogTracker;
2675
2676 #[apply(async_trait_maybe_send!)]
2677 impl EventLogTrimableTracker for BuiltInApplicationEventLogTracker {
2678 async fn store(
2680 &mut self,
2681 dbtx: &mut DatabaseTransaction<NonCommittable>,
2682 pos: EventLogTrimableId,
2683 ) -> Result<(), EventLogTrackerError> {
2684 dbtx.insert_entry(&DefaultApplicationEventLogKey, &pos)
2685 .await;
2686 Ok(())
2687 }
2688
2689 async fn load(
2691 &mut self,
2692 dbtx: &mut DatabaseTransaction<NonCommittable>,
2693 ) -> Result<Option<EventLogTrimableId>, EventLogTrackerError> {
2694 Ok(dbtx.get_value(&DefaultApplicationEventLogKey).await)
2695 }
2696 }
2697 Box::new(BuiltInApplicationEventLogTracker)
2698 }
2699
2700 pub async fn handle_historical_events<F, R, E>(
2708 &self,
2709 tracker: fedimint_eventlog::DynEventLogTracker,
2710 handler_fn: F,
2711 ) -> Result<(), EventHandlerError<E>>
2712 where
2713 F: Fn(&mut DatabaseTransaction<NonCommittable>, EventLogEntry) -> R,
2714 R: Future<Output = Result<(), E>>,
2715 E: std::error::Error + 'static,
2716 {
2717 fedimint_eventlog::handle_events(
2718 self.db.clone(),
2719 tracker,
2720 self.log_event_added_rx.clone(),
2721 handler_fn,
2722 )
2723 .await
2724 }
2725
2726 pub async fn handle_events<F, R, E>(
2745 &self,
2746 tracker: fedimint_eventlog::DynEventLogTrimableTracker,
2747 handler_fn: F,
2748 ) -> Result<(), EventHandlerError<E>>
2749 where
2750 F: Fn(&mut DatabaseTransaction<NonCommittable>, EventLogEntry) -> R,
2751 R: Future<Output = Result<(), E>>,
2752 E: std::error::Error + 'static,
2753 {
2754 fedimint_eventlog::handle_trimable_events(
2755 self.db.clone(),
2756 tracker,
2757 self.log_event_added_rx.clone(),
2758 handler_fn,
2759 )
2760 .await
2761 }
2762
2763 pub async fn get_event_log(
2764 &self,
2765 pos: Option<EventLogId>,
2766 limit: u64,
2767 ) -> Vec<PersistedLogEntry> {
2768 self.get_event_log_dbtx(&mut self.db.begin_transaction_nc().await, pos, limit)
2769 .await
2770 }
2771
2772 pub async fn get_next_event_log_id(&self) -> EventLogId {
2775 self.db
2776 .begin_transaction_nc()
2777 .await
2778 .get_next_event_log_id()
2779 .await
2780 }
2781
2782 pub async fn get_event_log_trimable(
2783 &self,
2784 pos: Option<EventLogTrimableId>,
2785 limit: u64,
2786 ) -> Vec<PersistedLogEntry> {
2787 self.get_event_log_trimable_dbtx(&mut self.db.begin_transaction_nc().await, pos, limit)
2788 .await
2789 }
2790
2791 pub async fn get_event_log_dbtx<Cap>(
2792 &self,
2793 dbtx: &mut DatabaseTransaction<'_, Cap>,
2794 pos: Option<EventLogId>,
2795 limit: u64,
2796 ) -> Vec<PersistedLogEntry>
2797 where
2798 Cap: Send,
2799 {
2800 dbtx.get_event_log(pos, limit).await
2801 }
2802
2803 pub async fn get_event_log_trimable_dbtx<Cap>(
2804 &self,
2805 dbtx: &mut DatabaseTransaction<'_, Cap>,
2806 pos: Option<EventLogTrimableId>,
2807 limit: u64,
2808 ) -> Vec<PersistedLogEntry>
2809 where
2810 Cap: Send,
2811 {
2812 dbtx.get_event_log_trimable(pos, limit).await
2813 }
2814
2815 pub fn get_event_log_transient_receiver(&self) -> broadcast::Receiver<EventLogEntry> {
2817 self.log_event_added_transient_tx.subscribe()
2818 }
2819
2820 pub fn log_event_added_rx(&self) -> watch::Receiver<()> {
2822 self.log_event_added_rx.clone()
2823 }
2824
2825 pub fn iroh_enable_dht(&self) -> bool {
2826 self.iroh_enable_dht
2827 }
2828
2829 pub fn iroh_enable_next(&self) -> bool {
2832 self.iroh_enable_next
2833 }
2834
2835 pub(crate) async fn run_core_migrations(
2836 db_no_decoders: &Database,
2837 ) -> Result<(), DbMigrationError> {
2838 let mut dbtx = db_no_decoders.begin_transaction().await;
2839 apply_migrations_core_client_dbtx(&mut dbtx.to_ref_nc(), "fedimint-client".to_string())
2840 .await?;
2841 if is_running_in_test_env() {
2842 verify_client_db_integrity_dbtx(&mut dbtx.to_ref_nc()).await;
2843 }
2844 dbtx.commit_tx_result().await?;
2845 Ok(())
2846 }
2847
2848 fn primary_modules_for_unit(
2850 &self,
2851 unit: AmountUnit,
2852 ) -> impl Iterator<Item = (ModuleInstanceId, &DynClientModule)> {
2853 self.primary_modules
2854 .iter()
2855 .flat_map(move |(_prio, candidates)| {
2856 candidates
2857 .specific
2858 .get(&unit)
2859 .into_iter()
2860 .flatten()
2861 .copied()
2862 .chain(candidates.wildcard.iter().copied())
2864 })
2865 .map(|id| (id, self.modules.get_expect(id)))
2866 }
2867
2868 pub fn primary_module_for_unit(
2872 &self,
2873 unit: AmountUnit,
2874 ) -> Option<(ModuleInstanceId, &DynClientModule)> {
2875 self.primary_modules_for_unit(unit).next()
2876 }
2877
2878 pub fn primary_module_for_btc(&self) -> (ModuleInstanceId, &DynClientModule) {
2880 self.primary_module_for_unit(AmountUnit::BITCOIN)
2881 .expect("No primary module for Bitcoin")
2882 }
2883
2884 pub fn get_primary_module_for_unit<M: ClientModule>(
2897 &self,
2898 unit: AmountUnit,
2899 ) -> Result<&M, ModuleLookupError> {
2900 let mut modules = self.primary_modules_for_unit(unit).peekable();
2901 if modules.peek().is_none() {
2902 return Err(ModuleLookupError::NoPrimaryModule { unit });
2903 }
2904 modules
2905 .find_map(|(_, module)| module.as_any().downcast_ref::<M>())
2906 .ok_or_else(|| ModuleLookupError::NoPrimaryModuleOfKind {
2907 kind: M::kind(),
2908 unit,
2909 })
2910 }
2911}
2912
2913#[apply(async_trait_maybe_send!)]
2914impl ClientContextIface for Client {
2915 fn get_module(&self, instance: ModuleInstanceId) -> &maybe_add_send_sync!(dyn IClientModule) {
2916 Client::get_module(self, instance)
2917 }
2918
2919 fn api_clone(&self) -> DynGlobalApi {
2920 Client::api_clone(self)
2921 }
2922 fn decoders(&self) -> &ModuleDecoderRegistry {
2923 Client::decoders(self)
2924 }
2925
2926 async fn finalize_and_submit_transaction(
2927 &self,
2928 operation_id: OperationId,
2929 operation_type: &str,
2930 operation_meta_gen: Box<maybe_add_send_sync!(dyn Fn(OutPointRange) -> serde_json::Value)>,
2931 tx_builder: TransactionBuilder,
2932 ) -> Result<OutPointRange, TransactionSubmitError> {
2933 Client::finalize_and_submit_transaction(
2934 self,
2935 operation_id,
2936 operation_type,
2937 &operation_meta_gen,
2939 tx_builder,
2940 )
2941 .await
2942 }
2943
2944 async fn finalize_and_submit_transaction_dbtx(
2945 &self,
2946 dbtx: &mut DatabaseTransaction<'_>,
2947 operation_id: OperationId,
2948 operation_type: &str,
2949 operation_meta_gen: Box<maybe_add_send_sync!(dyn Fn(OutPointRange) -> serde_json::Value)>,
2950 tx_builder: TransactionBuilder,
2951 ) -> Result<OutPointRange, TransactionSubmitError> {
2952 Client::finalize_and_submit_transaction_dbtx(
2953 self,
2954 dbtx,
2955 operation_id,
2956 operation_type,
2957 &operation_meta_gen,
2958 tx_builder,
2959 )
2960 .await
2961 }
2962
2963 async fn finalize_and_submit_transaction_inner(
2964 &self,
2965 dbtx: &mut DatabaseTransaction<'_>,
2966 operation_id: OperationId,
2967 tx_builder: TransactionBuilder,
2968 ) -> Result<OutPointRange, TransactionSubmitError> {
2969 Client::finalize_and_submit_transaction_inner(self, dbtx, operation_id, tx_builder).await
2970 }
2971
2972 async fn fee_quote(
2973 &self,
2974 operation_id: OperationId,
2975 request: FeeQuoteRequest,
2976 ) -> Result<FeeQuote, TransactionSubmitError> {
2977 Client::fee_quote(self, operation_id, request).await
2978 }
2979
2980 async fn get_balance_for_unit(&self, unit: AmountUnit) -> Result<Amount, ModuleLookupError> {
2981 Client::get_balance_for_unit(self, unit).await
2982 }
2983
2984 async fn transaction_updates(&self, operation_id: OperationId) -> TransactionUpdates {
2985 Client::transaction_updates(self, operation_id).await
2986 }
2987
2988 async fn await_primary_module_outputs(
2989 &self,
2990 operation_id: OperationId,
2991 outputs: Vec<OutPoint>,
2993 ) -> Result<(), TransactionSubmitError> {
2994 Client::await_primary_bitcoin_module_outputs(self, operation_id, outputs).await
2995 }
2996
2997 fn operation_log(&self) -> &dyn IOperationLog {
2998 Client::operation_log(self)
2999 }
3000
3001 async fn has_active_states(&self, operation_id: OperationId) -> bool {
3002 Client::has_active_states(self, operation_id).await
3003 }
3004
3005 async fn operation_exists(&self, operation_id: OperationId) -> bool {
3006 Client::operation_exists(self, operation_id).await
3007 }
3008
3009 async fn config(&self) -> ClientConfig {
3010 Client::config(self).await
3011 }
3012
3013 fn db(&self) -> &Database {
3014 Client::db(self)
3015 }
3016
3017 fn executor(&self) -> &(maybe_add_send_sync!(dyn IExecutor + 'static)) {
3018 Client::executor(self)
3019 }
3020
3021 async fn invite_code(&self, peer: PeerId) -> Option<InviteCode> {
3022 Client::invite_code(self, peer).await
3023 }
3024
3025 fn get_internal_payment_markers(&self) -> Result<(PublicKey, u64), bitcoin::secp256k1::Error> {
3026 Client::get_internal_payment_markers(self)
3027 }
3028
3029 async fn log_event_json(
3030 &self,
3031 dbtx: &mut DatabaseTransaction<'_, NonCommittable>,
3032 module_kind: Option<ModuleKind>,
3033 module_id: ModuleInstanceId,
3034 kind: EventKind,
3035 payload: serde_json::Value,
3036 persist: EventPersistence,
3037 ) {
3038 dbtx.ensure_global()
3039 .expect("Must be called with global dbtx");
3040 self.log_event_raw_dbtx(
3041 dbtx,
3042 kind,
3043 module_kind.map(|kind| (kind, module_id)),
3044 serde_json::to_vec(&payload).expect("Serialization can't fail"),
3045 persist,
3046 )
3047 .await;
3048 }
3049
3050 async fn read_operation_active_states<'dbtx>(
3051 &self,
3052 operation_id: OperationId,
3053 module_id: ModuleInstanceId,
3054 dbtx: &'dbtx mut DatabaseTransaction<'_>,
3055 ) -> Pin<Box<maybe_add_send!(dyn Stream<Item = (ActiveStateKey, ActiveStateMeta)> + 'dbtx)>>
3056 {
3057 Box::pin(
3058 dbtx.find_by_prefix(&ActiveModuleOperationStateKeyPrefix {
3059 operation_id,
3060 module_instance: module_id,
3061 })
3062 .await
3063 .map(move |(k, v)| (k.0, v)),
3064 )
3065 }
3066 async fn read_operation_inactive_states<'dbtx>(
3067 &self,
3068 operation_id: OperationId,
3069 module_id: ModuleInstanceId,
3070 dbtx: &'dbtx mut DatabaseTransaction<'_>,
3071 ) -> Pin<Box<maybe_add_send!(dyn Stream<Item = (InactiveStateKey, InactiveStateMeta)> + 'dbtx)>>
3072 {
3073 Box::pin(
3074 dbtx.find_by_prefix(&InactiveModuleOperationStateKeyPrefix {
3075 operation_id,
3076 module_instance: module_id,
3077 })
3078 .await
3079 .map(move |(k, v)| (k.0, v)),
3080 )
3081 }
3082}
3083
3084impl fmt::Debug for Client {
3086 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3087 write!(f, "Client")
3088 }
3089}
3090
3091pub fn client_decoders<'a>(
3092 registry: &ModuleInitRegistry<DynClientModuleInit>,
3093 module_kinds: impl Iterator<Item = (ModuleInstanceId, &'a ModuleKind)>,
3094) -> ModuleDecoderRegistry {
3095 let mut modules = BTreeMap::new();
3096 for (id, kind) in module_kinds {
3097 let Some(init) = registry.get(kind) else {
3098 debug!("Detected configuration for unsupported module id: {id}, kind: {kind}");
3099 continue;
3100 };
3101
3102 modules.insert(
3103 id,
3104 (
3105 kind.clone(),
3106 IClientModuleInit::decoder(AsRef::<dyn IClientModuleInit + 'static>::as_ref(init)),
3107 ),
3108 );
3109 }
3110 ModuleDecoderRegistry::from(modules)
3111}