Skip to main content

restate_sdk_shared_core/vm/
mod.rs

1use crate::headers::HeaderMap;
2use crate::service_protocol::messages::{
3    attach_invocation_command_message, complete_awakeable_command_message,
4    complete_promise_command_message, get_invocation_output_command_message,
5    output_command_message, send_signal_command_message, AttachInvocationCommandMessage,
6    CallCommandMessage, CompleteAwakeableCommandMessage, CompletePromiseCommandMessage,
7    ErrorBehavior, GetInvocationOutputCommandMessage, GetPromiseCommandMessage,
8    IdempotentRequestTarget, OneWayCallCommandMessage, OutputCommandMessage,
9    PeekPromiseCommandMessage, SendSignalCommandMessage, SleepCommandMessage, WorkflowTarget,
10};
11use crate::service_protocol::{Decoder, NotificationId, RawMessage, Version, CANCEL_SIGNAL_ID};
12use crate::vm::errors::{
13    ClosedError, OutOfBoundsDuration, UnexpectedStateError, UnsupportedFeatureForNegotiatedVersion,
14    EMPTY_IDEMPOTENCY_KEY, EMPTY_LIMIT_KEY, EMPTY_SCOPE, SUSPENDED,
15};
16use crate::vm::run_state::RunState;
17use crate::vm::transitions::*;
18use crate::{
19    AttachInvocationTarget, AwaitResponse, AwakeableHandle, CallHandle, CommandRelationship, Error,
20    Header, ImplicitCancellationOption, Input, NonDeterministicChecksOption, NonEmptyValue,
21    NotificationHandle, PayloadOptions, ResponseHead, RetryPolicy, RunExitResult, RunHandle,
22    SendHandle, Target, TerminalFailure, UnresolvedFuture, VMOptions, VMResult, Value,
23    CANCEL_NOTIFICATION_HANDLE,
24};
25use async_results_state::AsyncResultsState;
26use base64::engine::{DecodePaddingMode, GeneralPurpose, GeneralPurposeConfig};
27use base64::{alphabet, Engine};
28use bytes::{Buf, BufMut, Bytes, BytesMut};
29use context::{Context, EagerState, Output};
30use std::borrow::Cow;
31use std::collections::{HashMap, VecDeque};
32use std::mem::size_of;
33use std::time::Duration;
34use std::{fmt, mem};
35use strum::IntoStaticStr;
36use tracing::{debug, enabled, instrument, Level};
37
38mod async_results_state;
39mod context;
40pub(crate) mod errors;
41mod run_state;
42mod transitions;
43
44const CONTENT_TYPE: &str = "content-type";
45
46#[derive(Debug, IntoStaticStr)]
47pub(crate) enum State {
48    WaitingStart,
49    WaitingReplayEntries {
50        received_entries: u32,
51        commands: VecDeque<RawMessage>,
52        async_results: AsyncResultsState,
53        eager_state: EagerState,
54    },
55    Replaying {
56        commands: VecDeque<RawMessage>,
57        run_state: RunState,
58        async_results: AsyncResultsState,
59        eager_state: EagerState,
60    },
61    Processing {
62        processing_first_entry: bool,
63        run_state: RunState,
64        async_results: AsyncResultsState,
65        eager_state: EagerState,
66    },
67    Closed,
68}
69
70impl State {
71    fn as_unexpected_state(&self, event: impl ToString) -> Error {
72        if matches!(self, State::Closed) {
73            return ClosedError::new(event.to_string()).into();
74        }
75        UnexpectedStateError::new(self.into(), event.to_string()).into()
76    }
77
78    /// Tries to transition to Processing when the condition is met, in all the other cases returns the current state.
79    #[inline]
80    fn try_transition_to_processing(self) -> Self {
81        match self {
82            State::Replaying {
83                commands,
84                async_results,
85                eager_state,
86                run_state,
87            } if commands.is_empty() => State::Processing {
88                processing_first_entry: true,
89                run_state,
90                async_results,
91                eager_state,
92            },
93            s => s,
94        }
95    }
96
97    #[inline]
98    fn eager_state_mut(&mut self) -> Option<&mut EagerState> {
99        match self {
100            State::WaitingReplayEntries { eager_state, .. }
101            | State::Replaying { eager_state, .. }
102            | State::Processing { eager_state, .. } => Some(eager_state),
103            _ => None,
104        }
105    }
106}
107
108struct TrackedInvocationId {
109    handle: NotificationHandle,
110    invocation_id: Option<String>,
111}
112
113impl TrackedInvocationId {
114    fn is_resolved(&self) -> bool {
115        self.invocation_id.is_some()
116    }
117}
118
119pub struct CoreVM {
120    options: VMOptions,
121
122    // Input decoder
123    decoder: Decoder,
124
125    // State machine
126    context: Context,
127    last_transition: Result<State, Error>,
128
129    // Implicit cancellation tracking
130    tracked_invocation_ids: Vec<TrackedInvocationId>,
131
132    // Run names, useful for debugging
133    sys_run_names: HashMap<NotificationHandle, String>,
134}
135
136impl CoreVM {
137    // Returns empty string if the invocation id is not present
138    fn debug_invocation_id(&self) -> &str {
139        if let Some(start_info) = self.context.start_info() {
140            &start_info.debug_id
141        } else {
142            ""
143        }
144    }
145
146    fn debug_state(&self) -> &'static str {
147        match &self.last_transition {
148            Ok(s) => s.into(),
149            Err(_) => "Failed",
150        }
151    }
152
153    fn verify_error_metadata_feature_support(&mut self, value: &NonEmptyValue) -> VMResult<()> {
154        if let NonEmptyValue::Failure(f) = value {
155            if !f.metadata.is_empty() {
156                self.verify_feature_support("terminal error metadata", Version::V6)?;
157            }
158        }
159        Ok(())
160    }
161
162    #[allow(dead_code)]
163    fn verify_feature_support(
164        &mut self,
165        feature: &'static str,
166        minimum_required_protocol: Version,
167    ) -> VMResult<()> {
168        if self.context.negotiated_protocol_version < minimum_required_protocol {
169            return self.do_transition(HitError(
170                UnsupportedFeatureForNegotiatedVersion::new(
171                    feature,
172                    self.context.negotiated_protocol_version,
173                    minimum_required_protocol,
174                )
175                .into(),
176            ));
177        }
178        Ok(())
179    }
180
181    fn _is_completed(&self, handle: NotificationHandle) -> bool {
182        match &self.last_transition {
183            Ok(State::Replaying { async_results, .. })
184            | Ok(State::Processing { async_results, .. }) => {
185                async_results.is_handle_completed(handle)
186            }
187            _ => false,
188        }
189    }
190
191    fn _do_progress(
192        &mut self,
193        unresolved_future: UnresolvedFuture,
194    ) -> Result<AwaitResponse, Error> {
195        match self.do_transition(DoProgress(unresolved_future)) {
196            Ok(Ok(do_progress_response)) => Ok(do_progress_response),
197            Ok(Err(_)) => Err(SUSPENDED),
198            Err(e) => Err(e),
199        }
200    }
201
202    fn is_implicit_cancellation_enabled(&self) -> bool {
203        matches!(
204            self.options.implicit_cancellation,
205            ImplicitCancellationOption::Enabled { .. }
206        )
207    }
208
209    fn is_processing(&self) -> bool {
210        matches!(&self.last_transition, Ok(State::Processing { .. }))
211    }
212}
213
214impl fmt::Debug for CoreVM {
215    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216        let mut s = f.debug_struct("CoreVM");
217        s.field("version", &self.context.negotiated_protocol_version);
218
219        if let Some(start_info) = self.context.start_info() {
220            s.field("invocation_id", &start_info.debug_id);
221        }
222
223        match &self.last_transition {
224            Ok(state) => s.field("last_transition", &<&'static str>::from(state)),
225            Err(_) => s.field("last_transition", &"Errored"),
226        };
227
228        s.field("command_index", &self.context.journal.command_index())
229            .field(
230                "notification_index",
231                &self.context.journal.notification_index(),
232            )
233            .finish()
234    }
235}
236
237// --- Bound checks
238#[allow(unused)]
239const fn is_send<T: Send>() {}
240const _: () = is_send::<CoreVM>();
241
242// Macro used for informative debug logs
243macro_rules! invocation_debug_logs {
244    ($this:expr, $($arg:tt)*) => {
245        if ($this.is_processing()) {
246            tracing::debug!($($arg)*)
247        }
248    };
249}
250
251impl super::VM for CoreVM {
252    #[instrument(level = "trace", skip(request_headers), ret)]
253    fn new(request_headers: impl HeaderMap, options: VMOptions) -> Result<Self, Error> {
254        let version = request_headers
255            .extract(CONTENT_TYPE)
256            .map_err(|e| {
257                Error::new(
258                    errors::codes::BAD_REQUEST,
259                    format!("cannot read '{CONTENT_TYPE}' header: {e:?}"),
260                )
261            })?
262            .ok_or(errors::MISSING_CONTENT_TYPE)?
263            .parse::<Version>()?;
264
265        if version < Version::minimum_supported_version()
266            || version > Version::maximum_supported_version()
267        {
268            return Err(Error::new(
269                errors::codes::UNSUPPORTED_MEDIA_TYPE,
270                format!(
271                    "Unsupported protocol version {:?}, not within [{:?} to {:?}]. \
272                    You might need to rediscover the service, check https://docs.restate.dev/references/errors/#RT0015",
273                    version,
274                    Version::minimum_supported_version(),
275                    Version::maximum_supported_version()
276                ),
277            ));
278        }
279        let non_deterministic_checks_ignore_payload_equality = matches!(
280            options.non_determinism_checks,
281            NonDeterministicChecksOption::PayloadChecksDisabled
282        );
283        let awaiting_on_policy = options.awaiting_on_policy;
284
285        Ok(Self {
286            options,
287            decoder: Decoder::new(version),
288            context: Context {
289                input_is_closed: false,
290                output: Output::new(version),
291                start_info: None,
292                journal: Default::default(),
293                non_deterministic_checks_ignore_payload_equality,
294                negotiated_protocol_version: version,
295                awaiting_on_policy,
296            },
297            last_transition: Ok(State::WaitingStart),
298            tracked_invocation_ids: vec![],
299            sys_run_names: HashMap::with_capacity(0),
300        })
301    }
302
303    #[instrument(
304        level = "trace",
305        skip(self),
306        fields(
307            restate.invocation.id = self.debug_invocation_id(),
308            restate.protocol.state = self.debug_state(),
309            restate.journal.command_index = self.context.journal.command_index(),
310            restate.protocol.version = %self.context.negotiated_protocol_version
311        ),
312        ret
313    )]
314    fn get_response_head(&self) -> ResponseHead {
315        ResponseHead {
316            status_code: 200,
317            headers: vec![Header {
318                key: Cow::Borrowed(CONTENT_TYPE),
319                value: Cow::Borrowed(self.context.negotiated_protocol_version.content_type()),
320            }],
321            version: self.context.negotiated_protocol_version,
322        }
323    }
324
325    #[instrument(
326        level = "trace",
327        skip(self),
328        fields(
329            restate.invocation.id = self.debug_invocation_id(),
330            restate.protocol.state = self.debug_state(),
331            restate.journal.command_index = self.context.journal.command_index(),
332            restate.protocol.version = %self.context.negotiated_protocol_version
333        ),
334        ret
335    )]
336    fn notify_input(&mut self, buffer: Bytes) {
337        self.decoder.push(buffer);
338        loop {
339            match self.decoder.consume_next() {
340                Ok(Some(msg)) => {
341                    if self.do_transition(NewMessage(msg)).is_err() {
342                        return;
343                    }
344                }
345                Ok(None) => {
346                    return;
347                }
348                Err(e) => {
349                    if self.do_transition(HitError(e.into())).is_err() {
350                        return;
351                    }
352                }
353            }
354        }
355    }
356
357    #[instrument(
358        level = "trace",
359        skip(self),
360        fields(
361            restate.invocation.id = self.debug_invocation_id(),
362            restate.protocol.state = self.debug_state(),
363            restate.journal.command_index = self.context.journal.command_index(),
364            restate.protocol.version = %self.context.negotiated_protocol_version
365        ),
366        ret
367    )]
368    fn notify_input_closed(&mut self) {
369        self.context.input_is_closed = true;
370        let _ = self.do_transition(NotifyInputClosed);
371    }
372
373    #[instrument(
374        level = "trace",
375        skip(self),
376        fields(
377            restate.invocation.id = self.debug_invocation_id(),
378            restate.protocol.state = self.debug_state(),
379            restate.journal.command_index = self.context.journal.command_index(),
380            restate.protocol.version = %self.context.negotiated_protocol_version
381        ),
382        ret
383    )]
384    fn notify_error(
385        &mut self,
386        mut error: Error,
387        command_relationship: Option<CommandRelationship>,
388    ) {
389        if error.behavior != ErrorBehavior::Retry
390            && self
391                .verify_feature_support("error behavior", Version::V7)
392                .is_err()
393        {
394            return;
395        }
396
397        if let Some(command_relationship) = command_relationship {
398            error = error.with_related_command_metadata(
399                self.context
400                    .journal
401                    .resolve_related_command(command_relationship),
402            );
403        }
404
405        let _ = self.do_transition(HitError(error));
406    }
407
408    #[instrument(
409        level = "trace",
410        skip(self),
411        fields(
412            restate.invocation.id = self.debug_invocation_id(),
413            restate.protocol.state = self.debug_state(),
414            restate.journal.command_index = self.context.journal.command_index(),
415            restate.protocol.version = %self.context.negotiated_protocol_version
416        ),
417        ret
418    )]
419    fn take_output(&mut self) -> Bytes {
420        self.context
421            .output
422            .buffer
423            .copy_to_bytes(self.context.output.buffer.remaining())
424    }
425
426    #[instrument(
427        level = "trace",
428        skip(self),
429        fields(
430            restate.invocation.id = self.debug_invocation_id(),
431            restate.protocol.state = self.debug_state(),
432            restate.journal.command_index = self.context.journal.command_index(),
433            restate.protocol.version = %self.context.negotiated_protocol_version
434        ),
435        ret
436    )]
437    fn is_ready_to_execute(&self) -> Result<bool, Error> {
438        match &self.last_transition {
439            Ok(State::WaitingStart) | Ok(State::WaitingReplayEntries { .. }) => Ok(false),
440            Ok(State::Processing { .. }) | Ok(State::Replaying { .. }) => Ok(true),
441            Ok(s) => Err(s.as_unexpected_state("IsReadyToExecute")),
442            Err(e) => Err(e.clone()),
443        }
444    }
445
446    #[instrument(
447        level = "trace",
448        skip(self),
449        fields(
450            restate.invocation.id = self.debug_invocation_id(),
451            restate.protocol.state = self.debug_state(),
452            restate.journal.command_index = self.context.journal.command_index(),
453            restate.protocol.version = %self.context.negotiated_protocol_version
454        ),
455        ret
456    )]
457    fn is_completed(&self, handle: NotificationHandle) -> bool {
458        self._is_completed(handle)
459    }
460
461    #[instrument(
462        level = "trace",
463        skip(self),
464        fields(
465            restate.invocation.id = self.debug_invocation_id(),
466            restate.protocol.state = self.debug_state(),
467            restate.journal.command_index = self.context.journal.command_index(),
468            restate.protocol.version = %self.context.negotiated_protocol_version
469        ),
470        ret
471    )]
472    fn do_await(&mut self, unresolved_future: UnresolvedFuture) -> VMResult<AwaitResponse> {
473        if self.is_implicit_cancellation_enabled() {
474            // We want the runtime to wake us up in case cancel notification comes in.
475            let unresolved_future_with_cancellation = UnresolvedFuture::FirstCompleted(vec![
476                unresolved_future,
477                UnresolvedFuture::Single(CANCEL_NOTIFICATION_HANDLE),
478            ]);
479
480            match self._do_progress(unresolved_future_with_cancellation) {
481                Ok(AwaitResponse::AnyCompleted) => {
482                    // If it's cancel signal, then let's go on with the cancellation logic
483                    if self._is_completed(CANCEL_NOTIFICATION_HANDLE) {
484                        // Loop once over the tracked invocation ids to resolve the unresolved ones
485                        for i in 0..self.tracked_invocation_ids.len() {
486                            if self.tracked_invocation_ids[i].is_resolved() {
487                                continue;
488                            }
489
490                            let handle = self.tracked_invocation_ids[i].handle;
491
492                            // Try to resolve it
493                            match self._do_progress(UnresolvedFuture::Single(handle)) {
494                                Ok(AwaitResponse::AnyCompleted) => {
495                                    let invocation_id = match self.do_transition(CopyNotification(handle)) {
496                                        Ok(Ok(Some(Value::InvocationId(invocation_id)))) => Ok(invocation_id),
497                                        Ok(Err(_)) => Err(SUSPENDED),
498                                        _ => panic!("Unexpected variant! If the id handle is completed, it must be an invocation id handle!")
499                                    }?;
500
501                                    // This handle is resolved
502                                    self.tracked_invocation_ids[i].invocation_id =
503                                        Some(invocation_id);
504                                }
505                                res => return res,
506                            }
507                        }
508
509                        // Now we got all the invocation IDs, let's cancel!
510                        for tracked_invocation_id in mem::take(&mut self.tracked_invocation_ids) {
511                            self.sys_cancel_invocation(
512                                tracked_invocation_id
513                                    .invocation_id
514                                    .expect("We resolved before all the invocation ids"),
515                            )?;
516                        }
517
518                        // Flip the cancellation
519                        let _ = self.take_notification(CANCEL_NOTIFICATION_HANDLE);
520
521                        // Done
522                        Ok(AwaitResponse::CancelSignalReceived)
523                    } else {
524                        Ok(AwaitResponse::AnyCompleted)
525                    }
526                }
527                res => res,
528            }
529        } else {
530            self._do_progress(unresolved_future)
531        }
532    }
533
534    #[instrument(
535        level = "trace",
536        skip(self),
537        fields(
538            restate.invocation.id = self.debug_invocation_id(),
539            restate.protocol.state = self.debug_state(),
540            restate.journal.command_index = self.context.journal.command_index(),
541            restate.protocol.version = %self.context.negotiated_protocol_version
542        ),
543        ret
544    )]
545    fn take_notification(&mut self, handle: NotificationHandle) -> VMResult<Option<Value>> {
546        match self.do_transition(TakeNotification(handle)) {
547            Ok(Ok(Some(value))) => {
548                if self.is_implicit_cancellation_enabled() {
549                    // Let's check if that's one of the tracked invocation ids
550                    // We can do binary search here because we assume tracked_invocation_ids is ordered, as handles are incremental numbers
551                    if let Ok(found) = self
552                        .tracked_invocation_ids
553                        .binary_search_by(|tracked| tracked.handle.cmp(&handle))
554                    {
555                        let Value::InvocationId(invocation_id) = &value else {
556                            panic!("Expecting an invocation id here, but got {value:?}");
557                        };
558                        // Keep track of this invocation id
559                        self.tracked_invocation_ids
560                            .get_mut(found)
561                            .unwrap()
562                            .invocation_id = Some(invocation_id.clone());
563                    }
564                }
565
566                Ok(Some(value))
567            }
568            Ok(Ok(None)) => Ok(None),
569            Ok(Err(_)) => Err(SUSPENDED),
570            Err(e) => Err(e),
571        }
572    }
573
574    #[instrument(
575        level = "trace",
576        skip(self),
577        fields(
578            restate.invocation.id = self.debug_invocation_id(),
579            restate.protocol.state = self.debug_state(),
580            restate.journal.command_index = self.context.journal.command_index(),
581            restate.protocol.version = %self.context.negotiated_protocol_version
582        ),
583        ret
584    )]
585    fn sys_input(&mut self) -> Result<Input, Error> {
586        self.do_transition(SysInput)
587    }
588
589    #[instrument(
590        level = "trace",
591        skip(self),
592        fields(
593            restate.invocation.id = self.debug_invocation_id(),
594            restate.protocol.state = self.debug_state(),
595            restate.journal.command_index = self.context.journal.command_index(),
596            restate.protocol.version = %self.context.negotiated_protocol_version
597        ),
598        ret
599    )]
600    fn sys_state_get(
601        &mut self,
602        key: String,
603        options: PayloadOptions,
604    ) -> Result<NotificationHandle, Error> {
605        invocation_debug_logs!(self, "Executing 'Get state {key}'");
606        self.do_transition(SysStateGet(key, options))
607    }
608
609    #[instrument(
610        level = "trace",
611        skip(self),
612        fields(
613            restate.invocation.id = self.debug_invocation_id(),
614            restate.protocol.state = self.debug_state(),
615            restate.journal.command_index = self.context.journal.command_index(),
616            restate.protocol.version = %self.context.negotiated_protocol_version
617        ),
618        ret
619    )]
620    fn sys_state_get_keys(&mut self) -> VMResult<NotificationHandle> {
621        invocation_debug_logs!(self, "Executing 'Get state keys'");
622        self.do_transition(SysStateGetKeys)
623    }
624
625    #[instrument(
626        level = "trace",
627        skip(self, value),
628        fields(
629            restate.invocation.id = self.debug_invocation_id(),
630            restate.protocol.state = self.debug_state(),
631            restate.journal.command_index = self.context.journal.command_index(),
632            restate.protocol.version = %self.context.negotiated_protocol_version
633        ),
634        ret
635    )]
636    fn sys_state_set(
637        &mut self,
638        key: String,
639        value: Bytes,
640        options: PayloadOptions,
641    ) -> VMResult<()> {
642        invocation_debug_logs!(self, "Executing 'Set state {key}'");
643        self.do_transition(SysStateSet(key, value, options))
644    }
645
646    #[instrument(
647        level = "trace",
648        skip(self),
649        fields(
650            restate.invocation.id = self.debug_invocation_id(),
651            restate.protocol.state = self.debug_state(),
652            restate.journal.command_index = self.context.journal.command_index(),
653            restate.protocol.version = %self.context.negotiated_protocol_version
654        ),
655        ret
656    )]
657    fn sys_state_clear(&mut self, key: String) -> Result<(), Error> {
658        invocation_debug_logs!(self, "Executing 'Clear state {key}'");
659        self.do_transition(SysStateClear(key))
660    }
661
662    #[instrument(
663        level = "trace",
664        skip(self),
665        fields(
666            restate.invocation.id = self.debug_invocation_id(),
667            restate.protocol.state = self.debug_state(),
668            restate.journal.command_index = self.context.journal.command_index(),
669            restate.protocol.version = %self.context.negotiated_protocol_version
670        ),
671        ret
672    )]
673    fn sys_state_clear_all(&mut self) -> Result<(), Error> {
674        invocation_debug_logs!(self, "Executing 'Clear all state'");
675        self.do_transition(SysStateClearAll)
676    }
677
678    #[instrument(
679        level = "trace",
680        skip(self),
681        fields(
682            restate.invocation.id = self.debug_invocation_id(),
683            restate.protocol.state = self.debug_state(),
684            restate.journal.command_index = self.context.journal.command_index(),
685            restate.protocol.version = %self.context.negotiated_protocol_version
686        ),
687        ret
688    )]
689    fn sys_sleep(
690        &mut self,
691        name: String,
692        wake_up_time_since_unix_epoch: Duration,
693        now_since_unix_epoch: Option<Duration>,
694    ) -> VMResult<NotificationHandle> {
695        if self.is_processing() {
696            match (&name, now_since_unix_epoch) {
697                (name, Some(now_since_unix_epoch)) if name.is_empty() => {
698                    debug!(
699                        "Executing 'Timer with duration {:?}'",
700                        wake_up_time_since_unix_epoch.saturating_sub(now_since_unix_epoch)
701                    );
702                }
703                (name, Some(now_since_unix_epoch)) => {
704                    debug!(
705                        "Executing 'Timer {name} with duration {:?}'",
706                        wake_up_time_since_unix_epoch.saturating_sub(now_since_unix_epoch)
707                    );
708                }
709                (name, None) if name.is_empty() => {
710                    debug!("Executing 'Timer'");
711                }
712                (name, None) => {
713                    debug!("Executing 'Timer named {name}'");
714                }
715            }
716        }
717        let wake_up_time = match u64::try_from(wake_up_time_since_unix_epoch.as_millis()) {
718            Ok(d) => d,
719            Err(e) => {
720                self.do_transition(HitError(OutOfBoundsDuration("sleep duration", e).into()))?;
721                unreachable!();
722            }
723        };
724        let completion_id = self.context.journal.next_completion_notification_id();
725
726        self.do_transition(SysSimpleCompletableEntry(
727            SleepCommandMessage {
728                wake_up_time,
729                result_completion_id: completion_id,
730                name,
731            },
732            completion_id,
733            PayloadOptions::default(),
734        ))
735    }
736
737    #[instrument(
738        level = "trace",
739        skip(self, input),
740        fields(
741            restate.invocation.id = self.debug_invocation_id(),
742            restate.protocol.state = self.debug_state(),
743            restate.journal.command_index = self.context.journal.command_index(),
744            restate.protocol.version = %self.context.negotiated_protocol_version
745        ),
746        ret
747    )]
748    fn sys_call(
749        &mut self,
750        target: Target,
751        input: Bytes,
752        name: Option<String>,
753        options: PayloadOptions,
754    ) -> VMResult<CallHandle> {
755        invocation_debug_logs!(
756            self,
757            "Executing 'Call {}/{}'",
758            target.service,
759            target.handler
760        );
761        if let Some(idempotency_key) = &target.idempotency_key {
762            if idempotency_key.is_empty() {
763                self.do_transition(HitError(EMPTY_IDEMPOTENCY_KEY))?;
764                unreachable!();
765            }
766        }
767        if let Some(scope) = &target.scope {
768            if scope.is_empty() {
769                self.do_transition(HitError(EMPTY_SCOPE))?;
770                unreachable!();
771            }
772        }
773        if let Some(limit_key) = &target.limit_key {
774            if limit_key.is_empty() {
775                self.do_transition(HitError(EMPTY_LIMIT_KEY))?;
776                unreachable!();
777            }
778        }
779        if target.scope.is_some() {
780            self.verify_feature_support("scope", Version::V7)?;
781        }
782        if target.limit_key.is_some() {
783            self.verify_feature_support("limit key", Version::V7)?;
784        }
785
786        let call_invocation_id_completion_id =
787            self.context.journal.next_completion_notification_id();
788        let result_completion_id = self.context.journal.next_completion_notification_id();
789
790        let handles = self.do_transition(SysCompletableEntryWithMultipleCompletions(
791            CallCommandMessage {
792                service_name: target.service,
793                handler_name: target.handler,
794                key: target.key.unwrap_or_default(),
795                idempotency_key: target.idempotency_key,
796                scope: target.scope,
797                limit_key: target.limit_key,
798                headers: target
799                    .headers
800                    .into_iter()
801                    .map(crate::service_protocol::messages::Header::from)
802                    .collect(),
803                parameter: input,
804                invocation_id_notification_idx: call_invocation_id_completion_id,
805                name: name.unwrap_or_default(),
806                result_completion_id,
807            },
808            vec![call_invocation_id_completion_id, result_completion_id],
809            options,
810        ))?;
811
812        if matches!(
813            self.options.implicit_cancellation,
814            ImplicitCancellationOption::Enabled {
815                cancel_children_calls: true,
816                ..
817            }
818        ) {
819            self.tracked_invocation_ids.push(TrackedInvocationId {
820                handle: handles[0],
821                invocation_id: None,
822            })
823        }
824
825        Ok(CallHandle {
826            invocation_id_notification_handle: handles[0],
827            call_notification_handle: handles[1],
828        })
829    }
830
831    #[instrument(
832        level = "trace",
833        skip(self, input),
834        fields(
835            restate.invocation.id = self.debug_invocation_id(),
836            restate.protocol.state = self.debug_state(),
837            restate.journal.command_index = self.context.journal.command_index(),
838            restate.protocol.version = %self.context.negotiated_protocol_version
839        ),
840        ret
841    )]
842    fn sys_send(
843        &mut self,
844        target: Target,
845        input: Bytes,
846        delay: Option<Duration>,
847        name: Option<String>,
848        options: PayloadOptions,
849    ) -> VMResult<SendHandle> {
850        invocation_debug_logs!(
851            self,
852            "Executing 'Send to {}/{}'",
853            target.service,
854            target.handler
855        );
856        if let Some(idempotency_key) = &target.idempotency_key {
857            if idempotency_key.is_empty() {
858                self.do_transition(HitError(EMPTY_IDEMPOTENCY_KEY))?;
859                unreachable!();
860            }
861        }
862        if let Some(scope) = &target.scope {
863            if scope.is_empty() {
864                self.do_transition(HitError(EMPTY_SCOPE))?;
865                unreachable!();
866            }
867        }
868        if let Some(limit_key) = &target.limit_key {
869            if limit_key.is_empty() {
870                self.do_transition(HitError(EMPTY_LIMIT_KEY))?;
871                unreachable!();
872            }
873        }
874        if target.scope.is_some() {
875            self.verify_feature_support("scope", Version::V7)?;
876        }
877        if target.limit_key.is_some() {
878            self.verify_feature_support("limit key", Version::V7)?;
879        }
880        let invoke_time = match u64::try_from(delay.unwrap_or_default().as_millis()) {
881            Ok(d) => d,
882            Err(e) => {
883                self.do_transition(HitError(OutOfBoundsDuration("send delay", e).into()))?;
884                unreachable!();
885            }
886        };
887        let call_invocation_id_completion_id =
888            self.context.journal.next_completion_notification_id();
889        let invocation_id_notification_handle = self.do_transition(SysSimpleCompletableEntry(
890            OneWayCallCommandMessage {
891                service_name: target.service,
892                handler_name: target.handler,
893                key: target.key.unwrap_or_default(),
894                idempotency_key: target.idempotency_key,
895                scope: target.scope,
896                limit_key: target.limit_key,
897                headers: target
898                    .headers
899                    .into_iter()
900                    .map(crate::service_protocol::messages::Header::from)
901                    .collect(),
902                parameter: input,
903                invoke_time,
904                invocation_id_notification_idx: call_invocation_id_completion_id,
905                name: name.unwrap_or_default(),
906            },
907            call_invocation_id_completion_id,
908            options,
909        ))?;
910
911        if matches!(
912            self.options.implicit_cancellation,
913            ImplicitCancellationOption::Enabled {
914                cancel_children_one_way_calls: true,
915                ..
916            }
917        ) {
918            self.tracked_invocation_ids.push(TrackedInvocationId {
919                handle: invocation_id_notification_handle,
920                invocation_id: None,
921            })
922        }
923
924        Ok(SendHandle {
925            invocation_id_notification_handle,
926        })
927    }
928
929    #[instrument(
930        level = "trace",
931        skip(self),
932        fields(
933            restate.invocation.id = self.debug_invocation_id(),
934            restate.protocol.state = self.debug_state(),
935            restate.journal.command_index = self.context.journal.command_index(),
936            restate.protocol.version = %self.context.negotiated_protocol_version
937        ),
938        ret
939    )]
940    fn sys_awakeable(&mut self) -> VMResult<AwakeableHandle> {
941        invocation_debug_logs!(self, "Executing 'Create awakeable'");
942
943        let signal_id = self.context.journal.next_signal_notification_id();
944
945        let handle = self.do_transition(CreateSignalHandle(
946            "awakeable",
947            NotificationId::SignalId(signal_id),
948        ))?;
949
950        Ok(AwakeableHandle {
951            id: awakeable_id_str(&self.context.expect_start_info().id, signal_id),
952            handle,
953        })
954    }
955
956    #[instrument(
957        level = "trace",
958        skip(self, value),
959        fields(
960            restate.invocation.id = self.debug_invocation_id(),
961            restate.protocol.state = self.debug_state(),
962            restate.journal.command_index = self.context.journal.command_index(),
963            restate.protocol.version = %self.context.negotiated_protocol_version
964        ),
965        ret
966    )]
967    fn sys_complete_awakeable(
968        &mut self,
969        id: String,
970        value: NonEmptyValue,
971        options: PayloadOptions,
972    ) -> VMResult<()> {
973        invocation_debug_logs!(self, "Executing 'Complete awakeable {id}'");
974        self.verify_error_metadata_feature_support(&value)?;
975        self.do_transition(SysNonCompletableEntry(
976            CompleteAwakeableCommandMessage {
977                awakeable_id: id,
978                result: Some(match value {
979                    NonEmptyValue::Success(s) => {
980                        complete_awakeable_command_message::Result::Value(s.into())
981                    }
982                    NonEmptyValue::Failure(f) => {
983                        complete_awakeable_command_message::Result::Failure(f.into())
984                    }
985                }),
986                ..Default::default()
987            },
988            options,
989        ))
990    }
991
992    #[instrument(
993        level = "trace",
994        skip(self),
995        fields(
996            restate.invocation.id = self.debug_invocation_id(),
997            restate.protocol.state = self.debug_state(),
998            restate.journal.command_index = self.context.journal.command_index(),
999            restate.protocol.version = %self.context.negotiated_protocol_version
1000        ),
1001        ret
1002    )]
1003    fn create_signal_handle(&mut self, signal_name: String) -> VMResult<NotificationHandle> {
1004        invocation_debug_logs!(self, "Executing 'Create named signal'");
1005
1006        self.do_transition(CreateSignalHandle(
1007            "named awakeable",
1008            NotificationId::SignalName(signal_name),
1009        ))
1010    }
1011
1012    #[instrument(
1013        level = "trace",
1014        skip(self, value),
1015        fields(
1016            restate.invocation.id = self.debug_invocation_id(),
1017            restate.protocol.state = self.debug_state(),
1018            restate.journal.command_index = self.context.journal.command_index(),
1019            restate.protocol.version = %self.context.negotiated_protocol_version
1020        ),
1021        ret
1022    )]
1023    fn sys_complete_signal(
1024        &mut self,
1025        target_invocation_id: String,
1026        signal_name: String,
1027        value: NonEmptyValue,
1028    ) -> VMResult<()> {
1029        invocation_debug_logs!(self, "Executing 'Complete named signal {signal_name}'");
1030        self.verify_error_metadata_feature_support(&value)?;
1031        self.do_transition(SysNonCompletableEntry(
1032            SendSignalCommandMessage {
1033                target_invocation_id,
1034                signal_id: Some(send_signal_command_message::SignalId::Name(signal_name)),
1035                result: Some(match value {
1036                    NonEmptyValue::Success(s) => {
1037                        send_signal_command_message::Result::Value(s.into())
1038                    }
1039                    NonEmptyValue::Failure(f) => {
1040                        send_signal_command_message::Result::Failure(f.into())
1041                    }
1042                }),
1043                ..Default::default()
1044            },
1045            PayloadOptions::default(),
1046        ))
1047    }
1048
1049    #[instrument(
1050        level = "trace",
1051        skip(self),
1052        fields(
1053            restate.invocation.id = self.debug_invocation_id(),
1054            restate.protocol.state = self.debug_state(),
1055            restate.journal.command_index = self.context.journal.command_index(),
1056            restate.protocol.version = %self.context.negotiated_protocol_version
1057        ),
1058        ret
1059    )]
1060    fn sys_get_promise(&mut self, key: String) -> VMResult<NotificationHandle> {
1061        invocation_debug_logs!(self, "Executing 'Await promise {key}'");
1062
1063        let result_completion_id = self.context.journal.next_completion_notification_id();
1064        self.do_transition(SysSimpleCompletableEntry(
1065            GetPromiseCommandMessage {
1066                key,
1067                result_completion_id,
1068                ..Default::default()
1069            },
1070            result_completion_id,
1071            PayloadOptions::default(),
1072        ))
1073    }
1074
1075    #[instrument(
1076        level = "trace",
1077        skip(self),
1078        fields(
1079            restate.invocation.id = self.debug_invocation_id(),
1080            restate.protocol.state = self.debug_state(),
1081            restate.journal.command_index = self.context.journal.command_index(),
1082            restate.protocol.version = %self.context.negotiated_protocol_version
1083        ),
1084        ret
1085    )]
1086    fn sys_peek_promise(&mut self, key: String) -> VMResult<NotificationHandle> {
1087        invocation_debug_logs!(self, "Executing 'Peek promise {key}'");
1088
1089        let result_completion_id = self.context.journal.next_completion_notification_id();
1090        self.do_transition(SysSimpleCompletableEntry(
1091            PeekPromiseCommandMessage {
1092                key,
1093                result_completion_id,
1094                ..Default::default()
1095            },
1096            result_completion_id,
1097            PayloadOptions::default(),
1098        ))
1099    }
1100
1101    #[instrument(
1102        level = "trace",
1103        skip(self, value),
1104        fields(
1105            restate.invocation.id = self.debug_invocation_id(),
1106            restate.protocol.state = self.debug_state(),
1107            restate.journal.command_index = self.context.journal.command_index(),
1108            restate.protocol.version = %self.context.negotiated_protocol_version
1109        ),
1110        ret
1111    )]
1112    fn sys_complete_promise(
1113        &mut self,
1114        key: String,
1115        value: NonEmptyValue,
1116        options: PayloadOptions,
1117    ) -> VMResult<NotificationHandle> {
1118        invocation_debug_logs!(self, "Executing 'Complete promise {key}'");
1119        self.verify_error_metadata_feature_support(&value)?;
1120
1121        let result_completion_id = self.context.journal.next_completion_notification_id();
1122        self.do_transition(SysSimpleCompletableEntry(
1123            CompletePromiseCommandMessage {
1124                key,
1125                completion: Some(match value {
1126                    NonEmptyValue::Success(s) => {
1127                        complete_promise_command_message::Completion::CompletionValue(s.into())
1128                    }
1129                    NonEmptyValue::Failure(f) => {
1130                        complete_promise_command_message::Completion::CompletionFailure(f.into())
1131                    }
1132                }),
1133                result_completion_id,
1134                ..Default::default()
1135            },
1136            result_completion_id,
1137            options,
1138        ))
1139    }
1140
1141    #[instrument(
1142        level = "trace",
1143        skip(self),
1144        fields(
1145            restate.invocation.id = self.debug_invocation_id(),
1146            restate.protocol.state = self.debug_state(),
1147            restate.journal.command_index = self.context.journal.command_index(),
1148            restate.protocol.version = %self.context.negotiated_protocol_version
1149        ),
1150        ret
1151    )]
1152    fn sys_run(&mut self, name: String) -> VMResult<RunHandle> {
1153        match self.do_transition(SysRun(name.clone())) {
1154            Ok(handle) => {
1155                if enabled!(Level::DEBUG) && !handle.replayed {
1156                    // Store the name, we need it later when completing
1157                    self.sys_run_names.insert(handle.handle, name);
1158                }
1159                Ok(handle)
1160            }
1161            Err(e) => Err(e),
1162        }
1163    }
1164
1165    #[instrument(
1166        level = "trace",
1167        skip(self, value, retry_policy),
1168        fields(
1169            restate.invocation.id = self.debug_invocation_id(),
1170            restate.protocol.state = self.debug_state(),
1171            restate.journal.command_index = self.context.journal.command_index(),
1172            restate.protocol.version = %self.context.negotiated_protocol_version
1173        ),
1174        ret
1175    )]
1176    fn propose_run_completion(
1177        &mut self,
1178        notification_handle: NotificationHandle,
1179        value: RunExitResult,
1180        retry_policy: RetryPolicy,
1181    ) -> VMResult<()> {
1182        if enabled!(Level::DEBUG) {
1183            let name = self
1184                .sys_run_names
1185                .remove(&notification_handle)
1186                .unwrap_or_default();
1187            match &value {
1188                RunExitResult::Success(_) => {
1189                    invocation_debug_logs!(self, "Journaling run '{name}' success result");
1190                }
1191                RunExitResult::TerminalFailure(TerminalFailure { code, .. }) => {
1192                    invocation_debug_logs!(
1193                        self,
1194                        "Journaling run '{name}' terminal failure {code} result"
1195                    );
1196                }
1197                RunExitResult::RetryableFailure { .. } => {
1198                    invocation_debug_logs!(self, "Propagating run '{name}' retryable failure");
1199                }
1200            }
1201        }
1202        if let RunExitResult::TerminalFailure(f) = &value {
1203            if !f.metadata.is_empty() {
1204                self.verify_feature_support("terminal error metadata", Version::V6)?;
1205            }
1206        }
1207        if retry_policy.should_pause_on_max_attempts() {
1208            self.verify_feature_support("pause", Version::V7)?;
1209        }
1210
1211        self.do_transition(ProposeRunCompletion(
1212            notification_handle,
1213            value,
1214            retry_policy,
1215        ))
1216    }
1217
1218    #[instrument(
1219        level = "trace",
1220        skip(self),
1221        fields(
1222            restate.invocation.id = self.debug_invocation_id(),
1223            restate.protocol.state = self.debug_state(),
1224            restate.journal.command_index = self.context.journal.command_index(),
1225            restate.protocol.version = %self.context.negotiated_protocol_version
1226        ),
1227        ret
1228    )]
1229    fn sys_cancel_invocation(&mut self, target_invocation_id: String) -> VMResult<()> {
1230        invocation_debug_logs!(
1231            self,
1232            "Executing 'Cancel invocation' of {target_invocation_id}"
1233        );
1234        self.do_transition(SysNonCompletableEntry(
1235            SendSignalCommandMessage {
1236                target_invocation_id,
1237                signal_id: Some(send_signal_command_message::SignalId::Idx(CANCEL_SIGNAL_ID)),
1238                result: Some(send_signal_command_message::Result::Void(Default::default())),
1239                ..Default::default()
1240            },
1241            PayloadOptions::default(),
1242        ))
1243    }
1244
1245    #[instrument(
1246        level = "trace",
1247        skip(self),
1248        fields(
1249            restate.invocation.id = self.debug_invocation_id(),
1250            restate.protocol.state = self.debug_state(),
1251            restate.journal.command_index = self.context.journal.command_index(),
1252            restate.protocol.version = %self.context.negotiated_protocol_version
1253        ),
1254        ret
1255    )]
1256    fn sys_attach_invocation(
1257        &mut self,
1258        target: AttachInvocationTarget,
1259    ) -> VMResult<NotificationHandle> {
1260        invocation_debug_logs!(self, "Executing 'Attach invocation'");
1261
1262        match &target {
1263            AttachInvocationTarget::WorkflowId {
1264                scope: Some(scope), ..
1265            }
1266            | AttachInvocationTarget::IdempotencyId {
1267                scope: Some(scope), ..
1268            } => {
1269                if scope.is_empty() {
1270                    self.do_transition(HitError(EMPTY_SCOPE))?;
1271                    unreachable!();
1272                }
1273                self.verify_feature_support("scope", Version::V7)?;
1274            }
1275            _ => {}
1276        };
1277
1278        let result_completion_id = self.context.journal.next_completion_notification_id();
1279        self.do_transition(SysSimpleCompletableEntry(
1280            AttachInvocationCommandMessage {
1281                target: Some(match target {
1282                    AttachInvocationTarget::InvocationId(id) => {
1283                        attach_invocation_command_message::Target::InvocationId(id)
1284                    }
1285                    AttachInvocationTarget::WorkflowId { name, key, scope } => {
1286                        attach_invocation_command_message::Target::WorkflowTarget(WorkflowTarget {
1287                            workflow_name: name,
1288                            workflow_key: key,
1289                            scope,
1290                        })
1291                    }
1292                    AttachInvocationTarget::IdempotencyId {
1293                        service_name,
1294                        service_key,
1295                        handler_name,
1296                        idempotency_key,
1297                        scope,
1298                    } => attach_invocation_command_message::Target::IdempotentRequestTarget(
1299                        IdempotentRequestTarget {
1300                            service_name,
1301                            service_key,
1302                            handler_name,
1303                            idempotency_key,
1304                            scope,
1305                        },
1306                    ),
1307                }),
1308                result_completion_id,
1309                ..Default::default()
1310            },
1311            result_completion_id,
1312            PayloadOptions::default(),
1313        ))
1314    }
1315
1316    #[instrument(
1317        level = "trace",
1318        skip(self),
1319        fields(
1320            restate.invocation.id = self.debug_invocation_id(),
1321            restate.protocol.state = self.debug_state(),
1322            restate.journal.command_index = self.context.journal.command_index(),
1323            restate.protocol.version = %self.context.negotiated_protocol_version
1324        ),
1325        ret
1326    )]
1327    fn sys_get_invocation_output(
1328        &mut self,
1329        target: AttachInvocationTarget,
1330    ) -> VMResult<NotificationHandle> {
1331        invocation_debug_logs!(self, "Executing 'Get invocation output'");
1332
1333        match &target {
1334            AttachInvocationTarget::WorkflowId {
1335                scope: Some(scope), ..
1336            }
1337            | AttachInvocationTarget::IdempotencyId {
1338                scope: Some(scope), ..
1339            } => {
1340                if scope.is_empty() {
1341                    self.do_transition(HitError(EMPTY_SCOPE))?;
1342                    unreachable!();
1343                }
1344                self.verify_feature_support("scope", Version::V7)?;
1345            }
1346            _ => {}
1347        };
1348
1349        let result_completion_id = self.context.journal.next_completion_notification_id();
1350        self.do_transition(SysSimpleCompletableEntry(
1351            GetInvocationOutputCommandMessage {
1352                target: Some(match target {
1353                    AttachInvocationTarget::InvocationId(id) => {
1354                        get_invocation_output_command_message::Target::InvocationId(id)
1355                    }
1356                    AttachInvocationTarget::WorkflowId { name, key, scope } => {
1357                        get_invocation_output_command_message::Target::WorkflowTarget(
1358                            WorkflowTarget {
1359                                workflow_name: name,
1360                                workflow_key: key,
1361                                scope,
1362                            },
1363                        )
1364                    }
1365                    AttachInvocationTarget::IdempotencyId {
1366                        service_name,
1367                        service_key,
1368                        handler_name,
1369                        idempotency_key,
1370                        scope,
1371                    } => get_invocation_output_command_message::Target::IdempotentRequestTarget(
1372                        IdempotentRequestTarget {
1373                            service_name,
1374                            service_key,
1375                            handler_name,
1376                            idempotency_key,
1377                            scope,
1378                        },
1379                    ),
1380                }),
1381                result_completion_id,
1382                ..Default::default()
1383            },
1384            result_completion_id,
1385            PayloadOptions::default(),
1386        ))
1387    }
1388
1389    #[instrument(
1390        level = "trace",
1391        skip(self, value),
1392        fields(
1393            restate.invocation.id = self.debug_invocation_id(),
1394            restate.protocol.state = self.debug_state(),
1395            restate.journal.command_index = self.context.journal.command_index(),
1396            restate.protocol.version = %self.context.negotiated_protocol_version
1397        ),
1398        ret
1399    )]
1400    fn sys_write_output(&mut self, value: NonEmptyValue, options: PayloadOptions) -> VMResult<()> {
1401        match &value {
1402            NonEmptyValue::Success(_) => {
1403                invocation_debug_logs!(self, "Writing invocation result success value");
1404            }
1405            NonEmptyValue::Failure(_) => {
1406                invocation_debug_logs!(self, "Writing invocation result failure value");
1407            }
1408        }
1409        self.verify_error_metadata_feature_support(&value)?;
1410        self.do_transition(SysNonCompletableEntry(
1411            OutputCommandMessage {
1412                result: Some(match value {
1413                    NonEmptyValue::Success(b) => output_command_message::Result::Value(b.into()),
1414                    NonEmptyValue::Failure(f) => output_command_message::Result::Failure(f.into()),
1415                }),
1416                ..OutputCommandMessage::default()
1417            },
1418            options,
1419        ))
1420    }
1421
1422    #[instrument(
1423        level = "trace",
1424        skip(self),
1425        fields(
1426            restate.invocation.id = self.debug_invocation_id(),
1427            restate.protocol.state = self.debug_state(),
1428            restate.journal.command_index = self.context.journal.command_index(),
1429            restate.protocol.version = %self.context.negotiated_protocol_version
1430        ),
1431        ret
1432    )]
1433    fn sys_end(&mut self) -> Result<(), Error> {
1434        invocation_debug_logs!(self, "End of the invocation");
1435        self.do_transition(SysEnd)
1436    }
1437
1438    #[inline]
1439    fn state(&self) -> crate::State {
1440        match &self.last_transition {
1441            Ok(State::WaitingStart) | Ok(State::WaitingReplayEntries { .. }) => {
1442                crate::State::WaitingPreFlight
1443            }
1444            Ok(State::Replaying { .. }) => crate::State::Replaying,
1445            Ok(State::Processing { .. }) => crate::State::Processing,
1446            Ok(State::Closed) | Err(_) => crate::State::Closed,
1447        }
1448    }
1449
1450    fn last_command_index(&self) -> i64 {
1451        self.context.journal.command_index()
1452    }
1453}
1454
1455const INDIFFERENT_PAD: GeneralPurposeConfig = GeneralPurposeConfig::new()
1456    .with_decode_padding_mode(DecodePaddingMode::Indifferent)
1457    .with_encode_padding(false);
1458const URL_SAFE: GeneralPurpose = GeneralPurpose::new(&alphabet::URL_SAFE, INDIFFERENT_PAD);
1459
1460const AWAKEABLE_PREFIX: &str = "sign_1";
1461
1462pub(super) fn awakeable_id_str(id: &[u8], completion_index: u32) -> String {
1463    let mut input_buf = BytesMut::with_capacity(id.len() + size_of::<u32>());
1464    input_buf.put_slice(id);
1465    input_buf.put_u32(completion_index);
1466    format!("{AWAKEABLE_PREFIX}{}", URL_SAFE.encode(input_buf.freeze()))
1467}
1468
1469#[cfg(test)]
1470mod tests {
1471    use super::*;
1472
1473    use crate::service_protocol::messages::Future;
1474
1475    impl CoreVM {
1476        pub(crate) fn resolve_unresolved_future(
1477            &self,
1478            unresolved_future: UnresolvedFuture,
1479        ) -> Future {
1480            match &self.last_transition {
1481                Ok(
1482                    State::Replaying { async_results, .. }
1483                    | State::Processing { async_results, .. },
1484                ) => async_results.resolve_unresolved_future(unresolved_future),
1485                _ => panic!("Could not resolve unresolved future"),
1486            }
1487        }
1488    }
1489}