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