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