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