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 await_no_active_states(&self, operation_id: OperationId);
121
122 async fn operation_exists(&self, operation_id: OperationId) -> bool;
123
124 async fn config(&self) -> ClientConfig;
125
126 fn db(&self) -> &Database;
127
128 fn executor(&self) -> &(maybe_add_send_sync!(dyn IExecutor + 'static));
129
130 async fn invite_code(&self, peer: PeerId) -> Option<InviteCode>;
131
132 fn get_internal_payment_markers(&self) -> Result<(PublicKey, u64), bitcoin::secp256k1::Error>;
133
134 #[allow(clippy::too_many_arguments)]
135 async fn log_event_json(
136 &self,
137 dbtx: &mut DatabaseTransaction<'_, NonCommittable>,
138 module_kind: Option<ModuleKind>,
139 module_id: ModuleInstanceId,
140 kind: EventKind,
141 payload: serde_json::Value,
142 persist: EventPersistence,
143 );
144
145 async fn read_operation_active_states<'dbtx>(
146 &self,
147 operation_id: OperationId,
148 module_id: ModuleInstanceId,
149 dbtx: &'dbtx mut DatabaseTransaction<'_>,
150 ) -> Pin<Box<maybe_add_send!(dyn Stream<Item = (ActiveStateKey, ActiveStateMeta)> + 'dbtx)>>;
151
152 async fn read_operation_inactive_states<'dbtx>(
153 &self,
154 operation_id: OperationId,
155 module_id: ModuleInstanceId,
156 dbtx: &'dbtx mut DatabaseTransaction<'_>,
157 ) -> Pin<Box<maybe_add_send!(dyn Stream<Item = (InactiveStateKey, InactiveStateMeta)> + 'dbtx)>>;
158}
159
160#[derive(Clone, Default)]
166pub struct FinalClientIface(Arc<std::sync::OnceLock<Weak<dyn ClientContextIface>>>);
167
168impl FinalClientIface {
169 pub(crate) fn get(&self) -> Arc<dyn ClientContextIface> {
175 self.0
176 .get()
177 .expect("client must be already set")
178 .upgrade()
179 .expect("client module context must not be use past client shutdown")
180 }
181
182 pub fn set(&self, client: Weak<dyn ClientContextIface>) {
183 self.0.set(client).expect("FinalLazyClient already set");
184 }
185}
186
187impl fmt::Debug for FinalClientIface {
188 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
189 f.write_str("FinalClientIface")
190 }
191}
192pub struct ClientContext<M> {
197 client: FinalClientIface,
198 module_instance_id: ModuleInstanceId,
199 global_dbtx_access_token: GlobalDBTxAccessToken,
200 module_db: Database,
201 _marker: marker::PhantomData<M>,
202}
203
204impl<M> Clone for ClientContext<M> {
205 fn clone(&self) -> Self {
206 Self {
207 client: self.client.clone(),
208 module_db: self.module_db.clone(),
209 module_instance_id: self.module_instance_id,
210 _marker: marker::PhantomData,
211 global_dbtx_access_token: self.global_dbtx_access_token,
212 }
213 }
214}
215
216pub struct ClientContextSelfRef<'s, M> {
219 client: Arc<dyn ClientContextIface>,
222 module_instance_id: ModuleInstanceId,
223 _marker: marker::PhantomData<&'s M>,
224}
225
226impl<M> ops::Deref for ClientContextSelfRef<'_, M>
227where
228 M: ClientModule,
229{
230 type Target = M;
231
232 fn deref(&self) -> &Self::Target {
233 self.client
234 .get_module(self.module_instance_id)
235 .as_any()
236 .downcast_ref::<M>()
237 .unwrap_or_else(|| panic!("Module is not of type {}", std::any::type_name::<M>()))
238 }
239}
240
241impl<M> fmt::Debug for ClientContext<M> {
242 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243 f.write_str("ClientContext")
244 }
245}
246
247impl<M> ClientContext<M>
248where
249 M: ClientModule,
250{
251 pub fn new(
252 client: FinalClientIface,
253 module_instance_id: ModuleInstanceId,
254 global_dbtx_access_token: GlobalDBTxAccessToken,
255 module_db: Database,
256 ) -> Self {
257 Self {
258 client,
259 module_instance_id,
260 global_dbtx_access_token,
261 module_db,
262 _marker: marker::PhantomData,
263 }
264 }
265
266 #[allow(clippy::needless_lifetimes)] pub fn self_ref(&self) -> ClientContextSelfRef<'_, M> {
278 ClientContextSelfRef {
279 client: self.client.get(),
280 module_instance_id: self.module_instance_id,
281 _marker: marker::PhantomData,
282 }
283 }
284
285 pub fn global_api(&self) -> DynGlobalApi {
287 self.client.get().api_clone()
288 }
289
290 pub fn module_api(&self) -> DynModuleApi {
292 self.global_api().with_module(self.module_instance_id)
293 }
294
295 pub fn decoders(&self) -> ModuleDecoderRegistry {
297 Clone::clone(self.client.get().decoders())
298 }
299
300 pub fn input_from_dyn<'i>(
301 &self,
302 input: &'i DynInput,
303 ) -> Option<&'i <M::Common as ModuleCommon>::Input> {
304 (input.module_instance_id() == self.module_instance_id).then(|| {
305 input
306 .as_any()
307 .downcast_ref::<<M::Common as ModuleCommon>::Input>()
308 .unwrap_or_else(|| {
309 panic!("instance_id {} just checked", input.module_instance_id())
310 })
311 })
312 }
313
314 pub fn output_from_dyn<'o>(
315 &self,
316 output: &'o DynOutput,
317 ) -> Option<&'o <M::Common as ModuleCommon>::Output> {
318 (output.module_instance_id() == self.module_instance_id).then(|| {
319 output
320 .as_any()
321 .downcast_ref::<<M::Common as ModuleCommon>::Output>()
322 .unwrap_or_else(|| {
323 panic!("instance_id {} just checked", output.module_instance_id())
324 })
325 })
326 }
327
328 pub fn map_dyn<'s, 'i, 'o, I>(
329 &'s self,
330 typed: impl IntoIterator<Item = I> + 'i,
331 ) -> impl Iterator<Item = <I as IntoDynInstance>::DynType> + 'o
332 where
333 I: IntoDynInstance,
334 'i: 'o,
335 's: 'o,
336 {
337 typed.into_iter().map(|i| self.make_dyn(i))
338 }
339
340 pub fn make_dyn_output(&self, output: <M::Common as ModuleCommon>::Output) -> DynOutput {
342 self.make_dyn(output)
343 }
344
345 pub fn make_dyn_input(&self, input: <M::Common as ModuleCommon>::Input) -> DynInput {
347 self.make_dyn(input)
348 }
349
350 pub fn make_dyn<I>(&self, typed: I) -> <I as IntoDynInstance>::DynType
352 where
353 I: IntoDynInstance,
354 {
355 typed.into_dyn(self.module_instance_id)
356 }
357
358 pub fn make_client_outputs<O, S>(&self, output: ClientOutputBundle<O, S>) -> ClientOutputBundle
360 where
361 O: IntoDynInstance<DynType = DynOutput> + 'static,
362 S: IntoDynInstance<DynType = DynState> + 'static,
363 {
364 self.make_dyn(output)
365 }
366
367 pub fn make_client_inputs<I, S>(&self, inputs: ClientInputBundle<I, S>) -> ClientInputBundle
369 where
370 I: IntoDynInstance<DynType = DynInput> + 'static,
371 S: IntoDynInstance<DynType = DynState> + 'static,
372 {
373 self.make_dyn(inputs)
374 }
375
376 pub fn make_dyn_state<S>(&self, sm: S) -> DynState
377 where
378 S: sm::IState + 'static,
379 {
380 DynState::from_typed(self.module_instance_id, sm)
381 }
382
383 pub async fn finalize_and_submit_transaction<F, Meta>(
384 &self,
385 operation_id: OperationId,
386 operation_type: &str,
387 operation_meta_gen: F,
388 tx_builder: TransactionBuilder,
389 ) -> Result<OutPointRange, TransactionSubmitError>
390 where
391 F: Fn(OutPointRange) -> Meta + Clone + MaybeSend + MaybeSync + 'static,
392 Meta: serde::Serialize + MaybeSend,
393 {
394 self.client
395 .get()
396 .finalize_and_submit_transaction(
397 operation_id,
398 operation_type,
399 Box::new(move |out_point_range| {
400 serde_json::to_value(operation_meta_gen(out_point_range)).expect("Can't fail")
401 }),
402 tx_builder,
403 )
404 .await
405 }
406
407 pub async fn finalize_and_submit_transaction_dbtx<F, Meta>(
408 &self,
409 dbtx: &mut DatabaseTransaction<'_>,
410 operation_id: OperationId,
411 operation_type: &str,
412 operation_meta_gen: F,
413 tx_builder: TransactionBuilder,
414 ) -> Result<OutPointRange, TransactionSubmitError>
415 where
416 F: Fn(OutPointRange) -> Meta + MaybeSend + MaybeSync + 'static,
417 Meta: serde::Serialize + MaybeSend,
418 {
419 self.client
420 .get()
421 .finalize_and_submit_transaction_dbtx(
422 &mut dbtx.global_dbtx(self.global_dbtx_access_token),
423 operation_id,
424 operation_type,
425 Box::new(move |out_point_range| {
426 serde_json::to_value(operation_meta_gen(out_point_range)).expect("Can't fail")
427 }),
428 tx_builder,
429 )
430 .await
431 }
432
433 pub async fn fee_quote(
442 &self,
443 operation_id: OperationId,
444 request: FeeQuoteRequest,
445 ) -> Result<FeeQuote, TransactionSubmitError> {
446 self.client.get().fee_quote(operation_id, request).await
447 }
448
449 pub async fn get_balance_for_btc(&self) -> Result<Amount, ModuleLookupError> {
452 self.client
453 .get()
454 .get_balance_for_unit(AmountUnit::BITCOIN)
455 .await
456 }
457
458 pub async fn transaction_updates(&self, operation_id: OperationId) -> TransactionUpdates {
459 self.client.get().transaction_updates(operation_id).await
460 }
461
462 pub async fn await_primary_module_outputs(
463 &self,
464 operation_id: OperationId,
465 outputs: Vec<OutPoint>,
467 ) -> Result<(), TransactionSubmitError> {
468 self.client
469 .get()
470 .await_primary_module_outputs(operation_id, outputs)
471 .await
472 }
473
474 pub async fn get_operation(
476 &self,
477 operation_id: OperationId,
478 ) -> Result<oplog::OperationLogEntry, OperationLookupError> {
479 let operation = self
480 .client
481 .get()
482 .operation_log()
483 .get_operation(operation_id)
484 .await
485 .ok_or(OperationNotFoundError { operation_id })?;
486
487 if operation.operation_module_kind() != M::kind().as_str() {
488 return Err(OperationLookupError::WrongModuleKind {
489 operation_id,
490 expected: M::kind(),
491 found: operation.operation_module_kind().to_owned(),
492 });
493 }
494
495 Ok(operation)
496 }
497
498 fn global_db(&self) -> fedimint_core::db::Database {
502 let db = Clone::clone(self.client.get().db());
503
504 db.ensure_global()
505 .expect("global_db must always return a global db");
506
507 db
508 }
509
510 pub fn module_db(&self) -> &Database {
511 self.module_db
512 .ensure_isolated()
513 .expect("module_db must always return isolated db");
514 &self.module_db
515 }
516
517 pub async fn get_event_log(
520 &self,
521 pos: Option<EventLogId>,
522 limit: u64,
523 ) -> Vec<PersistedLogEntry> {
524 self.global_db()
525 .begin_transaction_nc()
526 .await
527 .get_event_log(pos, limit)
528 .await
529 }
530
531 pub async fn has_active_states(&self, op_id: OperationId) -> bool {
532 self.client.get().has_active_states(op_id).await
533 }
534
535 pub async fn await_no_active_states(&self, op_id: OperationId) {
544 self.client.get().await_no_active_states(op_id).await;
545 }
546
547 pub async fn operation_exists(&self, op_id: OperationId) -> bool {
548 self.client.get().operation_exists(op_id).await
549 }
550
551 pub async fn get_own_active_states(&self) -> Vec<(M::States, ActiveStateMeta)> {
552 self.client
553 .get()
554 .executor()
555 .get_active_states()
556 .await
557 .into_iter()
558 .filter(|s| s.0.module_instance_id() == self.module_instance_id)
559 .map(|s| {
560 (
561 Clone::clone(
562 s.0.as_any()
563 .downcast_ref::<M::States>()
564 .expect("incorrect output type passed to module plugin"),
565 ),
566 s.1,
567 )
568 })
569 .collect()
570 }
571
572 pub async fn get_own_operation_active_states(
574 &self,
575 operation_id: OperationId,
576 ) -> Vec<(M::States, ActiveStateMeta)> {
577 let db = self.global_db();
578 let mut dbtx = db.begin_transaction_nc().await;
579
580 self.client
581 .get()
582 .read_operation_active_states(operation_id, self.module_instance_id, &mut dbtx)
583 .await
584 .map(|(key, meta)| {
585 (
586 Clone::clone(
587 key.state
588 .as_any()
589 .downcast_ref::<M::States>()
590 .expect("incorrect output type passed to module plugin"),
591 ),
592 meta,
593 )
594 })
595 .collect()
596 .await
597 }
598
599 pub async fn get_own_operation_inactive_states(
602 &self,
603 operation_id: OperationId,
604 ) -> Vec<(M::States, InactiveStateMeta)> {
605 let db = self.global_db();
606 let mut dbtx = db.begin_transaction_nc().await;
607
608 self.client
609 .get()
610 .read_operation_inactive_states(operation_id, self.module_instance_id, &mut dbtx)
611 .await
612 .map(|(key, meta)| {
613 (
614 Clone::clone(
615 key.state
616 .as_any()
617 .downcast_ref::<M::States>()
618 .expect("incorrect output type passed to module plugin"),
619 ),
620 meta,
621 )
622 })
623 .collect()
624 .await
625 }
626
627 pub async fn get_config(&self) -> ClientConfig {
628 self.client.get().config().await
629 }
630
631 pub async fn get_invite_code(&self) -> InviteCode {
634 let cfg = self.get_config().await.global;
635 self.client
636 .get()
637 .invite_code(
638 *cfg.api_endpoints
639 .keys()
640 .next()
641 .expect("A federation always has at least one guardian"),
642 )
643 .await
644 .expect("The guardian we requested an invite code for exists")
645 }
646
647 pub fn get_internal_payment_markers(
648 &self,
649 ) -> Result<(PublicKey, u64), bitcoin::secp256k1::Error> {
650 self.client.get().get_internal_payment_markers()
651 }
652
653 pub async fn manual_operation_start(
656 &self,
657 operation_id: OperationId,
658 op_type: &str,
659 operation_meta: impl serde::Serialize + Debug,
660 sms: Vec<DynState>,
661 ) -> Result<(), TransactionSubmitError> {
662 let db = self.module_db();
663 let mut dbtx = db.begin_transaction().await;
664 {
665 let dbtx = &mut dbtx.global_dbtx(self.global_dbtx_access_token);
666
667 self.manual_operation_start_inner(
668 &mut dbtx.to_ref_nc(),
669 operation_id,
670 op_type,
671 operation_meta,
672 sms,
673 )
674 .await?;
675 }
676
677 dbtx.commit_tx_result().await?;
678
679 Ok(())
680 }
681
682 pub async fn manual_operation_start_dbtx(
683 &self,
684 dbtx: &mut DatabaseTransaction<'_>,
685 operation_id: OperationId,
686 op_type: &str,
687 operation_meta: impl serde::Serialize + Debug,
688 sms: Vec<DynState>,
689 ) -> Result<(), OperationAlreadyExistsError> {
690 self.manual_operation_start_inner(
691 &mut dbtx.global_dbtx(self.global_dbtx_access_token),
692 operation_id,
693 op_type,
694 operation_meta,
695 sms,
696 )
697 .await
698 }
699
700 async fn manual_operation_start_inner(
703 &self,
704 dbtx: &mut DatabaseTransaction<'_>,
705 operation_id: OperationId,
706 op_type: &str,
707 operation_meta: impl serde::Serialize + Debug,
708 sms: Vec<DynState>,
709 ) -> Result<(), OperationAlreadyExistsError> {
710 dbtx.ensure_global()
711 .expect("Must deal with global dbtx here");
712
713 if self
714 .client
715 .get()
716 .operation_log()
717 .get_operation_dbtx(&mut dbtx.to_ref_nc(), operation_id)
718 .await
719 .is_some()
720 {
721 return Err(OperationAlreadyExistsError { operation_id });
722 }
723
724 self.client
725 .get()
726 .operation_log()
727 .add_operation_log_entry_dbtx(
728 &mut dbtx.to_ref_nc(),
729 operation_id,
730 op_type,
731 serde_json::to_value(operation_meta).expect("Can't fail"),
732 )
733 .await;
734
735 self.client
736 .get()
737 .executor()
738 .add_state_machines_dbtx(&mut dbtx.to_ref_nc(), sms)
739 .await
740 .expect("State machine is valid");
741
742 Ok(())
743 }
744
745 pub fn outcome_or_updates<U, S>(
759 &self,
760 operation: &OperationLogEntry,
761 operation_id: OperationId,
762 is_terminal: impl Fn(&U) -> bool + MaybeSend + MaybeSync + 'static,
763 stream_gen: impl FnOnce() -> S + MaybeSend + 'static,
764 ) -> UpdateStreamOrOutcome<U>
765 where
766 U: Clone + Serialize + DeserializeOwned + Debug + MaybeSend + MaybeSync + 'static,
767 S: Stream<Item = U> + MaybeSend + 'static,
768 {
769 use futures::StreamExt;
770 match operation.try_outcome::<U>() {
771 Ok(Some(outcome)) if is_terminal(&outcome) => {
772 return UpdateStreamOrOutcome::Outcome(outcome);
773 }
774 Ok(Some(_non_terminal)) => {
775 warn!(
776 target: LOG_CLIENT,
777 "Cached operation outcome is not a terminal update (cached by a previous \
778 version); rebuilding it from the update stream"
779 );
780 }
781 Ok(None) => {}
782 Err(err) => {
783 warn!(
784 target: LOG_CLIENT,
785 err = %err.fmt_compact(),
786 "Cached operation outcome failed to deserialize; rebuilding it from the update stream"
787 );
788 }
789 }
790 let stream = self
791 .client
792 .get()
793 .operation_log()
794 .caching_operation_update_stream(
795 operation_id,
796 Box::new(move || {
797 let stream_gen = stream_gen();
798 Box::pin(
799 stream_gen.map(move |item| serde_json::to_value(item).expect("Can't fail")),
800 )
801 }),
802 Box::new(move |update| {
803 serde_json::from_value::<U>(update.clone())
808 .map(|update| is_terminal(&update))
809 .unwrap_or(false)
810 }),
811 );
812 UpdateStreamOrOutcome::UpdateStream(Box::pin(
813 stream.map(|u| serde_json::from_value::<U>(u).expect("Can't fail")),
814 ))
815 }
816
817 pub async fn claim_inputs<I, S>(
818 &self,
819 dbtx: &mut DatabaseTransaction<'_>,
820 inputs: ClientInputBundle<I, S>,
821 operation_id: OperationId,
822 ) -> Result<OutPointRange, TransactionSubmitError>
823 where
824 I: IInput + MaybeSend + MaybeSync + 'static,
825 S: sm::IState + MaybeSend + MaybeSync + 'static,
826 {
827 self.claim_inputs_dyn(dbtx, inputs.into_instanceless(), operation_id)
828 .await
829 }
830
831 async fn claim_inputs_dyn(
832 &self,
833 dbtx: &mut DatabaseTransaction<'_>,
834 inputs: InstancelessDynClientInputBundle,
835 operation_id: OperationId,
836 ) -> Result<OutPointRange, TransactionSubmitError> {
837 let tx_builder =
838 TransactionBuilder::new().with_inputs(inputs.into_dyn(self.module_instance_id));
839
840 self.client
841 .get()
842 .finalize_and_submit_transaction_inner(
843 &mut dbtx.global_dbtx(self.global_dbtx_access_token),
844 operation_id,
845 tx_builder,
846 )
847 .await
848 }
849
850 pub async fn add_state_machines_dbtx(
851 &self,
852 dbtx: &mut DatabaseTransaction<'_>,
853 states: Vec<DynState>,
854 ) -> AddStateMachinesResult {
855 self.client
856 .get()
857 .executor()
858 .add_state_machines_dbtx(&mut dbtx.global_dbtx(self.global_dbtx_access_token), states)
859 .await
860 }
861
862 pub async fn get_operation_dbtx(
868 &self,
869 dbtx: &mut DatabaseTransaction<'_>,
870 operation_id: OperationId,
871 ) -> Option<oplog::OperationLogEntry> {
872 self.client
873 .get()
874 .operation_log()
875 .get_operation_dbtx(
876 &mut dbtx.global_dbtx(self.global_dbtx_access_token),
877 operation_id,
878 )
879 .await
880 }
881
882 pub async fn add_operation_log_entry_dbtx(
883 &self,
884 dbtx: &mut DatabaseTransaction<'_>,
885 operation_id: OperationId,
886 operation_type: &str,
887 operation_meta: impl serde::Serialize,
888 ) {
889 self.client
890 .get()
891 .operation_log()
892 .add_operation_log_entry_dbtx(
893 &mut dbtx.global_dbtx(self.global_dbtx_access_token),
894 operation_id,
895 operation_type,
896 serde_json::to_value(operation_meta).expect("Can't fail"),
897 )
898 .await;
899 }
900
901 pub async fn log_event<E, Cap>(&self, dbtx: &mut DatabaseTransaction<'_, Cap>, event: E)
902 where
903 E: Event + Send,
904 Cap: Send,
905 {
906 if <E as Event>::MODULE != Some(<M as ClientModule>::kind()) {
907 warn!(
908 target: LOG_CLIENT,
909 module_kind = %<M as ClientModule>::kind(),
910 event_module = ?<E as Event>::MODULE,
911 "Client module logging events of different module than its own. This might become an error in the future."
912 );
913 }
914 self.client
915 .get()
916 .log_event_json(
917 &mut dbtx.global_dbtx(self.global_dbtx_access_token).to_ref_nc(),
918 <E as Event>::MODULE,
919 self.module_instance_id,
920 <E as Event>::KIND,
921 serde_json::to_value(event).expect("Can't fail"),
922 <E as Event>::PERSISTENCE,
923 )
924 .await;
925 }
926}
927
928#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
930pub struct PrimaryModulePriority(u64);
931
932impl PrimaryModulePriority {
933 pub const HIGH: Self = Self(100);
934 pub const LOW: Self = Self(10000);
935
936 pub fn custom(prio: u64) -> Self {
937 Self(prio)
938 }
939}
940pub enum PrimaryModuleSupport {
942 Any { priority: PrimaryModulePriority },
944 Selected {
946 priority: PrimaryModulePriority,
947 units: BTreeSet<AmountUnit>,
948 },
949 None,
951}
952
953impl PrimaryModuleSupport {
954 pub fn selected<const N: usize>(
955 priority: PrimaryModulePriority,
956 units: [AmountUnit; N],
957 ) -> Self {
958 Self::Selected {
959 priority,
960 units: BTreeSet::from(units),
961 }
962 }
963}
964
965#[apply(async_trait_maybe_send!)]
967pub trait ClientModule: Debug + MaybeSend + MaybeSync + 'static {
968 type Init: ClientModuleInit;
969
970 type Common: ModuleCommon;
972
973 type Backup: ModuleBackup;
976
977 type ModuleStateMachineContext: Context;
980
981 type States: State<ModuleContext = Self::ModuleStateMachineContext>
983 + IntoDynInstance<DynType = DynState>;
984
985 fn decoder() -> Decoder {
986 let mut decoder_builder = Self::Common::decoder_builder();
987 decoder_builder.with_decodable_type::<Self::States>();
988 decoder_builder.with_decodable_type::<Self::Backup>();
989 decoder_builder.build()
990 }
991
992 fn kind() -> ModuleKind {
993 <<<Self as ClientModule>::Init as ModuleInit>::Common as CommonModuleInit>::KIND
994 }
995
996 fn context(&self) -> Self::ModuleStateMachineContext;
997
998 async fn start(&self) {}
1004
1005 async fn handle_cli_command(
1012 &self,
1013 _args: &[ffi::OsString],
1014 ) -> Result<serde_json::Value, ClientModuleError> {
1015 Err(ClientModuleError::Unsupported {
1016 kind: <Self as ClientModule>::kind(),
1017 operation: "handle_cli_command",
1018 })
1019 }
1020
1021 async fn handle_rpc(
1029 &self,
1030 _method: String,
1031 _request: serde_json::Value,
1032 ) -> BoxStream<'_, Result<serde_json::Value, ClientModuleError>> {
1033 Box::pin(futures::stream::once(std::future::ready(Err(
1034 ClientModuleError::Unsupported {
1035 kind: <Self as ClientModule>::kind(),
1036 operation: "handle_rpc",
1037 },
1038 ))))
1039 }
1040
1041 fn input_fee(
1050 &self,
1051 amount: &Amounts,
1052 input: &<Self::Common as ModuleCommon>::Input,
1053 ) -> Option<Amounts>;
1054
1055 fn output_fee(
1064 &self,
1065 amount: &Amounts,
1066 output: &<Self::Common as ModuleCommon>::Output,
1067 ) -> Option<Amounts>;
1068
1069 fn supports_backup(&self) -> bool {
1070 false
1071 }
1072
1073 async fn backup(&self) -> Result<Self::Backup, ClientModuleError> {
1080 Err(ClientModuleError::Unsupported {
1081 kind: <Self as ClientModule>::kind(),
1082 operation: "backup",
1083 })
1084 }
1085
1086 fn supports_being_primary(&self) -> PrimaryModuleSupport {
1095 PrimaryModuleSupport::None
1096 }
1097
1098 async fn create_final_inputs_and_outputs(
1122 &self,
1123 _dbtx: &mut DatabaseTransaction<'_>,
1124 _operation_id: OperationId,
1125 _unit: AmountUnit,
1126 _input_amount: Amount,
1127 _output_amount: Amount,
1128 ) -> Result<
1129 (
1130 ClientInputBundle<<Self::Common as ModuleCommon>::Input, Self::States>,
1131 ClientOutputBundle<<Self::Common as ModuleCommon>::Output, Self::States>,
1132 ),
1133 ClientModuleError,
1134 > {
1135 Err(ClientModuleError::Unsupported {
1136 kind: <Self as ClientModule>::kind(),
1137 operation: "create_final_inputs_and_outputs",
1138 })
1139 }
1140
1141 async fn await_primary_module_output(
1152 &self,
1153 _operation_id: OperationId,
1154 _out_point: OutPoint,
1155 ) -> Result<(), ClientModuleError> {
1156 Err(ClientModuleError::Unsupported {
1157 kind: <Self as ClientModule>::kind(),
1158 operation: "await_primary_module_output",
1159 })
1160 }
1161
1162 async fn get_balance(&self, _dbtx: &mut DatabaseTransaction<'_>, _unit: AmountUnit) -> Amount {
1165 unimplemented!()
1166 }
1167
1168 async fn get_balances(&self, _dbtx: &mut DatabaseTransaction<'_>) -> Amounts {
1171 unimplemented!()
1172 }
1173
1174 async fn subscribe_balance_changes(&self) -> BoxStream<'static, ()> {
1177 unimplemented!()
1178 }
1179
1180 async fn leave(&self, _dbtx: &mut DatabaseTransaction<'_>) -> Result<(), ClientModuleError> {
1242 Err(ClientModuleError::Unsupported {
1243 kind: <Self as ClientModule>::kind(),
1244 operation: "leave",
1245 })
1246 }
1247}
1248
1249#[apply(async_trait_maybe_send!)]
1251pub trait IClientModule: Debug {
1252 fn as_any(&self) -> &(maybe_add_send_sync!(dyn std::any::Any));
1253
1254 fn decoder(&self) -> Decoder;
1255
1256 fn context(&self, instance: ModuleInstanceId) -> DynContext;
1257
1258 async fn start(&self);
1259
1260 async fn handle_cli_command(
1261 &self,
1262 args: &[ffi::OsString],
1263 ) -> Result<serde_json::Value, ClientModuleError>;
1264
1265 async fn handle_rpc(
1266 &self,
1267 method: String,
1268 request: serde_json::Value,
1269 ) -> BoxStream<'_, Result<serde_json::Value, ClientModuleError>>;
1270
1271 fn input_fee(&self, amount: &Amounts, input: &DynInput) -> Option<Amounts>;
1272
1273 fn output_fee(&self, amount: &Amounts, output: &DynOutput) -> Option<Amounts>;
1274
1275 fn supports_backup(&self) -> bool;
1276
1277 async fn backup(
1278 &self,
1279 module_instance_id: ModuleInstanceId,
1280 ) -> Result<DynModuleBackup, ClientModuleError>;
1281
1282 fn supports_being_primary(&self) -> PrimaryModuleSupport;
1283
1284 async fn create_final_inputs_and_outputs(
1285 &self,
1286 module_instance: ModuleInstanceId,
1287 dbtx: &mut DatabaseTransaction<'_>,
1288 operation_id: OperationId,
1289 unit: AmountUnit,
1290 input_amount: Amount,
1291 output_amount: Amount,
1292 ) -> Result<(ClientInputBundle, ClientOutputBundle), ClientModuleError>;
1293
1294 async fn await_primary_module_output(
1295 &self,
1296 operation_id: OperationId,
1297 out_point: OutPoint,
1298 ) -> Result<(), ClientModuleError>;
1299
1300 async fn get_balance(
1301 &self,
1302 module_instance: ModuleInstanceId,
1303 dbtx: &mut DatabaseTransaction<'_>,
1304 unit: AmountUnit,
1305 ) -> Amount;
1306
1307 async fn subscribe_balance_changes(&self) -> BoxStream<'static, ()>;
1308}
1309
1310#[apply(async_trait_maybe_send!)]
1311impl<T> IClientModule for T
1312where
1313 T: ClientModule,
1314{
1315 fn as_any(&self) -> &(maybe_add_send_sync!(dyn Any)) {
1316 self
1317 }
1318
1319 fn decoder(&self) -> Decoder {
1320 T::decoder()
1321 }
1322
1323 fn context(&self, instance: ModuleInstanceId) -> DynContext {
1324 DynContext::from_typed(instance, <T as ClientModule>::context(self))
1325 }
1326
1327 async fn start(&self) {
1328 <T as ClientModule>::start(self).await;
1329 }
1330
1331 async fn handle_cli_command(
1332 &self,
1333 args: &[ffi::OsString],
1334 ) -> Result<serde_json::Value, ClientModuleError> {
1335 <T as ClientModule>::handle_cli_command(self, args).await
1336 }
1337
1338 async fn handle_rpc(
1339 &self,
1340 method: String,
1341 request: serde_json::Value,
1342 ) -> BoxStream<'_, Result<serde_json::Value, ClientModuleError>> {
1343 <T as ClientModule>::handle_rpc(self, method, request).await
1344 }
1345
1346 fn input_fee(&self, amount: &Amounts, input: &DynInput) -> Option<Amounts> {
1347 <T as ClientModule>::input_fee(
1348 self,
1349 amount,
1350 input
1351 .as_any()
1352 .downcast_ref()
1353 .expect("Dispatched to correct module"),
1354 )
1355 }
1356
1357 fn output_fee(&self, amount: &Amounts, output: &DynOutput) -> Option<Amounts> {
1358 <T as ClientModule>::output_fee(
1359 self,
1360 amount,
1361 output
1362 .as_any()
1363 .downcast_ref()
1364 .expect("Dispatched to correct module"),
1365 )
1366 }
1367
1368 fn supports_backup(&self) -> bool {
1369 <T as ClientModule>::supports_backup(self)
1370 }
1371
1372 async fn backup(
1373 &self,
1374 module_instance_id: ModuleInstanceId,
1375 ) -> Result<DynModuleBackup, ClientModuleError> {
1376 Ok(DynModuleBackup::from_typed(
1377 module_instance_id,
1378 <T as ClientModule>::backup(self).await?,
1379 ))
1380 }
1381
1382 fn supports_being_primary(&self) -> PrimaryModuleSupport {
1383 <T as ClientModule>::supports_being_primary(self)
1384 }
1385
1386 async fn create_final_inputs_and_outputs(
1387 &self,
1388 module_instance: ModuleInstanceId,
1389 dbtx: &mut DatabaseTransaction<'_>,
1390 operation_id: OperationId,
1391 unit: AmountUnit,
1392 input_amount: Amount,
1393 output_amount: Amount,
1394 ) -> Result<(ClientInputBundle, ClientOutputBundle), ClientModuleError> {
1395 let (inputs, outputs) = <T as ClientModule>::create_final_inputs_and_outputs(
1396 self,
1397 &mut dbtx.to_ref_with_prefix_module_id(module_instance).0,
1398 operation_id,
1399 unit,
1400 input_amount,
1401 output_amount,
1402 )
1403 .await?;
1404
1405 let inputs = inputs.into_dyn(module_instance);
1406
1407 let outputs = outputs.into_dyn(module_instance);
1408
1409 Ok((inputs, outputs))
1410 }
1411
1412 async fn await_primary_module_output(
1413 &self,
1414 operation_id: OperationId,
1415 out_point: OutPoint,
1416 ) -> Result<(), ClientModuleError> {
1417 <T as ClientModule>::await_primary_module_output(self, operation_id, out_point).await
1418 }
1419
1420 async fn get_balance(
1421 &self,
1422 module_instance: ModuleInstanceId,
1423 dbtx: &mut DatabaseTransaction<'_>,
1424 unit: AmountUnit,
1425 ) -> Amount {
1426 <T as ClientModule>::get_balance(
1427 self,
1428 &mut dbtx.to_ref_with_prefix_module_id(module_instance).0,
1429 unit,
1430 )
1431 .await
1432 }
1433
1434 async fn subscribe_balance_changes(&self) -> BoxStream<'static, ()> {
1435 <T as ClientModule>::subscribe_balance_changes(self).await
1436 }
1437}
1438
1439dyn_newtype_define!(
1440 #[derive(Clone)]
1441 pub DynClientModule(Arc<IClientModule>)
1442);
1443
1444impl AsRef<maybe_add_send_sync!(dyn IClientModule + 'static)> for DynClientModule {
1445 fn as_ref(&self) -> &maybe_add_send_sync!(dyn IClientModule + 'static) {
1446 self.inner.as_ref()
1447 }
1448}
1449
1450pub use fedimint_core::{IdxRange, OutPointRange, OutPointRangeIter};
1452
1453pub type StateGenerator<S> = Arc<maybe_add_send_sync!(dyn Fn(OutPointRange) -> Vec<S> + 'static)>;