1use core::fmt;
2use std::any::Any;
3use std::collections::BTreeSet;
4use std::fmt::Debug;
5use std::pin::Pin;
6use std::sync::{Arc, Weak};
7use std::{ffi, marker, ops};
8
9use anyhow::bail;
10use bitcoin::secp256k1::PublicKey;
11use fedimint_api_client::api::{DynGlobalApi, DynModuleApi};
12use fedimint_core::config::ClientConfig;
13use fedimint_core::core::{
14 Decoder, DynInput, DynOutput, IInput, IntoDynInstance, ModuleInstanceId, ModuleKind,
15 OperationId,
16};
17use fedimint_core::db::{Database, DatabaseTransaction, GlobalDBTxAccessToken, NonCommittable};
18use fedimint_core::invite_code::InviteCode;
19use fedimint_core::module::registry::{ModuleDecoderRegistry, ModuleRegistry};
20use fedimint_core::module::{AmountUnit, Amounts, CommonModuleInit, ModuleCommon, ModuleInit};
21use fedimint_core::task::{MaybeSend, MaybeSync};
22use fedimint_core::util::{BoxStream, FmtCompact as _};
23use fedimint_core::{
24 Amount, OutPoint, PeerId, apply, async_trait_maybe_send, dyn_newtype_define, maybe_add_send,
25 maybe_add_send_sync,
26};
27use fedimint_eventlog::{
28 DBTransactionEventLogExt, Event, EventKind, EventLogId, EventPersistence, PersistedLogEntry,
29};
30use fedimint_logging::LOG_CLIENT;
31use futures::{Stream, StreamExt};
32use serde::Serialize;
33use serde::de::DeserializeOwned;
34use tracing::warn;
35
36use self::init::ClientModuleInit;
37use crate::error::{
38 ModuleLookupError, OperationAlreadyExistsError, OperationLookupError, OperationNotFoundError,
39 TransactionSubmitError,
40};
41use crate::module::recovery::{DynModuleBackup, ModuleBackup};
42use crate::oplog::{IOperationLog, OperationLogEntry, UpdateStreamOrOutcome};
43use crate::sm::executor::{ActiveStateKey, IExecutor, InactiveStateKey};
44use crate::sm::{self, ActiveStateMeta, Context, DynContext, DynState, InactiveStateMeta, State};
45use crate::transaction::{
46 ClientInputBundle, ClientOutputBundle, FeeQuote, FeeQuoteRequest, TransactionBuilder,
47};
48use crate::{AddStateMachinesResult, InstancelessDynClientInputBundle, TransactionUpdates, oplog};
49
50pub mod init;
51pub mod recovery;
52
53pub type ClientModuleRegistry = ModuleRegistry<DynClientModule>;
54
55#[apply(async_trait_maybe_send!)]
64pub trait ClientContextIface: MaybeSend + MaybeSync {
65 fn get_module(&self, instance: ModuleInstanceId) -> &maybe_add_send_sync!(dyn IClientModule);
66 fn api_clone(&self) -> DynGlobalApi;
67 fn decoders(&self) -> &ModuleDecoderRegistry;
68 async fn finalize_and_submit_transaction(
69 &self,
70 operation_id: OperationId,
71 operation_type: &str,
72 operation_meta_gen: Box<maybe_add_send_sync!(dyn Fn(OutPointRange) -> serde_json::Value)>,
73 tx_builder: TransactionBuilder,
74 ) -> Result<OutPointRange, TransactionSubmitError>;
75
76 async fn finalize_and_submit_transaction_dbtx(
77 &self,
78 dbtx: &mut DatabaseTransaction<'_>,
79 operation_id: OperationId,
80 operation_type: &str,
81 operation_meta_gen: Box<maybe_add_send_sync!(dyn Fn(OutPointRange) -> serde_json::Value)>,
82 tx_builder: TransactionBuilder,
83 ) -> Result<OutPointRange, TransactionSubmitError>;
84
85 async fn finalize_and_submit_transaction_inner(
87 &self,
88 dbtx: &mut DatabaseTransaction<'_>,
89 operation_id: OperationId,
90 tx_builder: TransactionBuilder,
91 ) -> Result<OutPointRange, TransactionSubmitError>;
92
93 async fn fee_quote(
97 &self,
98 operation_id: OperationId,
99 request: FeeQuoteRequest,
100 ) -> Result<FeeQuote, TransactionSubmitError>;
101
102 async fn get_balance_for_unit(&self, unit: AmountUnit) -> Result<Amount, ModuleLookupError>;
105
106 async fn transaction_updates(&self, operation_id: OperationId) -> TransactionUpdates;
107
108 async fn await_primary_module_outputs(
109 &self,
110 operation_id: OperationId,
111 outputs: Vec<OutPoint>,
113 ) -> Result<(), TransactionSubmitError>;
114
115 fn operation_log(&self) -> &dyn IOperationLog;
116
117 async fn has_active_states(&self, operation_id: OperationId) -> bool;
118
119 async fn operation_exists(&self, operation_id: OperationId) -> bool;
120
121 async fn config(&self) -> ClientConfig;
122
123 fn db(&self) -> &Database;
124
125 fn executor(&self) -> &(maybe_add_send_sync!(dyn IExecutor + 'static));
126
127 async fn invite_code(&self, peer: PeerId) -> Option<InviteCode>;
128
129 fn get_internal_payment_markers(&self) -> Result<(PublicKey, u64), bitcoin::secp256k1::Error>;
130
131 #[allow(clippy::too_many_arguments)]
132 async fn log_event_json(
133 &self,
134 dbtx: &mut DatabaseTransaction<'_, NonCommittable>,
135 module_kind: Option<ModuleKind>,
136 module_id: ModuleInstanceId,
137 kind: EventKind,
138 payload: serde_json::Value,
139 persist: EventPersistence,
140 );
141
142 async fn read_operation_active_states<'dbtx>(
143 &self,
144 operation_id: OperationId,
145 module_id: ModuleInstanceId,
146 dbtx: &'dbtx mut DatabaseTransaction<'_>,
147 ) -> Pin<Box<maybe_add_send!(dyn Stream<Item = (ActiveStateKey, ActiveStateMeta)> + 'dbtx)>>;
148
149 async fn read_operation_inactive_states<'dbtx>(
150 &self,
151 operation_id: OperationId,
152 module_id: ModuleInstanceId,
153 dbtx: &'dbtx mut DatabaseTransaction<'_>,
154 ) -> Pin<Box<maybe_add_send!(dyn Stream<Item = (InactiveStateKey, InactiveStateMeta)> + 'dbtx)>>;
155}
156
157#[derive(Clone, Default)]
163pub struct FinalClientIface(Arc<std::sync::OnceLock<Weak<dyn ClientContextIface>>>);
164
165impl FinalClientIface {
166 pub(crate) fn get(&self) -> Arc<dyn ClientContextIface> {
172 self.0
173 .get()
174 .expect("client must be already set")
175 .upgrade()
176 .expect("client module context must not be use past client shutdown")
177 }
178
179 pub fn set(&self, client: Weak<dyn ClientContextIface>) {
180 self.0.set(client).expect("FinalLazyClient already set");
181 }
182}
183
184impl fmt::Debug for FinalClientIface {
185 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
186 f.write_str("FinalClientIface")
187 }
188}
189pub struct ClientContext<M> {
194 client: FinalClientIface,
195 module_instance_id: ModuleInstanceId,
196 global_dbtx_access_token: GlobalDBTxAccessToken,
197 module_db: Database,
198 _marker: marker::PhantomData<M>,
199}
200
201impl<M> Clone for ClientContext<M> {
202 fn clone(&self) -> Self {
203 Self {
204 client: self.client.clone(),
205 module_db: self.module_db.clone(),
206 module_instance_id: self.module_instance_id,
207 _marker: marker::PhantomData,
208 global_dbtx_access_token: self.global_dbtx_access_token,
209 }
210 }
211}
212
213pub struct ClientContextSelfRef<'s, M> {
216 client: Arc<dyn ClientContextIface>,
219 module_instance_id: ModuleInstanceId,
220 _marker: marker::PhantomData<&'s M>,
221}
222
223impl<M> ops::Deref for ClientContextSelfRef<'_, M>
224where
225 M: ClientModule,
226{
227 type Target = M;
228
229 fn deref(&self) -> &Self::Target {
230 self.client
231 .get_module(self.module_instance_id)
232 .as_any()
233 .downcast_ref::<M>()
234 .unwrap_or_else(|| panic!("Module is not of type {}", std::any::type_name::<M>()))
235 }
236}
237
238impl<M> fmt::Debug for ClientContext<M> {
239 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240 f.write_str("ClientContext")
241 }
242}
243
244impl<M> ClientContext<M>
245where
246 M: ClientModule,
247{
248 pub fn new(
249 client: FinalClientIface,
250 module_instance_id: ModuleInstanceId,
251 global_dbtx_access_token: GlobalDBTxAccessToken,
252 module_db: Database,
253 ) -> Self {
254 Self {
255 client,
256 module_instance_id,
257 global_dbtx_access_token,
258 module_db,
259 _marker: marker::PhantomData,
260 }
261 }
262
263 #[allow(clippy::needless_lifetimes)] pub fn self_ref(&self) -> ClientContextSelfRef<'_, M> {
275 ClientContextSelfRef {
276 client: self.client.get(),
277 module_instance_id: self.module_instance_id,
278 _marker: marker::PhantomData,
279 }
280 }
281
282 pub fn global_api(&self) -> DynGlobalApi {
284 self.client.get().api_clone()
285 }
286
287 pub fn module_api(&self) -> DynModuleApi {
289 self.global_api().with_module(self.module_instance_id)
290 }
291
292 pub fn decoders(&self) -> ModuleDecoderRegistry {
294 Clone::clone(self.client.get().decoders())
295 }
296
297 pub fn input_from_dyn<'i>(
298 &self,
299 input: &'i DynInput,
300 ) -> Option<&'i <M::Common as ModuleCommon>::Input> {
301 (input.module_instance_id() == self.module_instance_id).then(|| {
302 input
303 .as_any()
304 .downcast_ref::<<M::Common as ModuleCommon>::Input>()
305 .unwrap_or_else(|| {
306 panic!("instance_id {} just checked", input.module_instance_id())
307 })
308 })
309 }
310
311 pub fn output_from_dyn<'o>(
312 &self,
313 output: &'o DynOutput,
314 ) -> Option<&'o <M::Common as ModuleCommon>::Output> {
315 (output.module_instance_id() == self.module_instance_id).then(|| {
316 output
317 .as_any()
318 .downcast_ref::<<M::Common as ModuleCommon>::Output>()
319 .unwrap_or_else(|| {
320 panic!("instance_id {} just checked", output.module_instance_id())
321 })
322 })
323 }
324
325 pub fn map_dyn<'s, 'i, 'o, I>(
326 &'s self,
327 typed: impl IntoIterator<Item = I> + 'i,
328 ) -> impl Iterator<Item = <I as IntoDynInstance>::DynType> + 'o
329 where
330 I: IntoDynInstance,
331 'i: 'o,
332 's: 'o,
333 {
334 typed.into_iter().map(|i| self.make_dyn(i))
335 }
336
337 pub fn make_dyn_output(&self, output: <M::Common as ModuleCommon>::Output) -> DynOutput {
339 self.make_dyn(output)
340 }
341
342 pub fn make_dyn_input(&self, input: <M::Common as ModuleCommon>::Input) -> DynInput {
344 self.make_dyn(input)
345 }
346
347 pub fn make_dyn<I>(&self, typed: I) -> <I as IntoDynInstance>::DynType
349 where
350 I: IntoDynInstance,
351 {
352 typed.into_dyn(self.module_instance_id)
353 }
354
355 pub fn make_client_outputs<O, S>(&self, output: ClientOutputBundle<O, S>) -> ClientOutputBundle
357 where
358 O: IntoDynInstance<DynType = DynOutput> + 'static,
359 S: IntoDynInstance<DynType = DynState> + 'static,
360 {
361 self.make_dyn(output)
362 }
363
364 pub fn make_client_inputs<I, S>(&self, inputs: ClientInputBundle<I, S>) -> ClientInputBundle
366 where
367 I: IntoDynInstance<DynType = DynInput> + 'static,
368 S: IntoDynInstance<DynType = DynState> + 'static,
369 {
370 self.make_dyn(inputs)
371 }
372
373 pub fn make_dyn_state<S>(&self, sm: S) -> DynState
374 where
375 S: sm::IState + 'static,
376 {
377 DynState::from_typed(self.module_instance_id, sm)
378 }
379
380 pub async fn finalize_and_submit_transaction<F, Meta>(
381 &self,
382 operation_id: OperationId,
383 operation_type: &str,
384 operation_meta_gen: F,
385 tx_builder: TransactionBuilder,
386 ) -> Result<OutPointRange, TransactionSubmitError>
387 where
388 F: Fn(OutPointRange) -> Meta + Clone + MaybeSend + MaybeSync + 'static,
389 Meta: serde::Serialize + MaybeSend,
390 {
391 self.client
392 .get()
393 .finalize_and_submit_transaction(
394 operation_id,
395 operation_type,
396 Box::new(move |out_point_range| {
397 serde_json::to_value(operation_meta_gen(out_point_range)).expect("Can't fail")
398 }),
399 tx_builder,
400 )
401 .await
402 }
403
404 pub async fn finalize_and_submit_transaction_dbtx<F, Meta>(
405 &self,
406 dbtx: &mut DatabaseTransaction<'_>,
407 operation_id: OperationId,
408 operation_type: &str,
409 operation_meta_gen: F,
410 tx_builder: TransactionBuilder,
411 ) -> Result<OutPointRange, TransactionSubmitError>
412 where
413 F: Fn(OutPointRange) -> Meta + MaybeSend + MaybeSync + 'static,
414 Meta: serde::Serialize + MaybeSend,
415 {
416 self.client
417 .get()
418 .finalize_and_submit_transaction_dbtx(
419 &mut dbtx.global_dbtx(self.global_dbtx_access_token),
420 operation_id,
421 operation_type,
422 Box::new(move |out_point_range| {
423 serde_json::to_value(operation_meta_gen(out_point_range)).expect("Can't fail")
424 }),
425 tx_builder,
426 )
427 .await
428 }
429
430 pub async fn fee_quote(
439 &self,
440 operation_id: OperationId,
441 request: FeeQuoteRequest,
442 ) -> Result<FeeQuote, TransactionSubmitError> {
443 self.client.get().fee_quote(operation_id, request).await
444 }
445
446 pub async fn get_balance_for_btc(&self) -> Result<Amount, ModuleLookupError> {
449 self.client
450 .get()
451 .get_balance_for_unit(AmountUnit::BITCOIN)
452 .await
453 }
454
455 pub async fn transaction_updates(&self, operation_id: OperationId) -> TransactionUpdates {
456 self.client.get().transaction_updates(operation_id).await
457 }
458
459 pub async fn await_primary_module_outputs(
460 &self,
461 operation_id: OperationId,
462 outputs: Vec<OutPoint>,
464 ) -> Result<(), TransactionSubmitError> {
465 self.client
466 .get()
467 .await_primary_module_outputs(operation_id, outputs)
468 .await
469 }
470
471 pub async fn get_operation(
473 &self,
474 operation_id: OperationId,
475 ) -> Result<oplog::OperationLogEntry, OperationLookupError> {
476 let operation = self
477 .client
478 .get()
479 .operation_log()
480 .get_operation(operation_id)
481 .await
482 .ok_or(OperationNotFoundError { operation_id })?;
483
484 if operation.operation_module_kind() != M::kind().as_str() {
485 return Err(OperationLookupError::WrongModuleKind {
486 operation_id,
487 expected: M::kind(),
488 found: operation.operation_module_kind().to_owned(),
489 });
490 }
491
492 Ok(operation)
493 }
494
495 fn global_db(&self) -> fedimint_core::db::Database {
499 let db = Clone::clone(self.client.get().db());
500
501 db.ensure_global()
502 .expect("global_db must always return a global db");
503
504 db
505 }
506
507 pub fn module_db(&self) -> &Database {
508 self.module_db
509 .ensure_isolated()
510 .expect("module_db must always return isolated db");
511 &self.module_db
512 }
513
514 pub async fn get_event_log(
517 &self,
518 pos: Option<EventLogId>,
519 limit: u64,
520 ) -> Vec<PersistedLogEntry> {
521 self.global_db()
522 .begin_transaction_nc()
523 .await
524 .get_event_log(pos, limit)
525 .await
526 }
527
528 pub async fn has_active_states(&self, op_id: OperationId) -> bool {
529 self.client.get().has_active_states(op_id).await
530 }
531
532 pub async fn operation_exists(&self, op_id: OperationId) -> bool {
533 self.client.get().operation_exists(op_id).await
534 }
535
536 pub async fn get_own_active_states(&self) -> Vec<(M::States, ActiveStateMeta)> {
537 self.client
538 .get()
539 .executor()
540 .get_active_states()
541 .await
542 .into_iter()
543 .filter(|s| s.0.module_instance_id() == self.module_instance_id)
544 .map(|s| {
545 (
546 Clone::clone(
547 s.0.as_any()
548 .downcast_ref::<M::States>()
549 .expect("incorrect output type passed to module plugin"),
550 ),
551 s.1,
552 )
553 })
554 .collect()
555 }
556
557 pub async fn get_own_operation_active_states(
559 &self,
560 operation_id: OperationId,
561 ) -> Vec<(M::States, ActiveStateMeta)> {
562 let db = self.global_db();
563 let mut dbtx = db.begin_transaction_nc().await;
564
565 self.client
566 .get()
567 .read_operation_active_states(operation_id, self.module_instance_id, &mut dbtx)
568 .await
569 .map(|(key, meta)| {
570 (
571 Clone::clone(
572 key.state
573 .as_any()
574 .downcast_ref::<M::States>()
575 .expect("incorrect output type passed to module plugin"),
576 ),
577 meta,
578 )
579 })
580 .collect()
581 .await
582 }
583
584 pub async fn get_own_operation_inactive_states(
587 &self,
588 operation_id: OperationId,
589 ) -> Vec<(M::States, InactiveStateMeta)> {
590 let db = self.global_db();
591 let mut dbtx = db.begin_transaction_nc().await;
592
593 self.client
594 .get()
595 .read_operation_inactive_states(operation_id, self.module_instance_id, &mut dbtx)
596 .await
597 .map(|(key, meta)| {
598 (
599 Clone::clone(
600 key.state
601 .as_any()
602 .downcast_ref::<M::States>()
603 .expect("incorrect output type passed to module plugin"),
604 ),
605 meta,
606 )
607 })
608 .collect()
609 .await
610 }
611
612 pub async fn get_config(&self) -> ClientConfig {
613 self.client.get().config().await
614 }
615
616 pub async fn get_invite_code(&self) -> InviteCode {
619 let cfg = self.get_config().await.global;
620 self.client
621 .get()
622 .invite_code(
623 *cfg.api_endpoints
624 .keys()
625 .next()
626 .expect("A federation always has at least one guardian"),
627 )
628 .await
629 .expect("The guardian we requested an invite code for exists")
630 }
631
632 pub fn get_internal_payment_markers(
633 &self,
634 ) -> Result<(PublicKey, u64), bitcoin::secp256k1::Error> {
635 self.client.get().get_internal_payment_markers()
636 }
637
638 pub async fn manual_operation_start(
641 &self,
642 operation_id: OperationId,
643 op_type: &str,
644 operation_meta: impl serde::Serialize + Debug,
645 sms: Vec<DynState>,
646 ) -> Result<(), TransactionSubmitError> {
647 let db = self.module_db();
648 let mut dbtx = db.begin_transaction().await;
649 {
650 let dbtx = &mut dbtx.global_dbtx(self.global_dbtx_access_token);
651
652 self.manual_operation_start_inner(
653 &mut dbtx.to_ref_nc(),
654 operation_id,
655 op_type,
656 operation_meta,
657 sms,
658 )
659 .await?;
660 }
661
662 dbtx.commit_tx_result().await?;
663
664 Ok(())
665 }
666
667 pub async fn manual_operation_start_dbtx(
668 &self,
669 dbtx: &mut DatabaseTransaction<'_>,
670 operation_id: OperationId,
671 op_type: &str,
672 operation_meta: impl serde::Serialize + Debug,
673 sms: Vec<DynState>,
674 ) -> Result<(), OperationAlreadyExistsError> {
675 self.manual_operation_start_inner(
676 &mut dbtx.global_dbtx(self.global_dbtx_access_token),
677 operation_id,
678 op_type,
679 operation_meta,
680 sms,
681 )
682 .await
683 }
684
685 async fn manual_operation_start_inner(
688 &self,
689 dbtx: &mut DatabaseTransaction<'_>,
690 operation_id: OperationId,
691 op_type: &str,
692 operation_meta: impl serde::Serialize + Debug,
693 sms: Vec<DynState>,
694 ) -> Result<(), OperationAlreadyExistsError> {
695 dbtx.ensure_global()
696 .expect("Must deal with global dbtx here");
697
698 if self
699 .client
700 .get()
701 .operation_log()
702 .get_operation_dbtx(&mut dbtx.to_ref_nc(), operation_id)
703 .await
704 .is_some()
705 {
706 return Err(OperationAlreadyExistsError { operation_id });
707 }
708
709 self.client
710 .get()
711 .operation_log()
712 .add_operation_log_entry_dbtx(
713 &mut dbtx.to_ref_nc(),
714 operation_id,
715 op_type,
716 serde_json::to_value(operation_meta).expect("Can't fail"),
717 )
718 .await;
719
720 self.client
721 .get()
722 .executor()
723 .add_state_machines_dbtx(&mut dbtx.to_ref_nc(), sms)
724 .await
725 .expect("State machine is valid");
726
727 Ok(())
728 }
729
730 pub fn outcome_or_updates<U, S>(
744 &self,
745 operation: &OperationLogEntry,
746 operation_id: OperationId,
747 is_terminal: impl Fn(&U) -> bool + MaybeSend + MaybeSync + 'static,
748 stream_gen: impl FnOnce() -> S + MaybeSend + 'static,
749 ) -> UpdateStreamOrOutcome<U>
750 where
751 U: Clone + Serialize + DeserializeOwned + Debug + MaybeSend + MaybeSync + 'static,
752 S: Stream<Item = U> + MaybeSend + 'static,
753 {
754 use futures::StreamExt;
755 match operation.try_outcome::<U>() {
756 Ok(Some(outcome)) if is_terminal(&outcome) => {
757 return UpdateStreamOrOutcome::Outcome(outcome);
758 }
759 Ok(Some(_non_terminal)) => {
760 warn!(
761 target: LOG_CLIENT,
762 "Cached operation outcome is not a terminal update (cached by a previous \
763 version); rebuilding it from the update stream"
764 );
765 }
766 Ok(None) => {}
767 Err(err) => {
768 warn!(
769 target: LOG_CLIENT,
770 err = %err.fmt_compact(),
771 "Cached operation outcome failed to deserialize; rebuilding it from the update stream"
772 );
773 }
774 }
775 let stream = self
776 .client
777 .get()
778 .operation_log()
779 .caching_operation_update_stream(
780 operation_id,
781 Box::new(move || {
782 let stream_gen = stream_gen();
783 Box::pin(
784 stream_gen.map(move |item| serde_json::to_value(item).expect("Can't fail")),
785 )
786 }),
787 Box::new(move |update| {
788 serde_json::from_value::<U>(update.clone())
793 .map(|update| is_terminal(&update))
794 .unwrap_or(false)
795 }),
796 );
797 UpdateStreamOrOutcome::UpdateStream(Box::pin(
798 stream.map(|u| serde_json::from_value::<U>(u).expect("Can't fail")),
799 ))
800 }
801
802 pub async fn claim_inputs<I, S>(
803 &self,
804 dbtx: &mut DatabaseTransaction<'_>,
805 inputs: ClientInputBundle<I, S>,
806 operation_id: OperationId,
807 ) -> Result<OutPointRange, TransactionSubmitError>
808 where
809 I: IInput + MaybeSend + MaybeSync + 'static,
810 S: sm::IState + MaybeSend + MaybeSync + 'static,
811 {
812 self.claim_inputs_dyn(dbtx, inputs.into_instanceless(), operation_id)
813 .await
814 }
815
816 async fn claim_inputs_dyn(
817 &self,
818 dbtx: &mut DatabaseTransaction<'_>,
819 inputs: InstancelessDynClientInputBundle,
820 operation_id: OperationId,
821 ) -> Result<OutPointRange, TransactionSubmitError> {
822 let tx_builder =
823 TransactionBuilder::new().with_inputs(inputs.into_dyn(self.module_instance_id));
824
825 self.client
826 .get()
827 .finalize_and_submit_transaction_inner(
828 &mut dbtx.global_dbtx(self.global_dbtx_access_token),
829 operation_id,
830 tx_builder,
831 )
832 .await
833 }
834
835 pub async fn add_state_machines_dbtx(
836 &self,
837 dbtx: &mut DatabaseTransaction<'_>,
838 states: Vec<DynState>,
839 ) -> AddStateMachinesResult {
840 self.client
841 .get()
842 .executor()
843 .add_state_machines_dbtx(&mut dbtx.global_dbtx(self.global_dbtx_access_token), states)
844 .await
845 }
846
847 pub async fn get_operation_dbtx(
853 &self,
854 dbtx: &mut DatabaseTransaction<'_>,
855 operation_id: OperationId,
856 ) -> Option<oplog::OperationLogEntry> {
857 self.client
858 .get()
859 .operation_log()
860 .get_operation_dbtx(
861 &mut dbtx.global_dbtx(self.global_dbtx_access_token),
862 operation_id,
863 )
864 .await
865 }
866
867 pub async fn add_operation_log_entry_dbtx(
868 &self,
869 dbtx: &mut DatabaseTransaction<'_>,
870 operation_id: OperationId,
871 operation_type: &str,
872 operation_meta: impl serde::Serialize,
873 ) {
874 self.client
875 .get()
876 .operation_log()
877 .add_operation_log_entry_dbtx(
878 &mut dbtx.global_dbtx(self.global_dbtx_access_token),
879 operation_id,
880 operation_type,
881 serde_json::to_value(operation_meta).expect("Can't fail"),
882 )
883 .await;
884 }
885
886 pub async fn log_event<E, Cap>(&self, dbtx: &mut DatabaseTransaction<'_, Cap>, event: E)
887 where
888 E: Event + Send,
889 Cap: Send,
890 {
891 if <E as Event>::MODULE != Some(<M as ClientModule>::kind()) {
892 warn!(
893 target: LOG_CLIENT,
894 module_kind = %<M as ClientModule>::kind(),
895 event_module = ?<E as Event>::MODULE,
896 "Client module logging events of different module than its own. This might become an error in the future."
897 );
898 }
899 self.client
900 .get()
901 .log_event_json(
902 &mut dbtx.global_dbtx(self.global_dbtx_access_token).to_ref_nc(),
903 <E as Event>::MODULE,
904 self.module_instance_id,
905 <E as Event>::KIND,
906 serde_json::to_value(event).expect("Can't fail"),
907 <E as Event>::PERSISTENCE,
908 )
909 .await;
910 }
911}
912
913#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
915pub struct PrimaryModulePriority(u64);
916
917impl PrimaryModulePriority {
918 pub const HIGH: Self = Self(100);
919 pub const LOW: Self = Self(10000);
920
921 pub fn custom(prio: u64) -> Self {
922 Self(prio)
923 }
924}
925pub enum PrimaryModuleSupport {
927 Any { priority: PrimaryModulePriority },
929 Selected {
931 priority: PrimaryModulePriority,
932 units: BTreeSet<AmountUnit>,
933 },
934 None,
936}
937
938impl PrimaryModuleSupport {
939 pub fn selected<const N: usize>(
940 priority: PrimaryModulePriority,
941 units: [AmountUnit; N],
942 ) -> Self {
943 Self::Selected {
944 priority,
945 units: BTreeSet::from(units),
946 }
947 }
948}
949
950#[apply(async_trait_maybe_send!)]
952pub trait ClientModule: Debug + MaybeSend + MaybeSync + 'static {
953 type Init: ClientModuleInit;
954
955 type Common: ModuleCommon;
957
958 type Backup: ModuleBackup;
961
962 type ModuleStateMachineContext: Context;
965
966 type States: State<ModuleContext = Self::ModuleStateMachineContext>
968 + IntoDynInstance<DynType = DynState>;
969
970 fn decoder() -> Decoder {
971 let mut decoder_builder = Self::Common::decoder_builder();
972 decoder_builder.with_decodable_type::<Self::States>();
973 decoder_builder.with_decodable_type::<Self::Backup>();
974 decoder_builder.build()
975 }
976
977 fn kind() -> ModuleKind {
978 <<<Self as ClientModule>::Init as ModuleInit>::Common as CommonModuleInit>::KIND
979 }
980
981 fn context(&self) -> Self::ModuleStateMachineContext;
982
983 async fn start(&self) {}
989
990 async fn handle_cli_command(
991 &self,
992 _args: &[ffi::OsString],
993 ) -> anyhow::Result<serde_json::Value> {
994 Err(anyhow::format_err!(
995 "This module does not implement cli commands"
996 ))
997 }
998
999 async fn handle_rpc(
1000 &self,
1001 _method: String,
1002 _request: serde_json::Value,
1003 ) -> BoxStream<'_, anyhow::Result<serde_json::Value>> {
1004 Box::pin(futures::stream::once(std::future::ready(Err(
1005 anyhow::format_err!("This module does not implement rpc"),
1006 ))))
1007 }
1008
1009 fn input_fee(
1018 &self,
1019 amount: &Amounts,
1020 input: &<Self::Common as ModuleCommon>::Input,
1021 ) -> Option<Amounts>;
1022
1023 fn output_fee(
1032 &self,
1033 amount: &Amounts,
1034 output: &<Self::Common as ModuleCommon>::Output,
1035 ) -> Option<Amounts>;
1036
1037 fn supports_backup(&self) -> bool {
1038 false
1039 }
1040
1041 async fn backup(&self) -> anyhow::Result<Self::Backup> {
1042 anyhow::bail!("Backup not supported");
1043 }
1044
1045 fn supports_being_primary(&self) -> PrimaryModuleSupport {
1054 PrimaryModuleSupport::None
1055 }
1056
1057 async fn create_final_inputs_and_outputs(
1075 &self,
1076 _dbtx: &mut DatabaseTransaction<'_>,
1077 _operation_id: OperationId,
1078 _unit: AmountUnit,
1079 _input_amount: Amount,
1080 _output_amount: Amount,
1081 ) -> anyhow::Result<(
1082 ClientInputBundle<<Self::Common as ModuleCommon>::Input, Self::States>,
1083 ClientOutputBundle<<Self::Common as ModuleCommon>::Output, Self::States>,
1084 )> {
1085 unimplemented!()
1086 }
1087
1088 async fn await_primary_module_output(
1093 &self,
1094 _operation_id: OperationId,
1095 _out_point: OutPoint,
1096 ) -> anyhow::Result<()> {
1097 unimplemented!()
1098 }
1099
1100 async fn get_balance(&self, _dbtx: &mut DatabaseTransaction<'_>, _unit: AmountUnit) -> Amount {
1103 unimplemented!()
1104 }
1105
1106 async fn get_balances(&self, _dbtx: &mut DatabaseTransaction<'_>) -> Amounts {
1109 unimplemented!()
1110 }
1111
1112 async fn subscribe_balance_changes(&self) -> BoxStream<'static, ()> {
1115 unimplemented!()
1116 }
1117
1118 async fn leave(&self, _dbtx: &mut DatabaseTransaction<'_>) -> anyhow::Result<()> {
1174 bail!("Unable to determine if safe to leave the federation: Not implemented")
1175 }
1176}
1177
1178#[apply(async_trait_maybe_send!)]
1180pub trait IClientModule: Debug {
1181 fn as_any(&self) -> &(maybe_add_send_sync!(dyn std::any::Any));
1182
1183 fn as_any_arc(self: Arc<Self>) -> Arc<maybe_add_send_sync!(dyn std::any::Any + 'static)>;
1184
1185 fn decoder(&self) -> Decoder;
1186
1187 fn context(&self, instance: ModuleInstanceId) -> DynContext;
1188
1189 async fn start(&self);
1190
1191 async fn handle_cli_command(&self, args: &[ffi::OsString])
1192 -> anyhow::Result<serde_json::Value>;
1193
1194 async fn handle_rpc(
1195 &self,
1196 method: String,
1197 request: serde_json::Value,
1198 ) -> BoxStream<'_, anyhow::Result<serde_json::Value>>;
1199
1200 fn input_fee(&self, amount: &Amounts, input: &DynInput) -> Option<Amounts>;
1201
1202 fn output_fee(&self, amount: &Amounts, output: &DynOutput) -> Option<Amounts>;
1203
1204 fn supports_backup(&self) -> bool;
1205
1206 async fn backup(&self, module_instance_id: ModuleInstanceId)
1207 -> anyhow::Result<DynModuleBackup>;
1208
1209 fn supports_being_primary(&self) -> PrimaryModuleSupport;
1210
1211 async fn create_final_inputs_and_outputs(
1212 &self,
1213 module_instance: ModuleInstanceId,
1214 dbtx: &mut DatabaseTransaction<'_>,
1215 operation_id: OperationId,
1216 unit: AmountUnit,
1217 input_amount: Amount,
1218 output_amount: Amount,
1219 ) -> anyhow::Result<(ClientInputBundle, ClientOutputBundle)>;
1220
1221 async fn await_primary_module_output(
1222 &self,
1223 operation_id: OperationId,
1224 out_point: OutPoint,
1225 ) -> anyhow::Result<()>;
1226
1227 async fn get_balance(
1228 &self,
1229 module_instance: ModuleInstanceId,
1230 dbtx: &mut DatabaseTransaction<'_>,
1231 unit: AmountUnit,
1232 ) -> Amount;
1233
1234 async fn subscribe_balance_changes(&self) -> BoxStream<'static, ()>;
1235}
1236
1237#[apply(async_trait_maybe_send!)]
1238impl<T> IClientModule for T
1239where
1240 T: ClientModule,
1241{
1242 fn as_any(&self) -> &(maybe_add_send_sync!(dyn Any)) {
1243 self
1244 }
1245
1246 fn as_any_arc(self: Arc<Self>) -> Arc<maybe_add_send_sync!(dyn Any + 'static)> {
1247 self
1248 }
1249
1250 fn decoder(&self) -> Decoder {
1251 T::decoder()
1252 }
1253
1254 fn context(&self, instance: ModuleInstanceId) -> DynContext {
1255 DynContext::from_typed(instance, <T as ClientModule>::context(self))
1256 }
1257
1258 async fn start(&self) {
1259 <T as ClientModule>::start(self).await;
1260 }
1261
1262 async fn handle_cli_command(
1263 &self,
1264 args: &[ffi::OsString],
1265 ) -> anyhow::Result<serde_json::Value> {
1266 <T as ClientModule>::handle_cli_command(self, args).await
1267 }
1268
1269 async fn handle_rpc(
1270 &self,
1271 method: String,
1272 request: serde_json::Value,
1273 ) -> BoxStream<'_, anyhow::Result<serde_json::Value>> {
1274 <T as ClientModule>::handle_rpc(self, method, request).await
1275 }
1276
1277 fn input_fee(&self, amount: &Amounts, input: &DynInput) -> Option<Amounts> {
1278 <T as ClientModule>::input_fee(
1279 self,
1280 amount,
1281 input
1282 .as_any()
1283 .downcast_ref()
1284 .expect("Dispatched to correct module"),
1285 )
1286 }
1287
1288 fn output_fee(&self, amount: &Amounts, output: &DynOutput) -> Option<Amounts> {
1289 <T as ClientModule>::output_fee(
1290 self,
1291 amount,
1292 output
1293 .as_any()
1294 .downcast_ref()
1295 .expect("Dispatched to correct module"),
1296 )
1297 }
1298
1299 fn supports_backup(&self) -> bool {
1300 <T as ClientModule>::supports_backup(self)
1301 }
1302
1303 async fn backup(
1304 &self,
1305 module_instance_id: ModuleInstanceId,
1306 ) -> anyhow::Result<DynModuleBackup> {
1307 Ok(DynModuleBackup::from_typed(
1308 module_instance_id,
1309 <T as ClientModule>::backup(self).await?,
1310 ))
1311 }
1312
1313 fn supports_being_primary(&self) -> PrimaryModuleSupport {
1314 <T as ClientModule>::supports_being_primary(self)
1315 }
1316
1317 async fn create_final_inputs_and_outputs(
1318 &self,
1319 module_instance: ModuleInstanceId,
1320 dbtx: &mut DatabaseTransaction<'_>,
1321 operation_id: OperationId,
1322 unit: AmountUnit,
1323 input_amount: Amount,
1324 output_amount: Amount,
1325 ) -> anyhow::Result<(ClientInputBundle, ClientOutputBundle)> {
1326 let (inputs, outputs) = <T as ClientModule>::create_final_inputs_and_outputs(
1327 self,
1328 &mut dbtx.to_ref_with_prefix_module_id(module_instance).0,
1329 operation_id,
1330 unit,
1331 input_amount,
1332 output_amount,
1333 )
1334 .await?;
1335
1336 let inputs = inputs.into_dyn(module_instance);
1337
1338 let outputs = outputs.into_dyn(module_instance);
1339
1340 Ok((inputs, outputs))
1341 }
1342
1343 async fn await_primary_module_output(
1344 &self,
1345 operation_id: OperationId,
1346 out_point: OutPoint,
1347 ) -> anyhow::Result<()> {
1348 <T as ClientModule>::await_primary_module_output(self, operation_id, out_point).await
1349 }
1350
1351 async fn get_balance(
1352 &self,
1353 module_instance: ModuleInstanceId,
1354 dbtx: &mut DatabaseTransaction<'_>,
1355 unit: AmountUnit,
1356 ) -> Amount {
1357 <T as ClientModule>::get_balance(
1358 self,
1359 &mut dbtx.to_ref_with_prefix_module_id(module_instance).0,
1360 unit,
1361 )
1362 .await
1363 }
1364
1365 async fn subscribe_balance_changes(&self) -> BoxStream<'static, ()> {
1366 <T as ClientModule>::subscribe_balance_changes(self).await
1367 }
1368}
1369
1370dyn_newtype_define!(
1371 #[derive(Clone)]
1372 pub DynClientModule(Arc<IClientModule>)
1373);
1374
1375impl DynClientModule {
1376 pub fn as_any_arc(&self) -> Arc<maybe_add_send_sync!(dyn Any + 'static)> {
1377 self.inner.clone().as_any_arc()
1378 }
1379}
1380
1381impl AsRef<maybe_add_send_sync!(dyn IClientModule + 'static)> for DynClientModule {
1382 fn as_ref(&self) -> &maybe_add_send_sync!(dyn IClientModule + 'static) {
1383 self.inner.as_ref()
1384 }
1385}
1386
1387pub use fedimint_core::{IdxRange, OutPointRange, OutPointRangeIter};
1389
1390pub type StateGenerator<S> = Arc<maybe_add_send_sync!(dyn Fn(OutPointRange) -> Vec<S> + 'static)>;