Skip to main content

fedimint_client_module/module/
mod.rs

1use core::fmt;
2use std::any::Any;
3use std::collections::BTreeSet;
4use std::fmt::Debug;
5use std::pin::Pin;
6use std::sync::{Arc, Weak};
7use std::{ffi, marker, ops};
8
9use anyhow::bail;
10use bitcoin::secp256k1::PublicKey;
11use fedimint_api_client::api::{DynGlobalApi, DynModuleApi};
12use fedimint_core::config::ClientConfig;
13use fedimint_core::core::{
14    Decoder, DynInput, DynOutput, IInput, IntoDynInstance, ModuleInstanceId, ModuleKind,
15    OperationId,
16};
17use fedimint_core::db::{Database, DatabaseTransaction, GlobalDBTxAccessToken, NonCommittable};
18use fedimint_core::invite_code::InviteCode;
19use fedimint_core::module::registry::{ModuleDecoderRegistry, ModuleRegistry};
20use fedimint_core::module::{AmountUnit, Amounts, CommonModuleInit, ModuleCommon, ModuleInit};
21use fedimint_core::task::{MaybeSend, MaybeSync};
22use fedimint_core::util::{BoxStream, FmtCompact as _};
23use fedimint_core::{
24    Amount, OutPoint, PeerId, apply, async_trait_maybe_send, dyn_newtype_define, maybe_add_send,
25    maybe_add_send_sync,
26};
27use fedimint_eventlog::{
28    DBTransactionEventLogExt, Event, EventKind, EventLogId, EventPersistence, PersistedLogEntry,
29};
30use fedimint_logging::LOG_CLIENT;
31use futures::{Stream, StreamExt};
32use serde::Serialize;
33use serde::de::DeserializeOwned;
34use tracing::warn;
35
36use self::init::ClientModuleInit;
37use crate::error::{
38    ModuleLookupError, OperationAlreadyExistsError, OperationLookupError, OperationNotFoundError,
39    TransactionSubmitError,
40};
41use crate::module::recovery::{DynModuleBackup, ModuleBackup};
42use crate::oplog::{IOperationLog, OperationLogEntry, UpdateStreamOrOutcome};
43use crate::sm::executor::{ActiveStateKey, IExecutor, InactiveStateKey};
44use crate::sm::{self, ActiveStateMeta, Context, DynContext, DynState, InactiveStateMeta, State};
45use crate::transaction::{
46    ClientInputBundle, ClientOutputBundle, FeeQuote, FeeQuoteRequest, TransactionBuilder,
47};
48use crate::{AddStateMachinesResult, InstancelessDynClientInputBundle, TransactionUpdates, oplog};
49
50pub mod init;
51pub mod recovery;
52
53pub type ClientModuleRegistry = ModuleRegistry<DynClientModule>;
54
55/// A fedimint-client interface exposed to client modules
56///
57/// To break the dependency of the client modules on the whole fedimint client
58/// and in particular the `fedimint-client` crate, the module gets access to an
59/// interface, that is implemented by the `Client`.
60///
61/// This allows lose coupling, less recompilation and better control and
62/// understanding of what functionality of the Client the modules get access to.
63#[apply(async_trait_maybe_send!)]
64pub trait ClientContextIface: MaybeSend + MaybeSync {
65    fn get_module(&self, instance: ModuleInstanceId) -> &maybe_add_send_sync!(dyn IClientModule);
66    fn api_clone(&self) -> DynGlobalApi;
67    fn decoders(&self) -> &ModuleDecoderRegistry;
68    async fn finalize_and_submit_transaction(
69        &self,
70        operation_id: OperationId,
71        operation_type: &str,
72        operation_meta_gen: Box<maybe_add_send_sync!(dyn Fn(OutPointRange) -> serde_json::Value)>,
73        tx_builder: TransactionBuilder,
74    ) -> Result<OutPointRange, TransactionSubmitError>;
75
76    async fn finalize_and_submit_transaction_dbtx(
77        &self,
78        dbtx: &mut DatabaseTransaction<'_>,
79        operation_id: OperationId,
80        operation_type: &str,
81        operation_meta_gen: Box<maybe_add_send_sync!(dyn Fn(OutPointRange) -> serde_json::Value)>,
82        tx_builder: TransactionBuilder,
83    ) -> Result<OutPointRange, TransactionSubmitError>;
84
85    // TODO: unify
86    async fn finalize_and_submit_transaction_inner(
87        &self,
88        dbtx: &mut DatabaseTransaction<'_>,
89        operation_id: OperationId,
90        tx_builder: TransactionBuilder,
91    ) -> Result<OutPointRange, TransactionSubmitError>;
92
93    /// Computes the fee finalizing and submitting a transaction with the
94    /// explicit items described by `request` would incur, without submitting
95    /// anything. See `Client::fee_quote`.
96    async fn fee_quote(
97        &self,
98        operation_id: OperationId,
99        request: FeeQuoteRequest,
100    ) -> Result<FeeQuote, TransactionSubmitError>;
101
102    /// The client's balance for `unit`, held by the primary module. See
103    /// `Client::get_balance_for_unit`.
104    async fn get_balance_for_unit(&self, unit: AmountUnit) -> Result<Amount, ModuleLookupError>;
105
106    async fn transaction_updates(&self, operation_id: OperationId) -> TransactionUpdates;
107
108    async fn await_primary_module_outputs(
109        &self,
110        operation_id: OperationId,
111        // TODO: make `impl Iterator<Item = ...>`
112        outputs: Vec<OutPoint>,
113    ) -> Result<(), TransactionSubmitError>;
114
115    fn operation_log(&self) -> &dyn IOperationLog;
116
117    async fn has_active_states(&self, operation_id: OperationId) -> bool;
118
119    async fn operation_exists(&self, operation_id: OperationId) -> bool;
120
121    async fn config(&self) -> ClientConfig;
122
123    fn db(&self) -> &Database;
124
125    fn executor(&self) -> &(maybe_add_send_sync!(dyn IExecutor + 'static));
126
127    async fn invite_code(&self, peer: PeerId) -> Option<InviteCode>;
128
129    fn get_internal_payment_markers(&self) -> Result<(PublicKey, u64), bitcoin::secp256k1::Error>;
130
131    #[allow(clippy::too_many_arguments)]
132    async fn log_event_json(
133        &self,
134        dbtx: &mut DatabaseTransaction<'_, NonCommittable>,
135        module_kind: Option<ModuleKind>,
136        module_id: ModuleInstanceId,
137        kind: EventKind,
138        payload: serde_json::Value,
139        persist: EventPersistence,
140    );
141
142    async fn read_operation_active_states<'dbtx>(
143        &self,
144        operation_id: OperationId,
145        module_id: ModuleInstanceId,
146        dbtx: &'dbtx mut DatabaseTransaction<'_>,
147    ) -> Pin<Box<maybe_add_send!(dyn Stream<Item = (ActiveStateKey, ActiveStateMeta)> + 'dbtx)>>;
148
149    async fn read_operation_inactive_states<'dbtx>(
150        &self,
151        operation_id: OperationId,
152        module_id: ModuleInstanceId,
153        dbtx: &'dbtx mut DatabaseTransaction<'_>,
154    ) -> Pin<Box<maybe_add_send!(dyn Stream<Item = (InactiveStateKey, InactiveStateMeta)> + 'dbtx)>>;
155}
156
157/// A final, fully initialized client
158///
159/// Client modules need to be able to access a `Client` they are a part
160/// of. To break the circular dependency, the final `Client` is passed
161/// after `Client` was built via a shared state.
162#[derive(Clone, Default)]
163pub struct FinalClientIface(Arc<std::sync::OnceLock<Weak<dyn ClientContextIface>>>);
164
165impl FinalClientIface {
166    /// Get a temporary strong reference to [`ClientContextIface`]
167    ///
168    /// Care must be taken to not let the user take ownership of this value,
169    /// and not store it elsewhere permanently either, as it could prevent
170    /// the cleanup of the Client.
171    pub(crate) fn get(&self) -> Arc<dyn ClientContextIface> {
172        self.0
173            .get()
174            .expect("client must be already set")
175            .upgrade()
176            .expect("client module context must not be use past client shutdown")
177    }
178
179    pub fn set(&self, client: Weak<dyn ClientContextIface>) {
180        self.0.set(client).expect("FinalLazyClient already set");
181    }
182}
183
184impl fmt::Debug for FinalClientIface {
185    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
186        f.write_str("FinalClientIface")
187    }
188}
189/// A Client context for a [`ClientModule`] `M`
190///
191/// Client modules can interact with the whole
192/// client through this struct.
193pub struct ClientContext<M> {
194    client: FinalClientIface,
195    module_instance_id: ModuleInstanceId,
196    global_dbtx_access_token: GlobalDBTxAccessToken,
197    module_db: Database,
198    _marker: marker::PhantomData<M>,
199}
200
201impl<M> Clone for ClientContext<M> {
202    fn clone(&self) -> Self {
203        Self {
204            client: self.client.clone(),
205            module_db: self.module_db.clone(),
206            module_instance_id: self.module_instance_id,
207            _marker: marker::PhantomData,
208            global_dbtx_access_token: self.global_dbtx_access_token,
209        }
210    }
211}
212
213/// A reference back to itself that the module cacn get from the
214/// [`ClientContext`]
215pub struct ClientContextSelfRef<'s, M> {
216    // we are OK storing `ClientStrong` here, because of the `'s` preventing `Self` from being
217    // stored permanently somewhere
218    client: Arc<dyn ClientContextIface>,
219    module_instance_id: ModuleInstanceId,
220    _marker: marker::PhantomData<&'s M>,
221}
222
223impl<M> ops::Deref for ClientContextSelfRef<'_, M>
224where
225    M: ClientModule,
226{
227    type Target = M;
228
229    fn deref(&self) -> &Self::Target {
230        self.client
231            .get_module(self.module_instance_id)
232            .as_any()
233            .downcast_ref::<M>()
234            .unwrap_or_else(|| panic!("Module is not of type {}", std::any::type_name::<M>()))
235    }
236}
237
238impl<M> fmt::Debug for ClientContext<M> {
239    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240        f.write_str("ClientContext")
241    }
242}
243
244impl<M> ClientContext<M>
245where
246    M: ClientModule,
247{
248    pub fn new(
249        client: FinalClientIface,
250        module_instance_id: ModuleInstanceId,
251        global_dbtx_access_token: GlobalDBTxAccessToken,
252        module_db: Database,
253    ) -> Self {
254        Self {
255            client,
256            module_instance_id,
257            global_dbtx_access_token,
258            module_db,
259            _marker: marker::PhantomData,
260        }
261    }
262
263    /// Get a reference back to client module from the [`Self`]
264    ///
265    /// It's often necessary for a client module to "move self"
266    /// by-value, especially due to async lifetimes issues.
267    /// Clients usually work with `&mut self`, which can't really
268    /// work in such context.
269    ///
270    /// Fortunately [`ClientContext`] is `Clone` and `Send, and
271    /// can be used to recover the reference to the module at later
272    /// time.
273    #[allow(clippy::needless_lifetimes)] // just for explicitiness
274    pub fn self_ref(&self) -> ClientContextSelfRef<'_, M> {
275        ClientContextSelfRef {
276            client: self.client.get(),
277            module_instance_id: self.module_instance_id,
278            _marker: marker::PhantomData,
279        }
280    }
281
282    /// Get a reference to a global Api handle
283    pub fn global_api(&self) -> DynGlobalApi {
284        self.client.get().api_clone()
285    }
286
287    /// Get a reference to a module Api handle
288    pub fn module_api(&self) -> DynModuleApi {
289        self.global_api().with_module(self.module_instance_id)
290    }
291
292    /// A set of all decoders of all modules of the client
293    pub fn decoders(&self) -> ModuleDecoderRegistry {
294        Clone::clone(self.client.get().decoders())
295    }
296
297    pub fn input_from_dyn<'i>(
298        &self,
299        input: &'i DynInput,
300    ) -> Option<&'i <M::Common as ModuleCommon>::Input> {
301        (input.module_instance_id() == self.module_instance_id).then(|| {
302            input
303                .as_any()
304                .downcast_ref::<<M::Common as ModuleCommon>::Input>()
305                .unwrap_or_else(|| {
306                    panic!("instance_id {} just checked", input.module_instance_id())
307                })
308        })
309    }
310
311    pub fn output_from_dyn<'o>(
312        &self,
313        output: &'o DynOutput,
314    ) -> Option<&'o <M::Common as ModuleCommon>::Output> {
315        (output.module_instance_id() == self.module_instance_id).then(|| {
316            output
317                .as_any()
318                .downcast_ref::<<M::Common as ModuleCommon>::Output>()
319                .unwrap_or_else(|| {
320                    panic!("instance_id {} just checked", output.module_instance_id())
321                })
322        })
323    }
324
325    pub fn map_dyn<'s, 'i, 'o, I>(
326        &'s self,
327        typed: impl IntoIterator<Item = I> + 'i,
328    ) -> impl Iterator<Item = <I as IntoDynInstance>::DynType> + 'o
329    where
330        I: IntoDynInstance,
331        'i: 'o,
332        's: 'o,
333    {
334        typed.into_iter().map(|i| self.make_dyn(i))
335    }
336
337    /// Turn a typed output into a dyn version
338    pub fn make_dyn_output(&self, output: <M::Common as ModuleCommon>::Output) -> DynOutput {
339        self.make_dyn(output)
340    }
341
342    /// Turn a typed input into a dyn version
343    pub fn make_dyn_input(&self, input: <M::Common as ModuleCommon>::Input) -> DynInput {
344        self.make_dyn(input)
345    }
346
347    /// Turn a `typed` into a dyn version
348    pub fn make_dyn<I>(&self, typed: I) -> <I as IntoDynInstance>::DynType
349    where
350        I: IntoDynInstance,
351    {
352        typed.into_dyn(self.module_instance_id)
353    }
354
355    /// Turn a typed [`ClientOutputBundle`] into a dyn version
356    pub fn make_client_outputs<O, S>(&self, output: ClientOutputBundle<O, S>) -> ClientOutputBundle
357    where
358        O: IntoDynInstance<DynType = DynOutput> + 'static,
359        S: IntoDynInstance<DynType = DynState> + 'static,
360    {
361        self.make_dyn(output)
362    }
363
364    /// Turn a typed [`ClientInputBundle`] into a dyn version
365    pub fn make_client_inputs<I, S>(&self, inputs: ClientInputBundle<I, S>) -> ClientInputBundle
366    where
367        I: IntoDynInstance<DynType = DynInput> + 'static,
368        S: IntoDynInstance<DynType = DynState> + 'static,
369    {
370        self.make_dyn(inputs)
371    }
372
373    pub fn make_dyn_state<S>(&self, sm: S) -> DynState
374    where
375        S: sm::IState + 'static,
376    {
377        DynState::from_typed(self.module_instance_id, sm)
378    }
379
380    pub async fn finalize_and_submit_transaction<F, Meta>(
381        &self,
382        operation_id: OperationId,
383        operation_type: &str,
384        operation_meta_gen: F,
385        tx_builder: TransactionBuilder,
386    ) -> Result<OutPointRange, TransactionSubmitError>
387    where
388        F: Fn(OutPointRange) -> Meta + Clone + MaybeSend + MaybeSync + 'static,
389        Meta: serde::Serialize + MaybeSend,
390    {
391        self.client
392            .get()
393            .finalize_and_submit_transaction(
394                operation_id,
395                operation_type,
396                Box::new(move |out_point_range| {
397                    serde_json::to_value(operation_meta_gen(out_point_range)).expect("Can't fail")
398                }),
399                tx_builder,
400            )
401            .await
402    }
403
404    pub async fn finalize_and_submit_transaction_dbtx<F, Meta>(
405        &self,
406        dbtx: &mut DatabaseTransaction<'_>,
407        operation_id: OperationId,
408        operation_type: &str,
409        operation_meta_gen: F,
410        tx_builder: TransactionBuilder,
411    ) -> Result<OutPointRange, TransactionSubmitError>
412    where
413        F: Fn(OutPointRange) -> Meta + MaybeSend + MaybeSync + 'static,
414        Meta: serde::Serialize + MaybeSend,
415    {
416        self.client
417            .get()
418            .finalize_and_submit_transaction_dbtx(
419                &mut dbtx.global_dbtx(self.global_dbtx_access_token),
420                operation_id,
421                operation_type,
422                Box::new(move |out_point_range| {
423                    serde_json::to_value(operation_meta_gen(out_point_range)).expect("Can't fail")
424                }),
425                tx_builder,
426            )
427            .await
428    }
429
430    /// Computes the fee that finalizing and submitting a transaction with the
431    /// explicit items described by `request` would incur, as a dry-run over the
432    /// client's current funds, without submitting anything. See
433    /// `Client::fee_quote`.
434    ///
435    /// Summarize the explicit inputs/outputs the operation would submit (their
436    /// gross amounts and federation fees) in `request`; the returned breakdown
437    /// adds the change the primary module's balancing would produce.
438    pub async fn fee_quote(
439        &self,
440        operation_id: OperationId,
441        request: FeeQuoteRequest,
442    ) -> Result<FeeQuote, TransactionSubmitError> {
443        self.client.get().fee_quote(operation_id, request).await
444    }
445
446    /// The client's Bitcoin balance, held by the primary module. See
447    /// `Client::get_balance_for_btc`.
448    pub async fn get_balance_for_btc(&self) -> Result<Amount, ModuleLookupError> {
449        self.client
450            .get()
451            .get_balance_for_unit(AmountUnit::BITCOIN)
452            .await
453    }
454
455    pub async fn transaction_updates(&self, operation_id: OperationId) -> TransactionUpdates {
456        self.client.get().transaction_updates(operation_id).await
457    }
458
459    pub async fn await_primary_module_outputs(
460        &self,
461        operation_id: OperationId,
462        // TODO: make `impl Iterator<Item = ...>`
463        outputs: Vec<OutPoint>,
464    ) -> Result<(), TransactionSubmitError> {
465        self.client
466            .get()
467            .await_primary_module_outputs(operation_id, outputs)
468            .await
469    }
470
471    // TODO: unify with `Self::get_operation`
472    pub async fn get_operation(
473        &self,
474        operation_id: OperationId,
475    ) -> Result<oplog::OperationLogEntry, OperationLookupError> {
476        let operation = self
477            .client
478            .get()
479            .operation_log()
480            .get_operation(operation_id)
481            .await
482            .ok_or(OperationNotFoundError { operation_id })?;
483
484        if operation.operation_module_kind() != M::kind().as_str() {
485            return Err(OperationLookupError::WrongModuleKind {
486                operation_id,
487                expected: M::kind(),
488                found: operation.operation_module_kind().to_owned(),
489            });
490        }
491
492        Ok(operation)
493    }
494
495    /// Get global db.
496    ///
497    /// Only intended for internal use (private).
498    fn global_db(&self) -> fedimint_core::db::Database {
499        let db = Clone::clone(self.client.get().db());
500
501        db.ensure_global()
502            .expect("global_db must always return a global db");
503
504        db
505    }
506
507    pub fn module_db(&self) -> &Database {
508        self.module_db
509            .ensure_isolated()
510            .expect("module_db must always return isolated db");
511        &self.module_db
512    }
513
514    /// Read a portion of the client's event log, starting at `pos` (or the
515    /// beginning of the log if `None`) and returning up to `limit` entries.
516    pub async fn get_event_log(
517        &self,
518        pos: Option<EventLogId>,
519        limit: u64,
520    ) -> Vec<PersistedLogEntry> {
521        self.global_db()
522            .begin_transaction_nc()
523            .await
524            .get_event_log(pos, limit)
525            .await
526    }
527
528    pub async fn has_active_states(&self, op_id: OperationId) -> bool {
529        self.client.get().has_active_states(op_id).await
530    }
531
532    pub async fn operation_exists(&self, op_id: OperationId) -> bool {
533        self.client.get().operation_exists(op_id).await
534    }
535
536    pub async fn get_own_active_states(&self) -> Vec<(M::States, ActiveStateMeta)> {
537        self.client
538            .get()
539            .executor()
540            .get_active_states()
541            .await
542            .into_iter()
543            .filter(|s| s.0.module_instance_id() == self.module_instance_id)
544            .map(|s| {
545                (
546                    Clone::clone(
547                        s.0.as_any()
548                            .downcast_ref::<M::States>()
549                            .expect("incorrect output type passed to module plugin"),
550                    ),
551                    s.1,
552                )
553            })
554            .collect()
555    }
556
557    /// Returns this module's currently active state machines for the operation.
558    pub async fn get_own_operation_active_states(
559        &self,
560        operation_id: OperationId,
561    ) -> Vec<(M::States, ActiveStateMeta)> {
562        let db = self.global_db();
563        let mut dbtx = db.begin_transaction_nc().await;
564
565        self.client
566            .get()
567            .read_operation_active_states(operation_id, self.module_instance_id, &mut dbtx)
568            .await
569            .map(|(key, meta)| {
570                (
571                    Clone::clone(
572                        key.state
573                            .as_any()
574                            .downcast_ref::<M::States>()
575                            .expect("incorrect output type passed to module plugin"),
576                    ),
577                    meta,
578                )
579            })
580            .collect()
581            .await
582    }
583
584    /// Returns this module's previously active state machines for the
585    /// operation.
586    pub async fn get_own_operation_inactive_states(
587        &self,
588        operation_id: OperationId,
589    ) -> Vec<(M::States, InactiveStateMeta)> {
590        let db = self.global_db();
591        let mut dbtx = db.begin_transaction_nc().await;
592
593        self.client
594            .get()
595            .read_operation_inactive_states(operation_id, self.module_instance_id, &mut dbtx)
596            .await
597            .map(|(key, meta)| {
598                (
599                    Clone::clone(
600                        key.state
601                            .as_any()
602                            .downcast_ref::<M::States>()
603                            .expect("incorrect output type passed to module plugin"),
604                    ),
605                    meta,
606                )
607            })
608            .collect()
609            .await
610    }
611
612    pub async fn get_config(&self) -> ClientConfig {
613        self.client.get().config().await
614    }
615
616    /// Returns an invite code for the federation that points to an arbitrary
617    /// guardian server for fetching the config
618    pub async fn get_invite_code(&self) -> InviteCode {
619        let cfg = self.get_config().await.global;
620        self.client
621            .get()
622            .invite_code(
623                *cfg.api_endpoints
624                    .keys()
625                    .next()
626                    .expect("A federation always has at least one guardian"),
627            )
628            .await
629            .expect("The guardian we requested an invite code for exists")
630    }
631
632    pub fn get_internal_payment_markers(
633        &self,
634    ) -> Result<(PublicKey, u64), bitcoin::secp256k1::Error> {
635        self.client.get().get_internal_payment_markers()
636    }
637
638    /// This method starts n state machines with given operation id without a
639    /// corresponding transaction
640    pub async fn manual_operation_start(
641        &self,
642        operation_id: OperationId,
643        op_type: &str,
644        operation_meta: impl serde::Serialize + Debug,
645        sms: Vec<DynState>,
646    ) -> Result<(), TransactionSubmitError> {
647        let db = self.module_db();
648        let mut dbtx = db.begin_transaction().await;
649        {
650            let dbtx = &mut dbtx.global_dbtx(self.global_dbtx_access_token);
651
652            self.manual_operation_start_inner(
653                &mut dbtx.to_ref_nc(),
654                operation_id,
655                op_type,
656                operation_meta,
657                sms,
658            )
659            .await?;
660        }
661
662        dbtx.commit_tx_result().await?;
663
664        Ok(())
665    }
666
667    pub async fn manual_operation_start_dbtx(
668        &self,
669        dbtx: &mut DatabaseTransaction<'_>,
670        operation_id: OperationId,
671        op_type: &str,
672        operation_meta: impl serde::Serialize + Debug,
673        sms: Vec<DynState>,
674    ) -> Result<(), OperationAlreadyExistsError> {
675        self.manual_operation_start_inner(
676            &mut dbtx.global_dbtx(self.global_dbtx_access_token),
677            operation_id,
678            op_type,
679            operation_meta,
680            sms,
681        )
682        .await
683    }
684
685    /// See [`Self::manual_operation_start`], just inside a database
686    /// transaction.
687    async fn manual_operation_start_inner(
688        &self,
689        dbtx: &mut DatabaseTransaction<'_>,
690        operation_id: OperationId,
691        op_type: &str,
692        operation_meta: impl serde::Serialize + Debug,
693        sms: Vec<DynState>,
694    ) -> Result<(), OperationAlreadyExistsError> {
695        dbtx.ensure_global()
696            .expect("Must deal with global dbtx here");
697
698        if self
699            .client
700            .get()
701            .operation_log()
702            .get_operation_dbtx(&mut dbtx.to_ref_nc(), operation_id)
703            .await
704            .is_some()
705        {
706            return Err(OperationAlreadyExistsError { operation_id });
707        }
708
709        self.client
710            .get()
711            .operation_log()
712            .add_operation_log_entry_dbtx(
713                &mut dbtx.to_ref_nc(),
714                operation_id,
715                op_type,
716                serde_json::to_value(operation_meta).expect("Can't fail"),
717            )
718            .await;
719
720        self.client
721            .get()
722            .executor()
723            .add_state_machines_dbtx(&mut dbtx.to_ref_nc(), sms)
724            .await
725            .expect("State machine is valid");
726
727        Ok(())
728    }
729
730    /// Resolve an operation to its cached outcome, or to a caching update
731    /// stream built by `stream_gen`.
732    ///
733    /// `is_terminal` reports whether an update is a final state of the
734    /// operation; only terminal updates are cached as the durable outcome (a
735    /// stream that ends early on a non-terminal update caches nothing). The
736    /// same predicate also validates outcomes READ from the cache: a cached
737    /// outcome that fails to deserialize into `U` (e.g. one written by an
738    /// incompatible earlier version) or that is non-terminal (one frozen by a
739    /// previous version that cached whatever update a stream ended on) is
740    /// discarded with a warning and the state is rebuilt from the update
741    /// stream — whose terminal end then overwrites the stale cache — instead
742    /// of short-circuiting subscribers to it or panicking.
743    pub fn outcome_or_updates<U, S>(
744        &self,
745        operation: &OperationLogEntry,
746        operation_id: OperationId,
747        is_terminal: impl Fn(&U) -> bool + MaybeSend + MaybeSync + 'static,
748        stream_gen: impl FnOnce() -> S + MaybeSend + 'static,
749    ) -> UpdateStreamOrOutcome<U>
750    where
751        U: Clone + Serialize + DeserializeOwned + Debug + MaybeSend + MaybeSync + 'static,
752        S: Stream<Item = U> + MaybeSend + 'static,
753    {
754        use futures::StreamExt;
755        match operation.try_outcome::<U>() {
756            Ok(Some(outcome)) if is_terminal(&outcome) => {
757                return UpdateStreamOrOutcome::Outcome(outcome);
758            }
759            Ok(Some(_non_terminal)) => {
760                warn!(
761                    target: LOG_CLIENT,
762                    "Cached operation outcome is not a terminal update (cached by a previous \
763                     version); rebuilding it from the update stream"
764                );
765            }
766            Ok(None) => {}
767            Err(err) => {
768                warn!(
769                    target: LOG_CLIENT,
770                    err = %err.fmt_compact(),
771                    "Cached operation outcome failed to deserialize; rebuilding it from the update stream"
772                );
773            }
774        }
775        let stream = self
776            .client
777            .get()
778            .operation_log()
779            .caching_operation_update_stream(
780                operation_id,
781                Box::new(move || {
782                    let stream_gen = stream_gen();
783                    Box::pin(
784                        stream_gen.map(move |item| serde_json::to_value(item).expect("Can't fail")),
785                    )
786                }),
787                Box::new(move |update| {
788                    // The update was serialized from a `U` moments ago in this
789                    // process, so deserialization only fails if `U` is not
790                    // round-trip-safe; treat that conservatively as non-terminal
791                    // (skip caching) rather than panicking inside the stream.
792                    serde_json::from_value::<U>(update.clone())
793                        .map(|update| is_terminal(&update))
794                        .unwrap_or(false)
795                }),
796            );
797        UpdateStreamOrOutcome::UpdateStream(Box::pin(
798            stream.map(|u| serde_json::from_value::<U>(u).expect("Can't fail")),
799        ))
800    }
801
802    pub async fn claim_inputs<I, S>(
803        &self,
804        dbtx: &mut DatabaseTransaction<'_>,
805        inputs: ClientInputBundle<I, S>,
806        operation_id: OperationId,
807    ) -> Result<OutPointRange, TransactionSubmitError>
808    where
809        I: IInput + MaybeSend + MaybeSync + 'static,
810        S: sm::IState + MaybeSend + MaybeSync + 'static,
811    {
812        self.claim_inputs_dyn(dbtx, inputs.into_instanceless(), operation_id)
813            .await
814    }
815
816    async fn claim_inputs_dyn(
817        &self,
818        dbtx: &mut DatabaseTransaction<'_>,
819        inputs: InstancelessDynClientInputBundle,
820        operation_id: OperationId,
821    ) -> Result<OutPointRange, TransactionSubmitError> {
822        let tx_builder =
823            TransactionBuilder::new().with_inputs(inputs.into_dyn(self.module_instance_id));
824
825        self.client
826            .get()
827            .finalize_and_submit_transaction_inner(
828                &mut dbtx.global_dbtx(self.global_dbtx_access_token),
829                operation_id,
830                tx_builder,
831            )
832            .await
833    }
834
835    pub async fn add_state_machines_dbtx(
836        &self,
837        dbtx: &mut DatabaseTransaction<'_>,
838        states: Vec<DynState>,
839    ) -> AddStateMachinesResult {
840        self.client
841            .get()
842            .executor()
843            .add_state_machines_dbtx(&mut dbtx.global_dbtx(self.global_dbtx_access_token), states)
844            .await
845    }
846
847    /// Reads an operation log entry within an ongoing database transaction.
848    ///
849    /// Unlike [`Self::get_operation`] this observes writes made earlier in
850    /// `dbtx`, which is what a caller needs to decide atomically whether it is
851    /// about to create an operation that already exists.
852    pub async fn get_operation_dbtx(
853        &self,
854        dbtx: &mut DatabaseTransaction<'_>,
855        operation_id: OperationId,
856    ) -> Option<oplog::OperationLogEntry> {
857        self.client
858            .get()
859            .operation_log()
860            .get_operation_dbtx(
861                &mut dbtx.global_dbtx(self.global_dbtx_access_token),
862                operation_id,
863            )
864            .await
865    }
866
867    pub async fn add_operation_log_entry_dbtx(
868        &self,
869        dbtx: &mut DatabaseTransaction<'_>,
870        operation_id: OperationId,
871        operation_type: &str,
872        operation_meta: impl serde::Serialize,
873    ) {
874        self.client
875            .get()
876            .operation_log()
877            .add_operation_log_entry_dbtx(
878                &mut dbtx.global_dbtx(self.global_dbtx_access_token),
879                operation_id,
880                operation_type,
881                serde_json::to_value(operation_meta).expect("Can't fail"),
882            )
883            .await;
884    }
885
886    pub async fn log_event<E, Cap>(&self, dbtx: &mut DatabaseTransaction<'_, Cap>, event: E)
887    where
888        E: Event + Send,
889        Cap: Send,
890    {
891        if <E as Event>::MODULE != Some(<M as ClientModule>::kind()) {
892            warn!(
893                target: LOG_CLIENT,
894                module_kind = %<M as ClientModule>::kind(),
895                event_module = ?<E as Event>::MODULE,
896                "Client module logging events of different module than its own. This might become an error in the future."
897            );
898        }
899        self.client
900            .get()
901            .log_event_json(
902                &mut dbtx.global_dbtx(self.global_dbtx_access_token).to_ref_nc(),
903                <E as Event>::MODULE,
904                self.module_instance_id,
905                <E as Event>::KIND,
906                serde_json::to_value(event).expect("Can't fail"),
907                <E as Event>::PERSISTENCE,
908            )
909            .await;
910    }
911}
912
913/// Priority module priority (lower number is higher priority)
914#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
915pub struct PrimaryModulePriority(u64);
916
917impl PrimaryModulePriority {
918    pub const HIGH: Self = Self(100);
919    pub const LOW: Self = Self(10000);
920
921    pub fn custom(prio: u64) -> Self {
922        Self(prio)
923    }
924}
925/// Which amount units this module supports being primary for
926pub enum PrimaryModuleSupport {
927    /// Potentially any unit
928    Any { priority: PrimaryModulePriority },
929    /// Some units supported by the module
930    Selected {
931        priority: PrimaryModulePriority,
932        units: BTreeSet<AmountUnit>,
933    },
934    /// None
935    None,
936}
937
938impl PrimaryModuleSupport {
939    pub fn selected<const N: usize>(
940        priority: PrimaryModulePriority,
941        units: [AmountUnit; N],
942    ) -> Self {
943        Self::Selected {
944            priority,
945            units: BTreeSet::from(units),
946        }
947    }
948}
949
950/// Fedimint module client
951#[apply(async_trait_maybe_send!)]
952pub trait ClientModule: Debug + MaybeSend + MaybeSync + 'static {
953    type Init: ClientModuleInit;
954
955    /// Common module types shared between client and server
956    type Common: ModuleCommon;
957
958    /// Data stored in regular backups so that restoring doesn't have to start
959    /// from epoch 0
960    type Backup: ModuleBackup;
961
962    /// Data and API clients available to state machine transitions of this
963    /// module
964    type ModuleStateMachineContext: Context;
965
966    /// All possible states this client can submit to the executor
967    type States: State<ModuleContext = Self::ModuleStateMachineContext>
968        + IntoDynInstance<DynType = DynState>;
969
970    fn decoder() -> Decoder {
971        let mut decoder_builder = Self::Common::decoder_builder();
972        decoder_builder.with_decodable_type::<Self::States>();
973        decoder_builder.with_decodable_type::<Self::Backup>();
974        decoder_builder.build()
975    }
976
977    fn kind() -> ModuleKind {
978        <<<Self as ClientModule>::Init as ModuleInit>::Common as CommonModuleInit>::KIND
979    }
980
981    fn context(&self) -> Self::ModuleStateMachineContext;
982
983    /// Initialize client.
984    ///
985    /// Called by the core client code on start, after [`ClientContext`] is
986    /// fully initialized, so unlike during [`ClientModuleInit::init`],
987    /// access to global client is allowed.
988    async fn start(&self) {}
989
990    async fn handle_cli_command(
991        &self,
992        _args: &[ffi::OsString],
993    ) -> anyhow::Result<serde_json::Value> {
994        Err(anyhow::format_err!(
995            "This module does not implement cli commands"
996        ))
997    }
998
999    async fn handle_rpc(
1000        &self,
1001        _method: String,
1002        _request: serde_json::Value,
1003    ) -> BoxStream<'_, anyhow::Result<serde_json::Value>> {
1004        Box::pin(futures::stream::once(std::future::ready(Err(
1005            anyhow::format_err!("This module does not implement rpc"),
1006        ))))
1007    }
1008
1009    /// Returns the fee the processing of this input requires.
1010    ///
1011    /// If the semantics of a given input aren't known this function returns
1012    /// `None`, this only happens if a future version of Fedimint introduces a
1013    /// new input variant. For clients this should only be the case when
1014    /// processing transactions created by other users, so the result of
1015    /// this function can be `unwrap`ped whenever dealing with inputs
1016    /// generated by ourselves.
1017    fn input_fee(
1018        &self,
1019        amount: &Amounts,
1020        input: &<Self::Common as ModuleCommon>::Input,
1021    ) -> Option<Amounts>;
1022
1023    /// Returns the fee the processing of this output requires.
1024    ///
1025    /// If the semantics of a given output aren't known this function returns
1026    /// `None`, this only happens if a future version of Fedimint introduces a
1027    /// new output variant. For clients this should only be the case when
1028    /// processing transactions created by other users, so the result of
1029    /// this function can be `unwrap`ped whenever dealing with inputs
1030    /// generated by ourselves.
1031    fn output_fee(
1032        &self,
1033        amount: &Amounts,
1034        output: &<Self::Common as ModuleCommon>::Output,
1035    ) -> Option<Amounts>;
1036
1037    fn supports_backup(&self) -> bool {
1038        false
1039    }
1040
1041    async fn backup(&self) -> anyhow::Result<Self::Backup> {
1042        anyhow::bail!("Backup not supported");
1043    }
1044
1045    /// Does this module support being a primary module
1046    ///
1047    /// If it does it must implement:
1048    ///
1049    /// * [`Self::create_final_inputs_and_outputs`]
1050    /// * [`Self::await_primary_module_output`]
1051    /// * [`Self::get_balance`]
1052    /// * [`Self::subscribe_balance_changes`]
1053    fn supports_being_primary(&self) -> PrimaryModuleSupport {
1054        PrimaryModuleSupport::None
1055    }
1056
1057    /// Creates all inputs and outputs necessary to balance the transaction.
1058    /// The function returns an error if and only if the client's funds are not
1059    /// sufficient to create the inputs necessary to fully fund the transaction.
1060    ///
1061    /// A returned input also contains:
1062    /// * A set of private keys belonging to the input for signing the
1063    ///   transaction
1064    /// * A closure that generates states belonging to the input. This closure
1065    ///   takes the transaction id of the transaction in which the input was
1066    ///   used and the input index as input since these cannot be known at time
1067    ///   of calling `create_funding_input` and have to be injected later.
1068    ///
1069    /// A returned output also contains:
1070    /// * A closure that generates states belonging to the output. This closure
1071    ///   takes the transaction id of the transaction in which the output was
1072    ///   used and the output index as input since these cannot be known at time
1073    ///   of calling `create_change_output` and have to be injected later.
1074    async fn create_final_inputs_and_outputs(
1075        &self,
1076        _dbtx: &mut DatabaseTransaction<'_>,
1077        _operation_id: OperationId,
1078        _unit: AmountUnit,
1079        _input_amount: Amount,
1080        _output_amount: Amount,
1081    ) -> anyhow::Result<(
1082        ClientInputBundle<<Self::Common as ModuleCommon>::Input, Self::States>,
1083        ClientOutputBundle<<Self::Common as ModuleCommon>::Output, Self::States>,
1084    )> {
1085        unimplemented!()
1086    }
1087
1088    /// Waits for the funds from an output created by
1089    /// [`Self::create_final_inputs_and_outputs`] to become available. This
1090    /// function returning typically implies a change in the output of
1091    /// [`Self::get_balance`].
1092    async fn await_primary_module_output(
1093        &self,
1094        _operation_id: OperationId,
1095        _out_point: OutPoint,
1096    ) -> anyhow::Result<()> {
1097        unimplemented!()
1098    }
1099
1100    /// Returns the balance held by this module and available for funding
1101    /// transactions.
1102    async fn get_balance(&self, _dbtx: &mut DatabaseTransaction<'_>, _unit: AmountUnit) -> Amount {
1103        unimplemented!()
1104    }
1105
1106    /// Returns the balance held by this module and available for funding
1107    /// transactions.
1108    async fn get_balances(&self, _dbtx: &mut DatabaseTransaction<'_>) -> Amounts {
1109        unimplemented!()
1110    }
1111
1112    /// Returns a stream that will output the updated module balance each time
1113    /// it changes.
1114    async fn subscribe_balance_changes(&self) -> BoxStream<'static, ()> {
1115        unimplemented!()
1116    }
1117
1118    /// Leave the federation
1119    ///
1120    /// While technically there's nothing stopping the client from just
1121    /// abandoning Federation at any point by deleting all the related
1122    /// local data, it is useful to make sure it's safe beforehand.
1123    ///
1124    /// This call indicates the desire of the caller client code
1125    /// to orderly and safely leave the Federation by this module instance.
1126    /// The goal of the implementations is to fulfil that wish,
1127    /// giving prompt and informative feedback if it's not yet possible.
1128    ///
1129    /// The client module implementation should handle the request
1130    /// and return as fast as possible avoiding blocking for longer than
1131    /// necessary. This would usually involve some combination of:
1132    ///
1133    /// * recording the state of being in process of leaving the Federation to
1134    ///   prevent initiating new conditions that could delay its completion;
1135    /// * performing any fast to complete cleanup/exit logic;
1136    /// * initiating any time-consuming logic (e.g. canceling outstanding
1137    ///   contracts), as background jobs, tasks machines, etc.
1138    /// * checking for any conditions indicating it might not be safe to leave
1139    ///   at the moment.
1140    ///
1141    /// This function should return `Ok` only if from the perspective
1142    /// of this module instance, it is safe to delete client data and
1143    /// stop using it, with no further actions (like background jobs) required
1144    /// to complete.
1145    ///
1146    /// This function should return an error if it's not currently possible
1147    /// to safely (e.g. without losing funds) leave the Federation.
1148    /// It should avoid running indefinitely trying to complete any cleanup
1149    /// actions necessary to reach a clean state, preferring spawning new
1150    /// state machines and returning an informative error about cleanup
1151    /// still in progress.
1152    ///
1153    /// If any internal task needs to complete, any user action is required,
1154    /// or even external condition needs to be met this function
1155    /// should return a `Err`.
1156    ///
1157    /// Notably modules should not disable interaction that might be necessary
1158    /// for the user (possibly through other modules) to leave the Federation.
1159    /// In particular a Mint module should retain ability to create new notes,
1160    /// and LN module should retain ability to send funds out.
1161    ///
1162    /// Calling code must NOT assume that a module that once returned `Ok`,
1163    /// will not return `Err` at later point. E.g. a Mint module might have
1164    /// no outstanding balance at first, but other modules winding down
1165    /// might "cash-out" to Ecash.
1166    ///
1167    /// Before leaving the Federation and deleting any state the calling code
1168    /// must collect a full round of `Ok` from all the modules.
1169    ///
1170    /// Calling code should allow the user to override and ignore any
1171    /// outstanding errors, after sufficient amount of warnings. Ideally,
1172    /// this should be done on per-module basis, to avoid mistakes.
1173    async fn leave(&self, _dbtx: &mut DatabaseTransaction<'_>) -> anyhow::Result<()> {
1174        bail!("Unable to determine if safe to leave the federation: Not implemented")
1175    }
1176}
1177
1178/// Type-erased version of [`ClientModule`]
1179#[apply(async_trait_maybe_send!)]
1180pub trait IClientModule: Debug {
1181    fn as_any(&self) -> &(maybe_add_send_sync!(dyn std::any::Any));
1182
1183    fn as_any_arc(self: Arc<Self>) -> Arc<maybe_add_send_sync!(dyn std::any::Any + 'static)>;
1184
1185    fn decoder(&self) -> Decoder;
1186
1187    fn context(&self, instance: ModuleInstanceId) -> DynContext;
1188
1189    async fn start(&self);
1190
1191    async fn handle_cli_command(&self, args: &[ffi::OsString])
1192    -> anyhow::Result<serde_json::Value>;
1193
1194    async fn handle_rpc(
1195        &self,
1196        method: String,
1197        request: serde_json::Value,
1198    ) -> BoxStream<'_, anyhow::Result<serde_json::Value>>;
1199
1200    fn input_fee(&self, amount: &Amounts, input: &DynInput) -> Option<Amounts>;
1201
1202    fn output_fee(&self, amount: &Amounts, output: &DynOutput) -> Option<Amounts>;
1203
1204    fn supports_backup(&self) -> bool;
1205
1206    async fn backup(&self, module_instance_id: ModuleInstanceId)
1207    -> anyhow::Result<DynModuleBackup>;
1208
1209    fn supports_being_primary(&self) -> PrimaryModuleSupport;
1210
1211    async fn create_final_inputs_and_outputs(
1212        &self,
1213        module_instance: ModuleInstanceId,
1214        dbtx: &mut DatabaseTransaction<'_>,
1215        operation_id: OperationId,
1216        unit: AmountUnit,
1217        input_amount: Amount,
1218        output_amount: Amount,
1219    ) -> anyhow::Result<(ClientInputBundle, ClientOutputBundle)>;
1220
1221    async fn await_primary_module_output(
1222        &self,
1223        operation_id: OperationId,
1224        out_point: OutPoint,
1225    ) -> anyhow::Result<()>;
1226
1227    async fn get_balance(
1228        &self,
1229        module_instance: ModuleInstanceId,
1230        dbtx: &mut DatabaseTransaction<'_>,
1231        unit: AmountUnit,
1232    ) -> Amount;
1233
1234    async fn subscribe_balance_changes(&self) -> BoxStream<'static, ()>;
1235}
1236
1237#[apply(async_trait_maybe_send!)]
1238impl<T> IClientModule for T
1239where
1240    T: ClientModule,
1241{
1242    fn as_any(&self) -> &(maybe_add_send_sync!(dyn Any)) {
1243        self
1244    }
1245
1246    fn as_any_arc(self: Arc<Self>) -> Arc<maybe_add_send_sync!(dyn Any + 'static)> {
1247        self
1248    }
1249
1250    fn decoder(&self) -> Decoder {
1251        T::decoder()
1252    }
1253
1254    fn context(&self, instance: ModuleInstanceId) -> DynContext {
1255        DynContext::from_typed(instance, <T as ClientModule>::context(self))
1256    }
1257
1258    async fn start(&self) {
1259        <T as ClientModule>::start(self).await;
1260    }
1261
1262    async fn handle_cli_command(
1263        &self,
1264        args: &[ffi::OsString],
1265    ) -> anyhow::Result<serde_json::Value> {
1266        <T as ClientModule>::handle_cli_command(self, args).await
1267    }
1268
1269    async fn handle_rpc(
1270        &self,
1271        method: String,
1272        request: serde_json::Value,
1273    ) -> BoxStream<'_, anyhow::Result<serde_json::Value>> {
1274        <T as ClientModule>::handle_rpc(self, method, request).await
1275    }
1276
1277    fn input_fee(&self, amount: &Amounts, input: &DynInput) -> Option<Amounts> {
1278        <T as ClientModule>::input_fee(
1279            self,
1280            amount,
1281            input
1282                .as_any()
1283                .downcast_ref()
1284                .expect("Dispatched to correct module"),
1285        )
1286    }
1287
1288    fn output_fee(&self, amount: &Amounts, output: &DynOutput) -> Option<Amounts> {
1289        <T as ClientModule>::output_fee(
1290            self,
1291            amount,
1292            output
1293                .as_any()
1294                .downcast_ref()
1295                .expect("Dispatched to correct module"),
1296        )
1297    }
1298
1299    fn supports_backup(&self) -> bool {
1300        <T as ClientModule>::supports_backup(self)
1301    }
1302
1303    async fn backup(
1304        &self,
1305        module_instance_id: ModuleInstanceId,
1306    ) -> anyhow::Result<DynModuleBackup> {
1307        Ok(DynModuleBackup::from_typed(
1308            module_instance_id,
1309            <T as ClientModule>::backup(self).await?,
1310        ))
1311    }
1312
1313    fn supports_being_primary(&self) -> PrimaryModuleSupport {
1314        <T as ClientModule>::supports_being_primary(self)
1315    }
1316
1317    async fn create_final_inputs_and_outputs(
1318        &self,
1319        module_instance: ModuleInstanceId,
1320        dbtx: &mut DatabaseTransaction<'_>,
1321        operation_id: OperationId,
1322        unit: AmountUnit,
1323        input_amount: Amount,
1324        output_amount: Amount,
1325    ) -> anyhow::Result<(ClientInputBundle, ClientOutputBundle)> {
1326        let (inputs, outputs) = <T as ClientModule>::create_final_inputs_and_outputs(
1327            self,
1328            &mut dbtx.to_ref_with_prefix_module_id(module_instance).0,
1329            operation_id,
1330            unit,
1331            input_amount,
1332            output_amount,
1333        )
1334        .await?;
1335
1336        let inputs = inputs.into_dyn(module_instance);
1337
1338        let outputs = outputs.into_dyn(module_instance);
1339
1340        Ok((inputs, outputs))
1341    }
1342
1343    async fn await_primary_module_output(
1344        &self,
1345        operation_id: OperationId,
1346        out_point: OutPoint,
1347    ) -> anyhow::Result<()> {
1348        <T as ClientModule>::await_primary_module_output(self, operation_id, out_point).await
1349    }
1350
1351    async fn get_balance(
1352        &self,
1353        module_instance: ModuleInstanceId,
1354        dbtx: &mut DatabaseTransaction<'_>,
1355        unit: AmountUnit,
1356    ) -> Amount {
1357        <T as ClientModule>::get_balance(
1358            self,
1359            &mut dbtx.to_ref_with_prefix_module_id(module_instance).0,
1360            unit,
1361        )
1362        .await
1363    }
1364
1365    async fn subscribe_balance_changes(&self) -> BoxStream<'static, ()> {
1366        <T as ClientModule>::subscribe_balance_changes(self).await
1367    }
1368}
1369
1370dyn_newtype_define!(
1371    #[derive(Clone)]
1372    pub DynClientModule(Arc<IClientModule>)
1373);
1374
1375impl DynClientModule {
1376    pub fn as_any_arc(&self) -> Arc<maybe_add_send_sync!(dyn Any + 'static)> {
1377        self.inner.clone().as_any_arc()
1378    }
1379}
1380
1381impl AsRef<maybe_add_send_sync!(dyn IClientModule + 'static)> for DynClientModule {
1382    fn as_ref(&self) -> &maybe_add_send_sync!(dyn IClientModule + 'static) {
1383        self.inner.as_ref()
1384    }
1385}
1386
1387// Re-export types from fedimint_core
1388pub use fedimint_core::{IdxRange, OutPointRange, OutPointRangeIter};
1389
1390pub type StateGenerator<S> = Arc<maybe_add_send_sync!(dyn Fn(OutPointRange) -> Vec<S> + 'static)>;