1use std::collections::{BTreeMap, BTreeSet, HashSet};
2use std::convert::Infallible;
3use std::fmt::{Debug, Formatter};
4use std::io::{Error, Write};
5use std::mem;
6use std::sync::Arc;
7use std::time::Duration;
8
9use fedimint_client_module::sm::executor::{
10 ActiveStateKey, ContextGen, IExecutor, InactiveStateKey,
11};
12use fedimint_client_module::sm::{
13 ActiveStateMeta, ClientSMDatabaseTransaction, DynContext, DynState, InactiveStateMeta, State,
14 StateTransition, StateTransitionFunction,
15};
16use fedimint_core::core::{IntoDynInstance, ModuleInstanceId, OperationId};
17use fedimint_core::db::{
18 AutocommitError, Database, DatabaseKeyWithNotify, DatabaseTransaction,
19 IDatabaseTransactionOpsCoreTyped,
20};
21use fedimint_core::encoding::{Decodable, DecodeError, Encodable};
22use fedimint_core::fmt_utils::AbbreviateJson;
23use fedimint_core::module::registry::ModuleDecoderRegistry;
24use fedimint_core::task::TaskGroup;
25use fedimint_core::util::{BoxFuture, FmtCompactAnyhow as _};
26use fedimint_core::{apply, async_trait_maybe_send};
27use fedimint_eventlog::{DBTransactionEventLogExt as _, Event, EventKind, EventPersistence};
28use fedimint_logging::LOG_CLIENT_REACTOR;
29use futures::future::{self, select_all};
30use futures::stream::{FuturesUnordered, StreamExt};
31use serde::{Deserialize, Serialize};
32use tokio::select;
33use tokio::sync::{mpsc, oneshot, watch};
34use tracing::{Instrument, debug, error, info, trace, warn};
35
36use crate::sm::notifier::Notifier;
37use crate::{AddStateMachinesError, AddStateMachinesResult, DynGlobalClientContext};
38
39const MAX_DB_ATTEMPTS: Option<usize> = Some(100);
41
42pub(crate) enum ExecutorDbPrefixes {
44 ActiveStates = 0xa1,
46 InactiveStates = 0xa2,
48}
49
50#[derive(Serialize, Deserialize)]
51pub struct StateMachineUpdated {
52 operation_id: OperationId,
53 started: bool,
54 terminal: bool,
55 module_id: ModuleInstanceId,
56}
57
58impl Event for StateMachineUpdated {
59 const MODULE: Option<fedimint_core::core::ModuleKind> = None;
60 const KIND: EventKind = EventKind::from_static("sm-updated");
61 const PERSISTENCE: EventPersistence = EventPersistence::Trimable;
62}
63
64#[derive(Clone, Debug)]
72pub struct Executor {
73 inner: Arc<ExecutorInner>,
74}
75
76struct ExecutorInner {
77 db: Database,
78 state: std::sync::RwLock<ExecutorState>,
79 module_contexts: BTreeMap<ModuleInstanceId, DynContext>,
80 valid_module_ids: BTreeSet<ModuleInstanceId>,
81 notifier: Notifier,
82 sm_update_tx: mpsc::UnboundedSender<DynState>,
85 client_task_group: TaskGroup,
86 log_ordering_wakeup_tx: watch::Sender<()>,
87}
88
89enum ExecutorState {
90 Unstarted {
91 sm_update_rx: mpsc::UnboundedReceiver<DynState>,
92 },
93 Running {
94 context_gen: ContextGen,
95 shutdown_sender: oneshot::Sender<()>,
96 },
97 Stopped,
98}
99
100impl ExecutorState {
101 fn start(
105 &mut self,
106 context: ContextGen,
107 ) -> Option<(oneshot::Receiver<()>, mpsc::UnboundedReceiver<DynState>)> {
108 let (shutdown_sender, shutdown_receiver) = tokio::sync::oneshot::channel::<()>();
109
110 let previous_state = mem::replace(
111 self,
112 ExecutorState::Running {
113 context_gen: context,
114 shutdown_sender,
115 },
116 );
117
118 match previous_state {
119 ExecutorState::Unstarted { sm_update_rx } => Some((shutdown_receiver, sm_update_rx)),
120 _ => {
121 *self = previous_state;
123
124 debug!(target: LOG_CLIENT_REACTOR, "Executor already started, ignoring start request");
125 None
126 }
127 }
128 }
129
130 fn stop(&mut self) -> Option<()> {
133 let previous_state = mem::replace(self, ExecutorState::Stopped);
134
135 match previous_state {
136 ExecutorState::Running {
137 shutdown_sender, ..
138 } => {
139 if shutdown_sender.send(()).is_err() {
140 warn!(target: LOG_CLIENT_REACTOR, "Failed to send shutdown signal to executor, already dead?");
141 }
142 Some(())
143 }
144 _ => {
145 *self = previous_state;
147
148 debug!(target: LOG_CLIENT_REACTOR, "Executor not running, ignoring stop request");
149 None
150 }
151 }
152 }
153
154 fn gen_context(&self, state: &DynState) -> Option<DynGlobalClientContext> {
155 let ExecutorState::Running { context_gen, .. } = self else {
156 return None;
157 };
158 Some(context_gen(
159 state.module_instance_id(),
160 state.operation_id(),
161 ))
162 }
163}
164
165#[derive(Debug, Default)]
168pub struct ExecutorBuilder {
169 module_contexts: BTreeMap<ModuleInstanceId, DynContext>,
170 valid_module_ids: BTreeSet<ModuleInstanceId>,
171}
172
173impl Executor {
174 pub fn builder() -> ExecutorBuilder {
176 ExecutorBuilder::default()
177 }
178
179 pub async fn get_active_states(&self) -> Vec<(DynState, ActiveStateMeta)> {
180 self.inner.get_active_states().await
181 }
182
183 pub async fn add_state_machines(
189 &self,
190 states: Vec<DynState>,
191 ) -> Result<(), AddStateMachinesError> {
192 self.inner
193 .db
194 .autocommit(
195 |dbtx, _| Box::pin(self.add_state_machines_dbtx(dbtx, states.clone())),
196 MAX_DB_ATTEMPTS,
197 )
198 .await
199 .map_err(|e| match e {
200 AutocommitError::CommitFailed { last_error, .. } => {
201 AddStateMachinesError::Database(last_error)
202 }
203 AutocommitError::ClosureError { error, .. } => error,
204 })?;
205
206 Ok(())
209 }
210
211 pub async fn add_state_machines_dbtx(
220 &self,
221 dbtx: &mut DatabaseTransaction<'_>,
222 states: Vec<DynState>,
223 ) -> AddStateMachinesResult {
224 for state in states {
225 if !self
226 .inner
227 .valid_module_ids
228 .contains(&state.module_instance_id())
229 {
230 return Err(AddStateMachinesError::UnknownModule {
231 module_instance_id: state.module_instance_id(),
232 });
233 }
234
235 let is_active_state = dbtx
236 .get_value(&ActiveStateKeyDb(ActiveStateKey::from_state(state.clone())))
237 .await
238 .is_some();
239 let is_inactive_state = dbtx
240 .get_value(&InactiveStateKeyDb(InactiveStateKey::from_state(
241 state.clone(),
242 )))
243 .await
244 .is_some();
245
246 if is_active_state || is_inactive_state {
247 return Err(AddStateMachinesError::StateAlreadyExists);
248 }
249
250 if let Some(module_context) =
255 self.inner.module_contexts.get(&state.module_instance_id())
256 {
257 match self
258 .inner
259 .state
260 .read()
261 .expect("locking failed")
262 .gen_context(&state)
263 {
264 Some(context) => {
265 if state.is_terminal(module_context, &context) {
266 return Err(AddStateMachinesError::StateAlreadyTerminal);
267 }
268 }
269 _ => {
270 warn!(target: LOG_CLIENT_REACTOR, "Executor should be running at this point");
271 }
272 }
273 }
274
275 dbtx.insert_new_entry(
276 &ActiveStateKeyDb(ActiveStateKey::from_state(state.clone())),
277 &ActiveStateMeta::default(),
278 )
279 .await;
280
281 let operation_id = state.operation_id();
282 self.inner
283 .log_event_dbtx(
284 dbtx,
285 StateMachineUpdated {
286 operation_id,
287 started: true,
288 terminal: false,
289 module_id: state.module_instance_id(),
290 },
291 )
292 .await;
293
294 let notify_sender = self.inner.notifier.sender();
295 let sm_updates_tx = self.inner.sm_update_tx.clone();
296 dbtx.on_commit(move || {
297 notify_sender.notify(state.clone());
298 let _ = sm_updates_tx.send(state);
299 });
300 }
301
302 Ok(())
303 }
304
305 pub async fn contains_active_state<S: State>(
310 &self,
311 instance: ModuleInstanceId,
312 state: S,
313 ) -> bool {
314 let state = DynState::from_typed(instance, state);
315 self.inner
316 .get_active_states()
317 .await
318 .into_iter()
319 .any(|(s, _)| s == state)
320 }
321
322 pub async fn contains_inactive_state<S: State>(
330 &self,
331 instance: ModuleInstanceId,
332 state: S,
333 ) -> bool {
334 let state = DynState::from_typed(instance, state);
335 self.inner
336 .get_inactive_states()
337 .await
338 .into_iter()
339 .any(|(s, _)| s == state)
340 }
341
342 pub async fn await_inactive_state(&self, state: DynState) -> InactiveStateMeta {
343 self.inner
344 .db
345 .wait_key_exists(&InactiveStateKeyDb(InactiveStateKey::from_state(state)))
346 .await
347 }
348
349 pub async fn await_active_state(&self, state: DynState) -> ActiveStateMeta {
350 self.inner
351 .db
352 .wait_key_exists(&ActiveStateKeyDb(ActiveStateKey::from_state(state)))
353 .await
354 }
355
356 pub async fn get_operation_states(
358 &self,
359 operation_id: OperationId,
360 ) -> (
361 Vec<(DynState, ActiveStateMeta)>,
362 Vec<(DynState, InactiveStateMeta)>,
363 ) {
364 let mut dbtx = self.inner.db.begin_transaction_nc().await;
365 let active_states: Vec<_> = dbtx
366 .find_by_prefix(&ActiveOperationStateKeyPrefix { operation_id })
367 .await
368 .map(|(active_key, active_meta)| (active_key.0.state, active_meta))
369 .collect()
370 .await;
371 let inactive_states: Vec<_> = dbtx
372 .find_by_prefix(&InactiveOperationStateKeyPrefix { operation_id })
373 .await
374 .map(|(active_key, inactive_meta)| (active_key.0.state, inactive_meta))
375 .collect()
376 .await;
377
378 (active_states, inactive_states)
379 }
380
381 pub fn start_executor(&self, context_gen: ContextGen, client_span: tracing::Span) {
388 let Some((shutdown_receiver, sm_update_rx)) = self
389 .inner
390 .state
391 .write()
392 .expect("locking can't fail")
393 .start(context_gen.clone())
394 else {
395 panic!("start_executor was called previously");
396 };
397
398 let task_runner_inner = self.inner.clone();
399 let _handle = self.inner.client_task_group.spawn_with_span(
400 client_span,
401 "sm-executor",
402 |task_handle| async move {
403 let executor_runner = task_runner_inner.run(context_gen, sm_update_rx);
404 let task_group_shutdown_rx = task_handle.make_shutdown_rx();
405 select! {
406 () = task_group_shutdown_rx => {
407 debug!(
408 target: LOG_CLIENT_REACTOR,
409 "Shutting down state machine executor runner due to task group shutdown signal"
410 );
411 },
412 shutdown_happened_sender = shutdown_receiver => {
413 match shutdown_happened_sender {
414 Ok(()) => {
415 debug!(
416 target: LOG_CLIENT_REACTOR,
417 "Shutting down state machine executor runner due to explicit shutdown signal"
418 );
419 },
420 Err(_) => {
421 warn!(
422 target: LOG_CLIENT_REACTOR,
423 "Shutting down state machine executor runner because the shutdown signal channel was closed (the executor object was dropped)"
424 );
425 }
426 }
427 },
428 () = executor_runner => {
429 error!(target: LOG_CLIENT_REACTOR, "State machine executor runner exited unexpectedly!");
430 },
431 };
432 },
433 );
434 }
435
436 pub fn stop_executor(&self) -> Option<()> {
450 self.inner.stop_executor()
451 }
452
453 pub fn notifier(&self) -> &Notifier {
456 &self.inner.notifier
457 }
458}
459
460impl Drop for ExecutorInner {
461 fn drop(&mut self) {
462 self.stop_executor();
463 }
464}
465
466struct TransitionForActiveState {
467 outcome: serde_json::Value,
468 state: DynState,
469 meta: ActiveStateMeta,
470 transition_fn: StateTransitionFunction<DynState>,
471}
472
473impl ExecutorInner {
474 async fn run(
475 &self,
476 global_context_gen: ContextGen,
477 sm_update_rx: tokio::sync::mpsc::UnboundedReceiver<DynState>,
478 ) {
479 debug!(target: LOG_CLIENT_REACTOR, "Starting state machine executor task");
480 if let Err(err) = self
481 .run_state_machines_executor_inner(global_context_gen, sm_update_rx)
482 .await
483 {
484 warn!(
485 target: LOG_CLIENT_REACTOR,
486 err = %err.fmt_compact_anyhow(),
487 "An unexpected error occurred during a state transition"
488 );
489 }
490 }
491
492 async fn get_transition_for(
493 &self,
494 state: &DynState,
495 meta: ActiveStateMeta,
496 global_context_gen: &ContextGen,
497 ) -> Vec<BoxFuture<'static, TransitionForActiveState>> {
498 let module_instance = state.module_instance_id();
499 let context = &self
500 .module_contexts
501 .get(&module_instance)
502 .expect("Unknown module");
503 let transitions = state
504 .transitions(
505 context,
506 &global_context_gen(module_instance, state.operation_id()),
507 )
508 .into_iter()
509 .map(|transition| {
510 let state = state.clone();
511 let f: BoxFuture<TransitionForActiveState> = Box::pin(async move {
512 let StateTransition {
513 trigger,
514 transition,
515 } = transition;
516 TransitionForActiveState {
517 outcome: trigger.await,
518 state,
519 transition_fn: transition,
520 meta,
521 }
522 });
523 f
524 })
525 .collect::<Vec<_>>();
526 if transitions.is_empty() {
527 warn!(
531 target: LOG_CLIENT_REACTOR,
532 module_id = module_instance, "A terminal state where only active states are expected. Please report this bug upstream."
533 );
534 self.db
535 .autocommit::<_, _, anyhow::Error>(
536 |dbtx, _| {
537 Box::pin(async {
538 let k = InactiveStateKey::from_state(state.clone());
539 let v = ActiveStateMeta::default().into_inactive();
540 dbtx.remove_entry(&ActiveStateKeyDb(ActiveStateKey::from_state(
541 state.clone(),
542 )))
543 .await;
544 dbtx.insert_entry(&InactiveStateKeyDb(k), &v).await;
545 Ok(())
546 })
547 },
548 None,
549 )
550 .await
551 .expect("Autocommit here can't fail");
552 }
553
554 transitions
555 }
556
557 async fn run_state_machines_executor_inner(
558 &self,
559 global_context_gen: ContextGen,
560 mut sm_update_rx: tokio::sync::mpsc::UnboundedReceiver<DynState>,
561 ) -> anyhow::Result<()> {
562 enum ExecutorLoopEvent {
565 New { state: DynState },
568 Triggered(TransitionForActiveState),
571 Invalid { state: DynState },
573 Completed {
575 state: DynState,
576 outcome: ActiveOrInactiveState,
577 },
578 Disconnected,
580 }
581
582 let active_states = self.get_active_states().await;
583 debug!(
584 target: LOG_CLIENT_REACTOR,
585 total = active_states.len(),
586 "Starting active state machines",
587 );
588 for (state, meta) in active_states {
589 trace!(target: LOG_CLIENT_REACTOR, ?state, ?meta, "Starting active state");
590
591 let age = fedimint_core::time::now()
592 .duration_since(meta.created_at)
593 .unwrap_or_default();
594 if age > Duration::from_secs(7 * 24 * 3600) {
595 warn!(
596 target: LOG_CLIENT_REACTOR,
597 operation_id = %state.operation_id().fmt_short(),
598 module_instance = %state.module_instance_id(),
599 age_days = age.as_secs() / 86400,
600 "Active state machine has been running for over a week, possibly stuck",
601 );
602 }
603 self.sm_update_tx
604 .send(state)
605 .expect("Must be able to send state machine to own opened channel");
606 }
607
608 let mut currently_running_sms = HashSet::<DynState>::new();
611 let mut futures: FuturesUnordered<BoxFuture<'_, ExecutorLoopEvent>> =
619 FuturesUnordered::new();
620
621 loop {
622 let event = tokio::select! {
623 new = sm_update_rx.recv() => {
624 match new { Some(new) => {
625 ExecutorLoopEvent::New {
626 state: new,
627 }
628 } _ => {
629 ExecutorLoopEvent::Disconnected
630 }}
631 },
632
633 event = futures.next(), if !futures.is_empty() => event.expect("we only .next() if there are pending futures"),
634 };
635
636 match event {
639 ExecutorLoopEvent::New { state } => {
640 if currently_running_sms.contains(&state) {
641 warn!(target: LOG_CLIENT_REACTOR, operation_id = %state.operation_id().fmt_short(), "Received a state machine that is already running. Ignoring");
642 continue;
643 }
644 currently_running_sms.insert(state.clone());
645 let futures_len = futures.len();
646 let global_context_gen = &global_context_gen;
647 trace!(target: LOG_CLIENT_REACTOR, state = ?state, "Started new active state machine, details.");
648 futures.push(Box::pin(async move {
649 let Some(meta) = self.get_active_state(&state).await else {
650 warn!(target: LOG_CLIENT_REACTOR, operation_id = %state.operation_id().fmt_short(), "Couldn't look up received state machine. Ignoring.");
651 return ExecutorLoopEvent::Invalid { state: state.clone() };
652 };
653
654 let transitions = self
655 .get_transition_for(&state, meta, global_context_gen)
656 .await;
657 if transitions.is_empty() {
658 warn!(target: LOG_CLIENT_REACTOR, operation_id = %state.operation_id().fmt_short(), "Received an active state that doesn't produce any transitions. Ignoring.");
659 return ExecutorLoopEvent::Invalid { state: state.clone() };
660 }
661 let transitions_num = transitions.len();
662
663 debug!(target: LOG_CLIENT_REACTOR, operation_id = %state.operation_id().fmt_short(), total = futures_len + 1, transitions_num, "New active state machine.");
664
665 let (first_completed_result, _index, _unused_transitions) =
666 select_all(transitions).await;
667 ExecutorLoopEvent::Triggered(first_completed_result)
668 }));
669 }
670 ExecutorLoopEvent::Triggered(TransitionForActiveState {
671 outcome,
672 state,
673 meta,
674 transition_fn,
675 }) => {
676 debug!(
677 target: LOG_CLIENT_REACTOR,
678 operation_id = %state.operation_id().fmt_short(),
679 "Triggered state transition",
680 );
681 let span = tracing::debug_span!(
682 target: LOG_CLIENT_REACTOR,
683 "sm_transition",
684 operation_id = %state.operation_id().fmt_short()
685 );
686 futures.push({
692 let sm_update_tx = self.sm_update_tx.clone();
693 let db = self.db.clone();
694 let notifier = self.notifier.clone();
695 let module_contexts = self.module_contexts.clone();
696 let global_context_gen = global_context_gen.clone();
697 Box::pin(
698 async move {
699 debug!(
700 target: LOG_CLIENT_REACTOR,
701 "Executing state transition",
702 );
703 trace!(
704 target: LOG_CLIENT_REACTOR,
705 ?state,
706 outcome = ?AbbreviateJson(&outcome),
707 "Executing state transition (details)",
708 );
709
710 let module_contexts = &module_contexts;
711 let global_context_gen = &global_context_gen;
712
713 let outcome = db
714 .autocommit::<'_, '_, _, _, Infallible>(
715 |dbtx, _| {
716 let state = state.clone();
717 let state_module_instance_id = state.module_instance_id();
718 let transition_fn = transition_fn.clone();
719 let transition_outcome = outcome.clone();
720 Box::pin(async move {
721 let new_state = transition_fn(
722 &mut ClientSMDatabaseTransaction::new(
723 &mut dbtx.to_ref(),
724 state.module_instance_id(),
725 ),
726 transition_outcome.clone(),
727 state.clone(),
728 )
729 .await;
730 dbtx.remove_entry(&ActiveStateKeyDb(ActiveStateKey::from_state(
731 state.clone(),
732 )))
733 .await;
734 dbtx.insert_entry(
735 &InactiveStateKeyDb(InactiveStateKey::from_state(state.clone())),
736 &meta.into_inactive(),
737 )
738 .await;
739
740 let context = &module_contexts
741 .get(&state.module_instance_id())
742 .expect("Unknown module");
743
744 let operation_id = state.operation_id();
745 let global_context = global_context_gen(
746 state.module_instance_id(),
747 operation_id,
748 );
749
750 let is_terminal = new_state.is_terminal(context, &global_context);
751
752 self.log_event_dbtx(dbtx,
753 StateMachineUpdated{
754 started: false,
755 operation_id,
756 module_id: state_module_instance_id,
757 terminal: is_terminal,
758 }
759 ).await;
760
761 if is_terminal {
762 let k = InactiveStateKey::from_state(
763 new_state.clone(),
764 );
765 let v = ActiveStateMeta::default().into_inactive();
766 dbtx.insert_entry(&InactiveStateKeyDb(k), &v).await;
767 Ok(ActiveOrInactiveState::Inactive {
768 dyn_state: new_state,
769 })
770 } else {
771 let k = ActiveStateKey::from_state(
772 new_state.clone(),
773 );
774 let v = ActiveStateMeta::default();
775 dbtx.insert_entry(&ActiveStateKeyDb(k), &v).await;
776 Ok(ActiveOrInactiveState::Active {
777 dyn_state: new_state,
778 meta: v,
779 })
780 }
781 })
782 },
783 None,
784 )
785 .await
786 .expect("autocommit should keep trying to commit (max_attempt: None) and body doesn't return errors");
787
788 debug!(
789 target: LOG_CLIENT_REACTOR,
790 terminal = !outcome.is_active(),
791 ?outcome,
792 "State transition complete",
793 );
794
795 match &outcome {
796 ActiveOrInactiveState::Active { dyn_state, meta: _ } => {
797 sm_update_tx
798 .send(dyn_state.clone())
799 .expect("can't fail: we are the receiving end");
800 notifier.notify(dyn_state.clone());
801 }
802 ActiveOrInactiveState::Inactive { dyn_state } => {
803 notifier.notify(dyn_state.clone());
804 }
805 }
806 ExecutorLoopEvent::Completed { state, outcome }
807 }
808 .instrument(span),
809 )
810 });
811 }
812 ExecutorLoopEvent::Invalid { state } => {
813 trace!(
814 target: LOG_CLIENT_REACTOR,
815 operation_id = %state.operation_id().fmt_short(), total = futures.len(),
816 "State invalid"
817 );
818 assert!(
819 currently_running_sms.remove(&state),
820 "State must have been recorded"
821 );
822 }
823
824 ExecutorLoopEvent::Completed { state, outcome } => {
825 assert!(
826 currently_running_sms.remove(&state),
827 "State must have been recorded"
828 );
829 debug!(
830 target: LOG_CLIENT_REACTOR,
831 operation_id = %state.operation_id().fmt_short(),
832 outcome_active = outcome.is_active(),
833 total = futures.len(),
834 "State transition complete"
835 );
836 trace!(
837 target: LOG_CLIENT_REACTOR,
838 ?outcome,
839 operation_id = %state.operation_id().fmt_short(), total = futures.len(),
840 "State transition complete"
841 );
842 }
843 ExecutorLoopEvent::Disconnected => {
844 break;
845 }
846 }
847 }
848
849 info!(target: LOG_CLIENT_REACTOR, "Terminated.");
850 Ok(())
851 }
852
853 async fn get_active_states(&self) -> Vec<(DynState, ActiveStateMeta)> {
854 self.db
855 .begin_transaction_nc()
856 .await
857 .find_by_prefix(&ActiveStateKeyPrefix)
858 .await
859 .filter(|(state, _)| {
861 future::ready(
862 self.module_contexts
863 .contains_key(&state.0.state.module_instance_id()),
864 )
865 })
866 .map(|(state, meta)| (state.0.state, meta))
867 .collect::<Vec<_>>()
868 .await
869 }
870
871 async fn get_active_state(&self, state: &DynState) -> Option<ActiveStateMeta> {
872 if !self
874 .module_contexts
875 .contains_key(&state.module_instance_id())
876 {
877 return None;
878 }
879 self.db
880 .begin_transaction_nc()
881 .await
882 .get_value(&ActiveStateKeyDb(ActiveStateKey::from_state(state.clone())))
883 .await
884 }
885
886 async fn get_inactive_states(&self) -> Vec<(DynState, InactiveStateMeta)> {
887 self.db
888 .begin_transaction_nc()
889 .await
890 .find_by_prefix(&InactiveStateKeyPrefix)
891 .await
892 .filter(|(state, _)| {
894 future::ready(
895 self.module_contexts
896 .contains_key(&state.0.state.module_instance_id()),
897 )
898 })
899 .map(|(state, meta)| (state.0.state, meta))
900 .collect::<Vec<_>>()
901 .await
902 }
903
904 pub async fn log_event_dbtx<E, Cap>(&self, dbtx: &mut DatabaseTransaction<'_, Cap>, event: E)
905 where
906 E: Event + Send,
907 Cap: Send,
908 {
909 dbtx.log_event(self.log_ordering_wakeup_tx.clone(), None, event)
910 .await;
911 }
912}
913
914impl ExecutorInner {
915 fn stop_executor(&self) -> Option<()> {
917 let mut state = self
921 .state
922 .write()
923 .unwrap_or_else(std::sync::PoisonError::into_inner);
924
925 state.stop()
926 }
927}
928
929impl Debug for ExecutorInner {
930 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
931 writeln!(f, "ExecutorInner {{}}")
932 }
933}
934
935impl ExecutorBuilder {
936 pub fn with_module<C>(&mut self, instance_id: ModuleInstanceId, context: C)
939 where
940 C: IntoDynInstance<DynType = DynContext>,
941 {
942 self.with_module_dyn(context.into_dyn(instance_id));
943 }
944
945 pub fn with_module_dyn(&mut self, context: DynContext) {
948 self.valid_module_ids.insert(context.module_instance_id());
949
950 if self
951 .module_contexts
952 .insert(context.module_instance_id(), context)
953 .is_some()
954 {
955 panic!("Tried to add two modules with the same instance id!");
956 }
957 }
958
959 pub fn with_valid_module_id(&mut self, module_id: ModuleInstanceId) {
963 self.valid_module_ids.insert(module_id);
964 }
965
966 pub fn build(
970 self,
971 db: Database,
972 notifier: Notifier,
973 client_task_group: TaskGroup,
974 log_ordering_wakeup_tx: watch::Sender<()>,
975 ) -> Executor {
976 let (sm_update_tx, sm_update_rx) = tokio::sync::mpsc::unbounded_channel();
977
978 let inner = Arc::new(ExecutorInner {
979 db,
980 log_ordering_wakeup_tx,
981 state: std::sync::RwLock::new(ExecutorState::Unstarted { sm_update_rx }),
982 module_contexts: self.module_contexts,
983 valid_module_ids: self.valid_module_ids,
984 notifier,
985 sm_update_tx,
986 client_task_group,
987 });
988
989 debug!(
990 target: LOG_CLIENT_REACTOR,
991 instances = ?inner.module_contexts.keys().copied().collect::<Vec<_>>(),
992 "Initialized state machine executor with module instances"
993 );
994 Executor { inner }
995 }
996}
997#[derive(Debug)]
998pub struct ActiveOperationStateKeyPrefix {
999 pub operation_id: OperationId,
1000}
1001
1002impl Encodable for ActiveOperationStateKeyPrefix {
1003 fn consensus_encode<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
1004 self.operation_id.consensus_encode(writer)
1005 }
1006}
1007
1008impl ::fedimint_core::db::DatabaseLookup for ActiveOperationStateKeyPrefix {
1009 type Record = ActiveStateKeyDb;
1010}
1011
1012#[derive(Debug)]
1013pub(crate) struct ActiveModuleOperationStateKeyPrefix {
1014 pub operation_id: OperationId,
1015 pub module_instance: ModuleInstanceId,
1016}
1017
1018impl Encodable for ActiveModuleOperationStateKeyPrefix {
1019 fn consensus_encode<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
1020 self.operation_id.consensus_encode(writer)?;
1021 self.module_instance.consensus_encode(writer)?;
1022 Ok(())
1023 }
1024}
1025
1026impl ::fedimint_core::db::DatabaseLookup for ActiveModuleOperationStateKeyPrefix {
1027 type Record = ActiveStateKeyDb;
1028}
1029
1030#[derive(Debug)]
1031pub struct ActiveStateKeyPrefix;
1032
1033impl Encodable for ActiveStateKeyPrefix {
1034 fn consensus_encode<W: Write>(&self, _writer: &mut W) -> Result<(), Error> {
1035 Ok(())
1036 }
1037}
1038
1039#[derive(Encodable, Decodable, Debug)]
1040pub struct ActiveStateKeyDb(pub fedimint_client_module::sm::executor::ActiveStateKey);
1041
1042impl ::fedimint_core::db::DatabaseRecord for ActiveStateKeyDb {
1043 const DB_PREFIX: u8 = ExecutorDbPrefixes::ActiveStates as u8;
1044 const NOTIFY_ON_MODIFY: bool = true;
1045 type Key = Self;
1046 type Value = ActiveStateMeta;
1047}
1048
1049impl DatabaseKeyWithNotify for ActiveStateKeyDb {}
1050
1051impl ::fedimint_core::db::DatabaseLookup for ActiveStateKeyPrefix {
1052 type Record = ActiveStateKeyDb;
1053}
1054
1055#[derive(Debug, Encodable, Decodable)]
1056pub struct ActiveStateKeyPrefixBytes;
1057
1058impl ::fedimint_core::db::DatabaseRecord for ActiveStateKeyBytes {
1059 const DB_PREFIX: u8 = ExecutorDbPrefixes::ActiveStates as u8;
1060 const NOTIFY_ON_MODIFY: bool = false;
1061 type Key = Self;
1062 type Value = ActiveStateMeta;
1063}
1064
1065impl ::fedimint_core::db::DatabaseLookup for ActiveStateKeyPrefixBytes {
1066 type Record = ActiveStateKeyBytes;
1067}
1068
1069#[derive(Encodable, Decodable, Debug)]
1070pub struct InactiveStateKeyDb(pub fedimint_client_module::sm::executor::InactiveStateKey);
1071
1072#[derive(Debug)]
1073pub struct InactiveStateKeyBytes {
1074 pub operation_id: OperationId,
1075 pub module_instance_id: ModuleInstanceId,
1076 pub state: Vec<u8>,
1077}
1078
1079impl Encodable for InactiveStateKeyBytes {
1080 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
1081 self.operation_id.consensus_encode(writer)?;
1082 writer.write_all(self.state.as_slice())?;
1083 Ok(())
1084 }
1085}
1086
1087impl Decodable for InactiveStateKeyBytes {
1088 fn consensus_decode_partial<R: std::io::Read>(
1089 reader: &mut R,
1090 modules: &ModuleDecoderRegistry,
1091 ) -> Result<Self, DecodeError> {
1092 let operation_id = OperationId::consensus_decode_partial(reader, modules)?;
1093 let module_instance_id = ModuleInstanceId::consensus_decode_partial(reader, modules)?;
1094 let mut bytes = Vec::new();
1095 reader.read_to_end(&mut bytes)?;
1096
1097 let mut instance_bytes = ModuleInstanceId::consensus_encode_to_vec(&module_instance_id);
1098 instance_bytes.append(&mut bytes);
1099
1100 Ok(InactiveStateKeyBytes {
1101 operation_id,
1102 module_instance_id,
1103 state: instance_bytes,
1104 })
1105 }
1106}
1107
1108#[derive(Debug)]
1109pub struct InactiveOperationStateKeyPrefix {
1110 pub operation_id: OperationId,
1111}
1112
1113impl Encodable for InactiveOperationStateKeyPrefix {
1114 fn consensus_encode<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
1115 self.operation_id.consensus_encode(writer)
1116 }
1117}
1118
1119impl ::fedimint_core::db::DatabaseLookup for InactiveOperationStateKeyPrefix {
1120 type Record = InactiveStateKeyDb;
1121}
1122
1123#[derive(Debug)]
1124pub(crate) struct InactiveModuleOperationStateKeyPrefix {
1125 pub operation_id: OperationId,
1126 pub module_instance: ModuleInstanceId,
1127}
1128
1129impl Encodable for InactiveModuleOperationStateKeyPrefix {
1130 fn consensus_encode<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
1131 self.operation_id.consensus_encode(writer)?;
1132 self.module_instance.consensus_encode(writer)?;
1133 Ok(())
1134 }
1135}
1136
1137impl ::fedimint_core::db::DatabaseLookup for InactiveModuleOperationStateKeyPrefix {
1138 type Record = InactiveStateKeyDb;
1139}
1140
1141#[derive(Debug, Clone)]
1142pub struct InactiveStateKeyPrefix;
1143
1144impl Encodable for InactiveStateKeyPrefix {
1145 fn consensus_encode<W: Write>(&self, _writer: &mut W) -> Result<(), Error> {
1146 Ok(())
1147 }
1148}
1149
1150#[derive(Debug, Encodable, Decodable)]
1151pub struct InactiveStateKeyPrefixBytes;
1152
1153impl ::fedimint_core::db::DatabaseRecord for InactiveStateKeyBytes {
1154 const DB_PREFIX: u8 = ExecutorDbPrefixes::InactiveStates as u8;
1155 const NOTIFY_ON_MODIFY: bool = false;
1156 type Key = Self;
1157 type Value = InactiveStateMeta;
1158}
1159
1160impl ::fedimint_core::db::DatabaseLookup for InactiveStateKeyPrefixBytes {
1161 type Record = InactiveStateKeyBytes;
1162}
1163
1164#[derive(Debug)]
1165pub struct ActiveStateKeyBytes {
1166 pub operation_id: OperationId,
1167 pub module_instance_id: ModuleInstanceId,
1168 pub state: Vec<u8>,
1169}
1170
1171impl Encodable for ActiveStateKeyBytes {
1172 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
1173 self.operation_id.consensus_encode(writer)?;
1174 writer.write_all(self.state.as_slice())?;
1175 Ok(())
1176 }
1177}
1178
1179impl Decodable for ActiveStateKeyBytes {
1180 fn consensus_decode_partial<R: std::io::Read>(
1181 reader: &mut R,
1182 modules: &ModuleDecoderRegistry,
1183 ) -> Result<Self, DecodeError> {
1184 let operation_id = OperationId::consensus_decode_partial(reader, modules)?;
1185 let module_instance_id = ModuleInstanceId::consensus_decode_partial(reader, modules)?;
1186 let mut bytes = Vec::new();
1187 reader.read_to_end(&mut bytes)?;
1188
1189 let mut instance_bytes = ModuleInstanceId::consensus_encode_to_vec(&module_instance_id);
1190 instance_bytes.append(&mut bytes);
1191
1192 Ok(ActiveStateKeyBytes {
1193 operation_id,
1194 module_instance_id,
1195 state: instance_bytes,
1196 })
1197 }
1198}
1199impl ::fedimint_core::db::DatabaseRecord for InactiveStateKeyDb {
1200 const DB_PREFIX: u8 = ExecutorDbPrefixes::InactiveStates as u8;
1201 const NOTIFY_ON_MODIFY: bool = true;
1202 type Key = Self;
1203 type Value = InactiveStateMeta;
1204}
1205
1206impl DatabaseKeyWithNotify for InactiveStateKeyDb {}
1207
1208impl ::fedimint_core::db::DatabaseLookup for InactiveStateKeyPrefix {
1209 type Record = InactiveStateKeyDb;
1210}
1211
1212#[derive(Debug)]
1213enum ActiveOrInactiveState {
1214 Active {
1215 dyn_state: DynState,
1216 #[allow(dead_code)] meta: ActiveStateMeta,
1218 },
1219 Inactive {
1220 dyn_state: DynState,
1221 },
1222}
1223
1224impl ActiveOrInactiveState {
1225 fn is_active(&self) -> bool {
1226 match self {
1227 ActiveOrInactiveState::Active { .. } => true,
1228 ActiveOrInactiveState::Inactive { .. } => false,
1229 }
1230 }
1231}
1232
1233#[apply(async_trait_maybe_send!)]
1234impl IExecutor for Executor {
1235 async fn get_active_states(&self) -> Vec<(DynState, ActiveStateMeta)> {
1236 Self::get_active_states(self).await
1237 }
1238
1239 async fn add_state_machines_dbtx(
1240 &self,
1241 dbtx: &mut DatabaseTransaction<'_>,
1242 states: Vec<DynState>,
1243 ) -> AddStateMachinesResult {
1244 Self::add_state_machines_dbtx(self, dbtx, states).await
1245 }
1246}
1247
1248#[cfg(test)]
1249mod tests;