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