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::{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;
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::module::recovery::{DynModuleBackup, ModuleBackup};
38use crate::oplog::{IOperationLog, OperationLogEntry, UpdateStreamOrOutcome};
39use crate::sm::executor::{ActiveStateKey, IExecutor, InactiveStateKey};
40use crate::sm::{self, ActiveStateMeta, Context, DynContext, DynState, InactiveStateMeta, State};
41use crate::transaction::{
42 ClientInputBundle, ClientOutputBundle, FeeQuote, FeeQuoteRequest, TransactionBuilder,
43};
44use crate::{AddStateMachinesResult, InstancelessDynClientInputBundle, TransactionUpdates, oplog};
45
46pub mod init;
47pub mod recovery;
48
49pub type ClientModuleRegistry = ModuleRegistry<DynClientModule>;
50
51#[apply(async_trait_maybe_send!)]
60pub trait ClientContextIface: MaybeSend + MaybeSync {
61 fn get_module(&self, instance: ModuleInstanceId) -> &maybe_add_send_sync!(dyn IClientModule);
62 fn api_clone(&self) -> DynGlobalApi;
63 fn decoders(&self) -> &ModuleDecoderRegistry;
64 async fn finalize_and_submit_transaction(
65 &self,
66 operation_id: OperationId,
67 operation_type: &str,
68 operation_meta_gen: Box<maybe_add_send_sync!(dyn Fn(OutPointRange) -> serde_json::Value)>,
69 tx_builder: TransactionBuilder,
70 ) -> anyhow::Result<OutPointRange>;
71
72 async fn finalize_and_submit_transaction_dbtx(
73 &self,
74 dbtx: &mut DatabaseTransaction<'_>,
75 operation_id: OperationId,
76 operation_type: &str,
77 operation_meta_gen: Box<maybe_add_send_sync!(dyn Fn(OutPointRange) -> serde_json::Value)>,
78 tx_builder: TransactionBuilder,
79 ) -> anyhow::Result<OutPointRange>;
80
81 async fn finalize_and_submit_transaction_inner(
83 &self,
84 dbtx: &mut DatabaseTransaction<'_>,
85 operation_id: OperationId,
86 tx_builder: TransactionBuilder,
87 ) -> anyhow::Result<OutPointRange>;
88
89 async fn fee_quote(
93 &self,
94 operation_id: OperationId,
95 request: FeeQuoteRequest,
96 ) -> anyhow::Result<FeeQuote>;
97
98 async fn transaction_updates(&self, operation_id: OperationId) -> TransactionUpdates;
99
100 async fn await_primary_module_outputs(
101 &self,
102 operation_id: OperationId,
103 outputs: Vec<OutPoint>,
105 ) -> anyhow::Result<()>;
106
107 fn operation_log(&self) -> &dyn IOperationLog;
108
109 async fn has_active_states(&self, operation_id: OperationId) -> bool;
110
111 async fn operation_exists(&self, operation_id: OperationId) -> bool;
112
113 async fn config(&self) -> ClientConfig;
114
115 fn db(&self) -> &Database;
116
117 fn executor(&self) -> &(maybe_add_send_sync!(dyn IExecutor + 'static));
118
119 async fn invite_code(&self, peer: PeerId) -> Option<InviteCode>;
120
121 fn get_internal_payment_markers(&self) -> anyhow::Result<(PublicKey, u64)>;
122
123 #[allow(clippy::too_many_arguments)]
124 async fn log_event_json(
125 &self,
126 dbtx: &mut DatabaseTransaction<'_, NonCommittable>,
127 module_kind: Option<ModuleKind>,
128 module_id: ModuleInstanceId,
129 kind: EventKind,
130 payload: serde_json::Value,
131 persist: EventPersistence,
132 );
133
134 async fn read_operation_active_states<'dbtx>(
135 &self,
136 operation_id: OperationId,
137 module_id: ModuleInstanceId,
138 dbtx: &'dbtx mut DatabaseTransaction<'_>,
139 ) -> Pin<Box<maybe_add_send!(dyn Stream<Item = (ActiveStateKey, ActiveStateMeta)> + 'dbtx)>>;
140
141 async fn read_operation_inactive_states<'dbtx>(
142 &self,
143 operation_id: OperationId,
144 module_id: ModuleInstanceId,
145 dbtx: &'dbtx mut DatabaseTransaction<'_>,
146 ) -> Pin<Box<maybe_add_send!(dyn Stream<Item = (InactiveStateKey, InactiveStateMeta)> + 'dbtx)>>;
147}
148
149#[derive(Clone, Default)]
155pub struct FinalClientIface(Arc<std::sync::OnceLock<Weak<dyn ClientContextIface>>>);
156
157impl FinalClientIface {
158 pub(crate) fn get(&self) -> Arc<dyn ClientContextIface> {
164 self.0
165 .get()
166 .expect("client must be already set")
167 .upgrade()
168 .expect("client module context must not be use past client shutdown")
169 }
170
171 pub fn set(&self, client: Weak<dyn ClientContextIface>) {
172 self.0.set(client).expect("FinalLazyClient already set");
173 }
174}
175
176impl fmt::Debug for FinalClientIface {
177 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178 f.write_str("FinalClientIface")
179 }
180}
181pub struct ClientContext<M> {
186 client: FinalClientIface,
187 module_instance_id: ModuleInstanceId,
188 global_dbtx_access_token: GlobalDBTxAccessToken,
189 module_db: Database,
190 _marker: marker::PhantomData<M>,
191}
192
193impl<M> Clone for ClientContext<M> {
194 fn clone(&self) -> Self {
195 Self {
196 client: self.client.clone(),
197 module_db: self.module_db.clone(),
198 module_instance_id: self.module_instance_id,
199 _marker: marker::PhantomData,
200 global_dbtx_access_token: self.global_dbtx_access_token,
201 }
202 }
203}
204
205pub struct ClientContextSelfRef<'s, M> {
208 client: Arc<dyn ClientContextIface>,
211 module_instance_id: ModuleInstanceId,
212 _marker: marker::PhantomData<&'s M>,
213}
214
215impl<M> ops::Deref for ClientContextSelfRef<'_, M>
216where
217 M: ClientModule,
218{
219 type Target = M;
220
221 fn deref(&self) -> &Self::Target {
222 self.client
223 .get_module(self.module_instance_id)
224 .as_any()
225 .downcast_ref::<M>()
226 .unwrap_or_else(|| panic!("Module is not of type {}", std::any::type_name::<M>()))
227 }
228}
229
230impl<M> fmt::Debug for ClientContext<M> {
231 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
232 f.write_str("ClientContext")
233 }
234}
235
236impl<M> ClientContext<M>
237where
238 M: ClientModule,
239{
240 pub fn new(
241 client: FinalClientIface,
242 module_instance_id: ModuleInstanceId,
243 global_dbtx_access_token: GlobalDBTxAccessToken,
244 module_db: Database,
245 ) -> Self {
246 Self {
247 client,
248 module_instance_id,
249 global_dbtx_access_token,
250 module_db,
251 _marker: marker::PhantomData,
252 }
253 }
254
255 #[allow(clippy::needless_lifetimes)] pub fn self_ref(&self) -> ClientContextSelfRef<'_, M> {
267 ClientContextSelfRef {
268 client: self.client.get(),
269 module_instance_id: self.module_instance_id,
270 _marker: marker::PhantomData,
271 }
272 }
273
274 pub fn global_api(&self) -> DynGlobalApi {
276 self.client.get().api_clone()
277 }
278
279 pub fn module_api(&self) -> DynModuleApi {
281 self.global_api().with_module(self.module_instance_id)
282 }
283
284 pub fn decoders(&self) -> ModuleDecoderRegistry {
286 Clone::clone(self.client.get().decoders())
287 }
288
289 pub fn input_from_dyn<'i>(
290 &self,
291 input: &'i DynInput,
292 ) -> Option<&'i <M::Common as ModuleCommon>::Input> {
293 (input.module_instance_id() == self.module_instance_id).then(|| {
294 input
295 .as_any()
296 .downcast_ref::<<M::Common as ModuleCommon>::Input>()
297 .unwrap_or_else(|| {
298 panic!("instance_id {} just checked", input.module_instance_id())
299 })
300 })
301 }
302
303 pub fn output_from_dyn<'o>(
304 &self,
305 output: &'o DynOutput,
306 ) -> Option<&'o <M::Common as ModuleCommon>::Output> {
307 (output.module_instance_id() == self.module_instance_id).then(|| {
308 output
309 .as_any()
310 .downcast_ref::<<M::Common as ModuleCommon>::Output>()
311 .unwrap_or_else(|| {
312 panic!("instance_id {} just checked", output.module_instance_id())
313 })
314 })
315 }
316
317 pub fn map_dyn<'s, 'i, 'o, I>(
318 &'s self,
319 typed: impl IntoIterator<Item = I> + 'i,
320 ) -> impl Iterator<Item = <I as IntoDynInstance>::DynType> + 'o
321 where
322 I: IntoDynInstance,
323 'i: 'o,
324 's: 'o,
325 {
326 typed.into_iter().map(|i| self.make_dyn(i))
327 }
328
329 pub fn make_dyn_output(&self, output: <M::Common as ModuleCommon>::Output) -> DynOutput {
331 self.make_dyn(output)
332 }
333
334 pub fn make_dyn_input(&self, input: <M::Common as ModuleCommon>::Input) -> DynInput {
336 self.make_dyn(input)
337 }
338
339 pub fn make_dyn<I>(&self, typed: I) -> <I as IntoDynInstance>::DynType
341 where
342 I: IntoDynInstance,
343 {
344 typed.into_dyn(self.module_instance_id)
345 }
346
347 pub fn make_client_outputs<O, S>(&self, output: ClientOutputBundle<O, S>) -> ClientOutputBundle
349 where
350 O: IntoDynInstance<DynType = DynOutput> + 'static,
351 S: IntoDynInstance<DynType = DynState> + 'static,
352 {
353 self.make_dyn(output)
354 }
355
356 pub fn make_client_inputs<I, S>(&self, inputs: ClientInputBundle<I, S>) -> ClientInputBundle
358 where
359 I: IntoDynInstance<DynType = DynInput> + 'static,
360 S: IntoDynInstance<DynType = DynState> + 'static,
361 {
362 self.make_dyn(inputs)
363 }
364
365 pub fn make_dyn_state<S>(&self, sm: S) -> DynState
366 where
367 S: sm::IState + 'static,
368 {
369 DynState::from_typed(self.module_instance_id, sm)
370 }
371
372 pub async fn finalize_and_submit_transaction<F, Meta>(
373 &self,
374 operation_id: OperationId,
375 operation_type: &str,
376 operation_meta_gen: F,
377 tx_builder: TransactionBuilder,
378 ) -> anyhow::Result<OutPointRange>
379 where
380 F: Fn(OutPointRange) -> Meta + Clone + MaybeSend + MaybeSync + 'static,
381 Meta: serde::Serialize + MaybeSend,
382 {
383 self.client
384 .get()
385 .finalize_and_submit_transaction(
386 operation_id,
387 operation_type,
388 Box::new(move |out_point_range| {
389 serde_json::to_value(operation_meta_gen(out_point_range)).expect("Can't fail")
390 }),
391 tx_builder,
392 )
393 .await
394 }
395
396 pub async fn finalize_and_submit_transaction_dbtx<F, Meta>(
397 &self,
398 dbtx: &mut DatabaseTransaction<'_>,
399 operation_id: OperationId,
400 operation_type: &str,
401 operation_meta_gen: F,
402 tx_builder: TransactionBuilder,
403 ) -> anyhow::Result<OutPointRange>
404 where
405 F: Fn(OutPointRange) -> Meta + MaybeSend + MaybeSync + 'static,
406 Meta: serde::Serialize + MaybeSend,
407 {
408 self.client
409 .get()
410 .finalize_and_submit_transaction_dbtx(
411 &mut dbtx.global_dbtx(self.global_dbtx_access_token),
412 operation_id,
413 operation_type,
414 Box::new(move |out_point_range| {
415 serde_json::to_value(operation_meta_gen(out_point_range)).expect("Can't fail")
416 }),
417 tx_builder,
418 )
419 .await
420 }
421
422 pub async fn fee_quote(
431 &self,
432 operation_id: OperationId,
433 request: FeeQuoteRequest,
434 ) -> anyhow::Result<FeeQuote> {
435 self.client.get().fee_quote(operation_id, request).await
436 }
437
438 pub async fn transaction_updates(&self, operation_id: OperationId) -> TransactionUpdates {
439 self.client.get().transaction_updates(operation_id).await
440 }
441
442 pub async fn await_primary_module_outputs(
443 &self,
444 operation_id: OperationId,
445 outputs: Vec<OutPoint>,
447 ) -> anyhow::Result<()> {
448 self.client
449 .get()
450 .await_primary_module_outputs(operation_id, outputs)
451 .await
452 }
453
454 pub async fn get_operation(
456 &self,
457 operation_id: OperationId,
458 ) -> anyhow::Result<oplog::OperationLogEntry> {
459 let operation = self
460 .client
461 .get()
462 .operation_log()
463 .get_operation(operation_id)
464 .await
465 .ok_or(anyhow::anyhow!("Operation not found"))?;
466
467 if operation.operation_module_kind() != M::kind().as_str() {
468 bail!("Operation is not a lightning operation");
469 }
470
471 Ok(operation)
472 }
473
474 fn global_db(&self) -> fedimint_core::db::Database {
478 let db = Clone::clone(self.client.get().db());
479
480 db.ensure_global()
481 .expect("global_db must always return a global db");
482
483 db
484 }
485
486 pub fn module_db(&self) -> &Database {
487 self.module_db
488 .ensure_isolated()
489 .expect("module_db must always return isolated db");
490 &self.module_db
491 }
492
493 pub async fn get_event_log(
496 &self,
497 pos: Option<EventLogId>,
498 limit: u64,
499 ) -> Vec<PersistedLogEntry> {
500 self.global_db()
501 .begin_transaction_nc()
502 .await
503 .get_event_log(pos, limit)
504 .await
505 }
506
507 pub async fn has_active_states(&self, op_id: OperationId) -> bool {
508 self.client.get().has_active_states(op_id).await
509 }
510
511 pub async fn operation_exists(&self, op_id: OperationId) -> bool {
512 self.client.get().operation_exists(op_id).await
513 }
514
515 pub async fn get_own_active_states(&self) -> Vec<(M::States, ActiveStateMeta)> {
516 self.client
517 .get()
518 .executor()
519 .get_active_states()
520 .await
521 .into_iter()
522 .filter(|s| s.0.module_instance_id() == self.module_instance_id)
523 .map(|s| {
524 (
525 Clone::clone(
526 s.0.as_any()
527 .downcast_ref::<M::States>()
528 .expect("incorrect output type passed to module plugin"),
529 ),
530 s.1,
531 )
532 })
533 .collect()
534 }
535
536 pub async fn get_own_operation_active_states(
538 &self,
539 operation_id: OperationId,
540 ) -> Vec<(M::States, ActiveStateMeta)> {
541 let db = self.global_db();
542 let mut dbtx = db.begin_transaction_nc().await;
543
544 self.client
545 .get()
546 .read_operation_active_states(operation_id, self.module_instance_id, &mut dbtx)
547 .await
548 .map(|(key, meta)| {
549 (
550 Clone::clone(
551 key.state
552 .as_any()
553 .downcast_ref::<M::States>()
554 .expect("incorrect output type passed to module plugin"),
555 ),
556 meta,
557 )
558 })
559 .collect()
560 .await
561 }
562
563 pub async fn get_own_operation_inactive_states(
566 &self,
567 operation_id: OperationId,
568 ) -> Vec<(M::States, InactiveStateMeta)> {
569 let db = self.global_db();
570 let mut dbtx = db.begin_transaction_nc().await;
571
572 self.client
573 .get()
574 .read_operation_inactive_states(operation_id, self.module_instance_id, &mut dbtx)
575 .await
576 .map(|(key, meta)| {
577 (
578 Clone::clone(
579 key.state
580 .as_any()
581 .downcast_ref::<M::States>()
582 .expect("incorrect output type passed to module plugin"),
583 ),
584 meta,
585 )
586 })
587 .collect()
588 .await
589 }
590
591 pub async fn get_config(&self) -> ClientConfig {
592 self.client.get().config().await
593 }
594
595 pub async fn get_invite_code(&self) -> InviteCode {
598 let cfg = self.get_config().await.global;
599 self.client
600 .get()
601 .invite_code(
602 *cfg.api_endpoints
603 .keys()
604 .next()
605 .expect("A federation always has at least one guardian"),
606 )
607 .await
608 .expect("The guardian we requested an invite code for exists")
609 }
610
611 pub fn get_internal_payment_markers(&self) -> anyhow::Result<(PublicKey, u64)> {
612 self.client.get().get_internal_payment_markers()
613 }
614
615 pub async fn manual_operation_start(
618 &self,
619 operation_id: OperationId,
620 op_type: &str,
621 operation_meta: impl serde::Serialize + Debug,
622 sms: Vec<DynState>,
623 ) -> anyhow::Result<()> {
624 let db = self.module_db();
625 let mut dbtx = db.begin_transaction().await;
626 {
627 let dbtx = &mut dbtx.global_dbtx(self.global_dbtx_access_token);
628
629 self.manual_operation_start_inner(
630 &mut dbtx.to_ref_nc(),
631 operation_id,
632 op_type,
633 operation_meta,
634 sms,
635 )
636 .await?;
637 }
638
639 dbtx.commit_tx_result().await.map_err(|_| {
640 anyhow!(
641 "Operation with id {} already exists",
642 operation_id.fmt_short()
643 )
644 })?;
645
646 Ok(())
647 }
648
649 pub async fn manual_operation_start_dbtx(
650 &self,
651 dbtx: &mut DatabaseTransaction<'_>,
652 operation_id: OperationId,
653 op_type: &str,
654 operation_meta: impl serde::Serialize + Debug,
655 sms: Vec<DynState>,
656 ) -> anyhow::Result<()> {
657 self.manual_operation_start_inner(
658 &mut dbtx.global_dbtx(self.global_dbtx_access_token),
659 operation_id,
660 op_type,
661 operation_meta,
662 sms,
663 )
664 .await
665 }
666
667 async fn manual_operation_start_inner(
670 &self,
671 dbtx: &mut DatabaseTransaction<'_>,
672 operation_id: OperationId,
673 op_type: &str,
674 operation_meta: impl serde::Serialize + Debug,
675 sms: Vec<DynState>,
676 ) -> anyhow::Result<()> {
677 dbtx.ensure_global()
678 .expect("Must deal with global dbtx here");
679
680 if self
681 .client
682 .get()
683 .operation_log()
684 .get_operation_dbtx(&mut dbtx.to_ref_nc(), operation_id)
685 .await
686 .is_some()
687 {
688 bail!(
689 "Operation with id {} already exists",
690 operation_id.fmt_short()
691 );
692 }
693
694 self.client
695 .get()
696 .operation_log()
697 .add_operation_log_entry_dbtx(
698 &mut dbtx.to_ref_nc(),
699 operation_id,
700 op_type,
701 serde_json::to_value(operation_meta).expect("Can't fail"),
702 )
703 .await;
704
705 self.client
706 .get()
707 .executor()
708 .add_state_machines_dbtx(&mut dbtx.to_ref_nc(), sms)
709 .await
710 .expect("State machine is valid");
711
712 Ok(())
713 }
714
715 pub fn outcome_or_updates<U, S>(
716 &self,
717 operation: OperationLogEntry,
718 operation_id: OperationId,
719 stream_gen: impl FnOnce() -> S + 'static,
720 ) -> UpdateStreamOrOutcome<U>
721 where
722 U: Clone + Serialize + DeserializeOwned + Debug + MaybeSend + MaybeSync + 'static,
723 S: Stream<Item = U> + MaybeSend + 'static,
724 {
725 use futures::StreamExt;
726 match self.client.get().operation_log().outcome_or_updates(
727 &self.global_db(),
728 operation_id,
729 operation,
730 Box::new(move || {
731 let stream_gen = stream_gen();
732 Box::pin(
733 stream_gen.map(move |item| serde_json::to_value(item).expect("Can't fail")),
734 )
735 }),
736 ) {
737 UpdateStreamOrOutcome::UpdateStream(stream) => UpdateStreamOrOutcome::UpdateStream(
738 Box::pin(stream.map(|u| serde_json::from_value(u).expect("Can't fail"))),
739 ),
740 UpdateStreamOrOutcome::Outcome(o) => {
741 UpdateStreamOrOutcome::Outcome(serde_json::from_value(o).expect("Can't fail"))
742 }
743 }
744 }
745
746 pub async fn claim_inputs<I, S>(
747 &self,
748 dbtx: &mut DatabaseTransaction<'_>,
749 inputs: ClientInputBundle<I, S>,
750 operation_id: OperationId,
751 ) -> anyhow::Result<OutPointRange>
752 where
753 I: IInput + MaybeSend + MaybeSync + 'static,
754 S: sm::IState + MaybeSend + MaybeSync + 'static,
755 {
756 self.claim_inputs_dyn(dbtx, inputs.into_instanceless(), operation_id)
757 .await
758 }
759
760 async fn claim_inputs_dyn(
761 &self,
762 dbtx: &mut DatabaseTransaction<'_>,
763 inputs: InstancelessDynClientInputBundle,
764 operation_id: OperationId,
765 ) -> anyhow::Result<OutPointRange> {
766 let tx_builder =
767 TransactionBuilder::new().with_inputs(inputs.into_dyn(self.module_instance_id));
768
769 self.client
770 .get()
771 .finalize_and_submit_transaction_inner(
772 &mut dbtx.global_dbtx(self.global_dbtx_access_token),
773 operation_id,
774 tx_builder,
775 )
776 .await
777 }
778
779 pub async fn add_state_machines_dbtx(
780 &self,
781 dbtx: &mut DatabaseTransaction<'_>,
782 states: Vec<DynState>,
783 ) -> AddStateMachinesResult {
784 self.client
785 .get()
786 .executor()
787 .add_state_machines_dbtx(&mut dbtx.global_dbtx(self.global_dbtx_access_token), states)
788 .await
789 }
790
791 pub async fn add_operation_log_entry_dbtx(
792 &self,
793 dbtx: &mut DatabaseTransaction<'_>,
794 operation_id: OperationId,
795 operation_type: &str,
796 operation_meta: impl serde::Serialize,
797 ) {
798 self.client
799 .get()
800 .operation_log()
801 .add_operation_log_entry_dbtx(
802 &mut dbtx.global_dbtx(self.global_dbtx_access_token),
803 operation_id,
804 operation_type,
805 serde_json::to_value(operation_meta).expect("Can't fail"),
806 )
807 .await;
808 }
809
810 pub async fn log_event<E, Cap>(&self, dbtx: &mut DatabaseTransaction<'_, Cap>, event: E)
811 where
812 E: Event + Send,
813 Cap: Send,
814 {
815 if <E as Event>::MODULE != Some(<M as ClientModule>::kind()) {
816 warn!(
817 target: LOG_CLIENT,
818 module_kind = %<M as ClientModule>::kind(),
819 event_module = ?<E as Event>::MODULE,
820 "Client module logging events of different module than its own. This might become an error in the future."
821 );
822 }
823 self.client
824 .get()
825 .log_event_json(
826 &mut dbtx.global_dbtx(self.global_dbtx_access_token).to_ref_nc(),
827 <E as Event>::MODULE,
828 self.module_instance_id,
829 <E as Event>::KIND,
830 serde_json::to_value(event).expect("Can't fail"),
831 <E as Event>::PERSISTENCE,
832 )
833 .await;
834 }
835}
836
837#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
839pub struct PrimaryModulePriority(u64);
840
841impl PrimaryModulePriority {
842 pub const HIGH: Self = Self(100);
843 pub const LOW: Self = Self(10000);
844
845 pub fn custom(prio: u64) -> Self {
846 Self(prio)
847 }
848}
849pub enum PrimaryModuleSupport {
851 Any { priority: PrimaryModulePriority },
853 Selected {
855 priority: PrimaryModulePriority,
856 units: BTreeSet<AmountUnit>,
857 },
858 None,
860}
861
862impl PrimaryModuleSupport {
863 pub fn selected<const N: usize>(
864 priority: PrimaryModulePriority,
865 units: [AmountUnit; N],
866 ) -> Self {
867 Self::Selected {
868 priority,
869 units: BTreeSet::from(units),
870 }
871 }
872}
873
874#[apply(async_trait_maybe_send!)]
876pub trait ClientModule: Debug + MaybeSend + MaybeSync + 'static {
877 type Init: ClientModuleInit;
878
879 type Common: ModuleCommon;
881
882 type Backup: ModuleBackup;
885
886 type ModuleStateMachineContext: Context;
889
890 type States: State<ModuleContext = Self::ModuleStateMachineContext>
892 + IntoDynInstance<DynType = DynState>;
893
894 fn decoder() -> Decoder {
895 let mut decoder_builder = Self::Common::decoder_builder();
896 decoder_builder.with_decodable_type::<Self::States>();
897 decoder_builder.with_decodable_type::<Self::Backup>();
898 decoder_builder.build()
899 }
900
901 fn kind() -> ModuleKind {
902 <<<Self as ClientModule>::Init as ModuleInit>::Common as CommonModuleInit>::KIND
903 }
904
905 fn context(&self) -> Self::ModuleStateMachineContext;
906
907 async fn start(&self) {}
913
914 async fn handle_cli_command(
915 &self,
916 _args: &[ffi::OsString],
917 ) -> anyhow::Result<serde_json::Value> {
918 Err(anyhow::format_err!(
919 "This module does not implement cli commands"
920 ))
921 }
922
923 async fn handle_rpc(
924 &self,
925 _method: String,
926 _request: serde_json::Value,
927 ) -> BoxStream<'_, anyhow::Result<serde_json::Value>> {
928 Box::pin(futures::stream::once(std::future::ready(Err(
929 anyhow::format_err!("This module does not implement rpc"),
930 ))))
931 }
932
933 fn input_fee(
942 &self,
943 amount: &Amounts,
944 input: &<Self::Common as ModuleCommon>::Input,
945 ) -> Option<Amounts>;
946
947 fn output_fee(
956 &self,
957 amount: &Amounts,
958 output: &<Self::Common as ModuleCommon>::Output,
959 ) -> Option<Amounts>;
960
961 fn supports_backup(&self) -> bool {
962 false
963 }
964
965 async fn backup(&self) -> anyhow::Result<Self::Backup> {
966 anyhow::bail!("Backup not supported");
967 }
968
969 fn supports_being_primary(&self) -> PrimaryModuleSupport {
978 PrimaryModuleSupport::None
979 }
980
981 async fn create_final_inputs_and_outputs(
999 &self,
1000 _dbtx: &mut DatabaseTransaction<'_>,
1001 _operation_id: OperationId,
1002 _unit: AmountUnit,
1003 _input_amount: Amount,
1004 _output_amount: Amount,
1005 ) -> anyhow::Result<(
1006 ClientInputBundle<<Self::Common as ModuleCommon>::Input, Self::States>,
1007 ClientOutputBundle<<Self::Common as ModuleCommon>::Output, Self::States>,
1008 )> {
1009 unimplemented!()
1010 }
1011
1012 async fn await_primary_module_output(
1017 &self,
1018 _operation_id: OperationId,
1019 _out_point: OutPoint,
1020 ) -> anyhow::Result<()> {
1021 unimplemented!()
1022 }
1023
1024 async fn get_balance(&self, _dbtx: &mut DatabaseTransaction<'_>, _unit: AmountUnit) -> Amount {
1027 unimplemented!()
1028 }
1029
1030 async fn get_balances(&self, _dbtx: &mut DatabaseTransaction<'_>) -> Amounts {
1033 unimplemented!()
1034 }
1035
1036 async fn subscribe_balance_changes(&self) -> BoxStream<'static, ()> {
1039 unimplemented!()
1040 }
1041
1042 async fn leave(&self, _dbtx: &mut DatabaseTransaction<'_>) -> anyhow::Result<()> {
1098 bail!("Unable to determine if safe to leave the federation: Not implemented")
1099 }
1100}
1101
1102#[apply(async_trait_maybe_send!)]
1104pub trait IClientModule: Debug {
1105 fn as_any(&self) -> &(maybe_add_send_sync!(dyn std::any::Any));
1106
1107 fn decoder(&self) -> Decoder;
1108
1109 fn context(&self, instance: ModuleInstanceId) -> DynContext;
1110
1111 async fn start(&self);
1112
1113 async fn handle_cli_command(&self, args: &[ffi::OsString])
1114 -> anyhow::Result<serde_json::Value>;
1115
1116 async fn handle_rpc(
1117 &self,
1118 method: String,
1119 request: serde_json::Value,
1120 ) -> BoxStream<'_, anyhow::Result<serde_json::Value>>;
1121
1122 fn input_fee(&self, amount: &Amounts, input: &DynInput) -> Option<Amounts>;
1123
1124 fn output_fee(&self, amount: &Amounts, output: &DynOutput) -> Option<Amounts>;
1125
1126 fn supports_backup(&self) -> bool;
1127
1128 async fn backup(&self, module_instance_id: ModuleInstanceId)
1129 -> anyhow::Result<DynModuleBackup>;
1130
1131 fn supports_being_primary(&self) -> PrimaryModuleSupport;
1132
1133 async fn create_final_inputs_and_outputs(
1134 &self,
1135 module_instance: ModuleInstanceId,
1136 dbtx: &mut DatabaseTransaction<'_>,
1137 operation_id: OperationId,
1138 unit: AmountUnit,
1139 input_amount: Amount,
1140 output_amount: Amount,
1141 ) -> anyhow::Result<(ClientInputBundle, ClientOutputBundle)>;
1142
1143 async fn await_primary_module_output(
1144 &self,
1145 operation_id: OperationId,
1146 out_point: OutPoint,
1147 ) -> anyhow::Result<()>;
1148
1149 async fn get_balance(
1150 &self,
1151 module_instance: ModuleInstanceId,
1152 dbtx: &mut DatabaseTransaction<'_>,
1153 unit: AmountUnit,
1154 ) -> Amount;
1155
1156 async fn subscribe_balance_changes(&self) -> BoxStream<'static, ()>;
1157}
1158
1159#[apply(async_trait_maybe_send!)]
1160impl<T> IClientModule for T
1161where
1162 T: ClientModule,
1163{
1164 fn as_any(&self) -> &(maybe_add_send_sync!(dyn Any)) {
1165 self
1166 }
1167
1168 fn decoder(&self) -> Decoder {
1169 T::decoder()
1170 }
1171
1172 fn context(&self, instance: ModuleInstanceId) -> DynContext {
1173 DynContext::from_typed(instance, <T as ClientModule>::context(self))
1174 }
1175
1176 async fn start(&self) {
1177 <T as ClientModule>::start(self).await;
1178 }
1179
1180 async fn handle_cli_command(
1181 &self,
1182 args: &[ffi::OsString],
1183 ) -> anyhow::Result<serde_json::Value> {
1184 <T as ClientModule>::handle_cli_command(self, args).await
1185 }
1186
1187 async fn handle_rpc(
1188 &self,
1189 method: String,
1190 request: serde_json::Value,
1191 ) -> BoxStream<'_, anyhow::Result<serde_json::Value>> {
1192 <T as ClientModule>::handle_rpc(self, method, request).await
1193 }
1194
1195 fn input_fee(&self, amount: &Amounts, input: &DynInput) -> Option<Amounts> {
1196 <T as ClientModule>::input_fee(
1197 self,
1198 amount,
1199 input
1200 .as_any()
1201 .downcast_ref()
1202 .expect("Dispatched to correct module"),
1203 )
1204 }
1205
1206 fn output_fee(&self, amount: &Amounts, output: &DynOutput) -> Option<Amounts> {
1207 <T as ClientModule>::output_fee(
1208 self,
1209 amount,
1210 output
1211 .as_any()
1212 .downcast_ref()
1213 .expect("Dispatched to correct module"),
1214 )
1215 }
1216
1217 fn supports_backup(&self) -> bool {
1218 <T as ClientModule>::supports_backup(self)
1219 }
1220
1221 async fn backup(
1222 &self,
1223 module_instance_id: ModuleInstanceId,
1224 ) -> anyhow::Result<DynModuleBackup> {
1225 Ok(DynModuleBackup::from_typed(
1226 module_instance_id,
1227 <T as ClientModule>::backup(self).await?,
1228 ))
1229 }
1230
1231 fn supports_being_primary(&self) -> PrimaryModuleSupport {
1232 <T as ClientModule>::supports_being_primary(self)
1233 }
1234
1235 async fn create_final_inputs_and_outputs(
1236 &self,
1237 module_instance: ModuleInstanceId,
1238 dbtx: &mut DatabaseTransaction<'_>,
1239 operation_id: OperationId,
1240 unit: AmountUnit,
1241 input_amount: Amount,
1242 output_amount: Amount,
1243 ) -> anyhow::Result<(ClientInputBundle, ClientOutputBundle)> {
1244 let (inputs, outputs) = <T as ClientModule>::create_final_inputs_and_outputs(
1245 self,
1246 &mut dbtx.to_ref_with_prefix_module_id(module_instance).0,
1247 operation_id,
1248 unit,
1249 input_amount,
1250 output_amount,
1251 )
1252 .await?;
1253
1254 let inputs = inputs.into_dyn(module_instance);
1255
1256 let outputs = outputs.into_dyn(module_instance);
1257
1258 Ok((inputs, outputs))
1259 }
1260
1261 async fn await_primary_module_output(
1262 &self,
1263 operation_id: OperationId,
1264 out_point: OutPoint,
1265 ) -> anyhow::Result<()> {
1266 <T as ClientModule>::await_primary_module_output(self, operation_id, out_point).await
1267 }
1268
1269 async fn get_balance(
1270 &self,
1271 module_instance: ModuleInstanceId,
1272 dbtx: &mut DatabaseTransaction<'_>,
1273 unit: AmountUnit,
1274 ) -> Amount {
1275 <T as ClientModule>::get_balance(
1276 self,
1277 &mut dbtx.to_ref_with_prefix_module_id(module_instance).0,
1278 unit,
1279 )
1280 .await
1281 }
1282
1283 async fn subscribe_balance_changes(&self) -> BoxStream<'static, ()> {
1284 <T as ClientModule>::subscribe_balance_changes(self).await
1285 }
1286}
1287
1288dyn_newtype_define!(
1289 #[derive(Clone)]
1290 pub DynClientModule(Arc<IClientModule>)
1291);
1292
1293impl AsRef<maybe_add_send_sync!(dyn IClientModule + 'static)> for DynClientModule {
1294 fn as_ref(&self) -> &maybe_add_send_sync!(dyn IClientModule + 'static) {
1295 self.inner.as_ref()
1296 }
1297}
1298
1299pub use fedimint_core::{IdxRange, OutPointRange, OutPointRangeIter};
1301
1302pub type StateGenerator<S> = Arc<maybe_add_send_sync!(dyn Fn(OutPointRange) -> Vec<S> + 'static)>;