oasis_runtime_sdk/
state.rs

1use std::{
2    any::Any,
3    cell::RefCell,
4    collections::btree_map::{BTreeMap, Entry},
5    fmt,
6    marker::PhantomData,
7    mem,
8};
9
10use oasis_core_runtime::{common::crypto::hash::Hash, consensus::roothash, storage::mkvs};
11
12use crate::{
13    context::Context,
14    crypto::{random::RootRng, signature::PublicKey},
15    event::{Event, EventTag, EventTags},
16    modules::core::Error,
17    storage::{MKVSStore, NestedStore, OverlayStore, Store},
18    types::{address::Address, message::MessageEventHookInvocation, transaction},
19};
20
21/// Execution mode.
22#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
23#[repr(u8)]
24pub enum Mode {
25    /// Actually execute transactions during block production.
26    #[default]
27    Execute,
28    /// Check that transactions are valid for local acceptance into the transaction pool.
29    Check,
30    /// Simulate transaction outcomes (e.g. for gas estimation).
31    Simulate,
32    /// Check that transactions are still valid before scheduling.
33    PreSchedule,
34}
35
36const MODE_CHECK: &str = "check";
37const MODE_EXECUTE: &str = "execute";
38const MODE_SIMULATE: &str = "simulate";
39const MODE_PRE_SCHEDULE: &str = "pre_schedule";
40
41impl fmt::Display for Mode {
42    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43        f.write_str(self.into())
44    }
45}
46
47impl From<&Mode> for &'static str {
48    fn from(m: &Mode) -> Self {
49        match m {
50            Mode::Check => MODE_CHECK,
51            Mode::Execute => MODE_EXECUTE,
52            Mode::Simulate => MODE_SIMULATE,
53            Mode::PreSchedule => MODE_PRE_SCHEDULE,
54        }
55    }
56}
57
58/// Information about the execution environment.
59#[derive(Clone, Default, Debug)]
60pub struct Environment {
61    mode: Mode,
62    tx: Option<TransactionWithMeta>,
63    internal: bool,
64}
65
66impl Environment {
67    /// Execution mode.
68    pub fn mode(&self) -> Mode {
69        self.mode
70    }
71
72    /// Whether the execution mode is such that only checks should be performed.
73    pub fn is_check_only(&self) -> bool {
74        matches!(self.mode, Mode::Check | Mode::PreSchedule)
75    }
76
77    /// Whether the execution mode is `Mode::PreSchedule`.
78    pub fn is_pre_schedule(&self) -> bool {
79        matches!(self.mode, Mode::PreSchedule)
80    }
81
82    /// Whether the execution mode is `Mode::Simulate`.
83    pub fn is_simulation(&self) -> bool {
84        matches!(self.mode, Mode::Simulate)
85    }
86
87    /// Whether the execution mode is `Mode::Execute`.
88    pub fn is_execute(&self) -> bool {
89        matches!(self.mode, Mode::Execute)
90    }
91
92    /// Whether there is an active transaction in the current environment.
93    pub fn is_transaction(&self) -> bool {
94        self.tx.is_some()
95    }
96
97    /// An active transaction's index (order) within the block.
98    ///
99    /// # Panics
100    ///
101    /// This method will panic if called outside a transaction environment.
102    pub fn tx_index(&self) -> usize {
103        self.tx
104            .as_ref()
105            .map(|tx| tx.index)
106            .expect("only in transaction environment")
107    }
108
109    /// An active transaction's size in bytes.
110    ///
111    /// # Panics
112    ///
113    /// This method will panic if called outside a transaction environment.
114    pub fn tx_size(&self) -> u32 {
115        self.tx
116            .as_ref()
117            .map(|tx| tx.size)
118            .expect("only in transaction environment")
119    }
120
121    /// An active transaction's authentication information.
122    ///
123    /// # Panics
124    ///
125    /// This method will panic if called outside a transaction environment.
126    pub fn tx_auth_info(&self) -> &transaction::AuthInfo {
127        self.tx
128            .as_ref()
129            .map(|tx| &tx.data.auth_info)
130            .expect("only in transaction environment")
131    }
132
133    /// An active transaction's call format.
134    ///
135    /// # Panics
136    ///
137    /// This method will panic if called outside a transaction environment.
138    pub fn tx_call_format(&self) -> transaction::CallFormat {
139        self.tx
140            .as_ref()
141            .map(|tx| tx.data.call.format)
142            .expect("only in transaction environment")
143    }
144
145    /// An active transaction's read only flag.
146    ///
147    /// # Panics
148    ///
149    /// This method will panic if called outside a transaction environment.
150    pub fn is_read_only(&self) -> bool {
151        self.tx
152            .as_ref()
153            .map(|tx| tx.data.call.read_only)
154            .expect("only in transaction environment")
155    }
156
157    /// Whether the current execution environment is part of an internal subcall.
158    pub fn is_internal(&self) -> bool {
159        self.internal
160    }
161
162    /// Authenticated address of the caller.
163    ///
164    /// In case there are multiple signers of a transaction, this will return the address
165    /// corresponding to the first signer. If there are no signers, it returns the default address.
166    ///
167    /// # Panics
168    ///
169    /// This method will panic if called outside a transaction environment.
170    pub fn tx_caller_address(&self) -> Address {
171        self.tx_auth_info()
172            .signer_info
173            .first()
174            .map(|si| si.address_spec.address())
175            .unwrap_or_default()
176    }
177
178    /// Authenticated caller public key if available.
179    ///
180    /// In case there are multiple signers of a transaction, this will return the public key
181    /// corresponding to the first signer. If there are no signers or if the address specification
182    /// does not represent a single public key, it returns `None`.
183    ///
184    /// # Panics
185    ///
186    /// This method will panic if called outside a transaction environment.
187    pub fn tx_caller_public_key(&self) -> Option<PublicKey> {
188        self.tx_auth_info()
189            .signer_info
190            .first()
191            .and_then(|si| si.address_spec.public_key())
192    }
193}
194
195/// Decoded transaction with additional metadata.
196#[derive(Clone, Debug)]
197pub struct TransactionWithMeta {
198    /// Decoded transaction.
199    pub data: transaction::Transaction,
200    /// Transaction size.
201    pub size: u32,
202    /// Transaction index within the batch.
203    pub index: usize,
204    /// Transaction hash.
205    pub hash: Hash,
206}
207
208impl TransactionWithMeta {
209    /// Create transaction with metadata for an internally generated transaction.
210    ///
211    /// Internally generated transactions have zero size, index and hash.
212    pub fn internal(tx: transaction::Transaction) -> Self {
213        Self {
214            data: tx,
215            size: 0,
216            index: 0,
217            hash: Default::default(),
218        }
219    }
220}
221
222#[cfg(any(test, feature = "test"))]
223impl From<transaction::Transaction> for TransactionWithMeta {
224    fn from(tx: transaction::Transaction) -> Self {
225        Self::internal(tx) // For use in tests.
226    }
227}
228
229/// Environment modification options.
230#[derive(Clone, Default, Debug)]
231pub struct Options {
232    pub mode: Option<Mode>,
233    pub tx: Option<TransactionWithMeta>,
234    pub internal: Option<bool>,
235    pub rng_local_entropy: bool,
236}
237
238impl Options {
239    /// Create options with default values.
240    pub fn new() -> Self {
241        Self::default()
242    }
243
244    /// Change the execution mode of the environment.
245    pub fn with_mode(self, mode: Mode) -> Self {
246        Self {
247            mode: Some(mode),
248            ..self
249        }
250    }
251
252    /// Change the active transaction of the environment.
253    pub fn with_tx(self, tx: TransactionWithMeta) -> Self {
254        Self {
255            tx: Some(tx),
256            ..self
257        }
258    }
259
260    /// Change the internal flag of the environment.
261    pub fn with_internal(self, internal: bool) -> Self {
262        Self {
263            internal: Some(internal),
264            ..self
265        }
266    }
267
268    /// Request for local entropy to be mixed into the current RNG.
269    ///
270    /// # Determinisim
271    ///
272    /// Using this method will result in non-deterministic behavior as the node's local entropy is
273    /// mixed into the RNG. As such, this method should only be used in cases where non-determinism
274    /// is not problematic (e.g. local queries).
275    pub fn with_rng_local_entropy(self) -> Self {
276        Self {
277            rng_local_entropy: true,
278            ..self
279        }
280    }
281}
282
283/// Mutable block state of a runtime.
284///
285/// The state includes storage, emitted events, messages to consensus layer, etc. States can be
286/// nested via `open`, `commit` and `rollback` methods which behave like transactions.
287pub struct State {
288    parent: Option<Box<State>>,
289    store: Option<OverlayStore<Box<dyn Store>>>,
290
291    events: EventTags,
292    unconditional_events: EventTags,
293    messages: Vec<(roothash::Message, MessageEventHookInvocation)>,
294
295    block_values: BTreeMap<&'static str, Box<dyn Any>>,
296    hidden_block_values: Option<BTreeMap<&'static str, Box<dyn Any>>>,
297    local_values: BTreeMap<&'static str, Box<dyn Any>>,
298
299    rng: Option<RootRng>,
300    hidden_rng: Option<RootRng>,
301    env: Environment,
302
303    always_rollback: bool,
304}
305
306impl State {
307    /// Initialize the state with the given options.
308    fn init(&mut self, opts: Options) {
309        if let Some(mode) = opts.mode {
310            // Change mode.
311            self.env.mode = mode;
312            // If we have enabled pre-schedule or simulation mode, always rollback state and hide
313            // block values to prevent leaking them.
314            if matches!(mode, Mode::PreSchedule | Mode::Simulate) {
315                self.always_rollback = true;
316                self.hide_block_values();
317            }
318        }
319
320        if let Some(tx) = opts.tx {
321            // Change RNG state.
322            self.rng.as_mut().unwrap().append_tx(tx.hash);
323            // Change tx metadata.
324            self.env.tx = Some(tx);
325        }
326
327        if let Some(internal) = opts.internal {
328            self.env.internal = internal;
329            if internal {
330                self.hide_block_values();
331            }
332        }
333
334        if opts.rng_local_entropy {
335            // Append local entropy to RNG state.
336            self.rng.as_mut().unwrap().append_local_entropy();
337        }
338
339        if !matches!(self.env.mode, Mode::PreSchedule) {
340            // Record opening a child state in the RNG.
341            self.rng.as_mut().unwrap().append_subcontext();
342        } else {
343            // Use an invalid RNG as its use is not allowed in pre-schedule context.
344            self.disable_rng();
345        }
346    }
347
348    /// Open a child state after which self will point to the child state.
349    pub fn open(&mut self) {
350        let mut parent = Self {
351            parent: None,
352            store: None,
353            events: EventTags::new(),
354            unconditional_events: EventTags::new(),
355            messages: Vec::new(),
356            block_values: BTreeMap::new(),
357            hidden_block_values: None,
358            local_values: BTreeMap::new(),
359            rng: None,
360            hidden_rng: None,
361            env: self.env.clone(),
362            always_rollback: false,
363        };
364        mem::swap(&mut parent, self);
365
366        // Wrap parent store to create an overlay child store.
367        self.store = parent
368            .store
369            .take()
370            .map(|pstore| OverlayStore::new(Box::new(pstore) as Box<dyn Store>));
371
372        // Take block values map. We will put it back after commit/rollback.
373        mem::swap(&mut parent.block_values, &mut self.block_values);
374        // Take RNG. We will put it back after commit/rollback.
375        mem::swap(&mut parent.rng, &mut self.rng);
376
377        self.parent = Some(Box::new(parent));
378    }
379
380    fn convert_store(store: Box<dyn Store>) -> OverlayStore<Box<dyn Store>> {
381        let raw = Box::into_raw(store);
382        unsafe {
383            // SAFETY: This is safe because we always wrap child stores into OverlayStore.
384            *Box::from_raw(raw as *mut OverlayStore<Box<dyn Store>>)
385        }
386    }
387
388    /// Commit the current state and return to its parent state.
389    ///
390    /// # Panics
391    ///
392    /// This method will panic when attempting to commit the root state.
393    pub fn commit(&mut self) {
394        if self.always_rollback {
395            self.rollback();
396        } else {
397            self._commit();
398        }
399    }
400
401    fn _commit(&mut self) {
402        let mut child = *self.parent.take().expect("cannot commit on root state");
403        mem::swap(&mut child, self);
404
405        // Commit storage.
406        self.store = child
407            .store
408            .take()
409            .map(|cstore| Self::convert_store(cstore.commit()));
410
411        // Propagate messages.
412        self.messages.extend(child.messages);
413
414        // Propagate events.
415        for (key, event) in child.events {
416            let events = self.events.entry(key).or_default();
417            events.extend(event);
418        }
419        for (key, event) in child.unconditional_events {
420            let events = self.unconditional_events.entry(key).or_default();
421            events.extend(event);
422        }
423
424        // Put back per-block values.
425        if let Some(mut block_values) = child.hidden_block_values {
426            mem::swap(&mut block_values, &mut self.block_values); // Block values were hidden.
427        } else {
428            mem::swap(&mut child.block_values, &mut self.block_values);
429        }
430        // Always drop local values.
431
432        // Put back RNG.
433        if child.hidden_rng.is_some() {
434            mem::swap(&mut child.hidden_rng, &mut self.rng); // RNG was hidden.
435        } else {
436            mem::swap(&mut child.rng, &mut self.rng);
437        }
438    }
439
440    /// Rollback the current state and return to its parent state.
441    ///
442    /// # Panics
443    ///
444    /// This method will panic when attempting to rollback the root state.
445    pub fn rollback(&mut self) {
446        let mut child = *self.parent.take().expect("cannot rollback on root state");
447        mem::swap(&mut child, self);
448
449        // Rollback storage.
450        self.store = child
451            .store
452            .take()
453            .map(|cstore| Self::convert_store(cstore.rollback()));
454
455        // Always put back per-block values.
456        if let Some(mut block_values) = child.hidden_block_values {
457            mem::swap(&mut block_values, &mut self.block_values); // Block values were hidden.
458        } else {
459            mem::swap(&mut child.block_values, &mut self.block_values);
460        }
461        // Always drop local values.
462
463        // Always put back RNG.
464        if child.hidden_rng.is_some() {
465            mem::swap(&mut child.hidden_rng, &mut self.rng); // RNG was hidden.
466        } else {
467            mem::swap(&mut child.rng, &mut self.rng);
468        }
469    }
470
471    /// Fetches a block state value entry.
472    ///
473    /// Block values live as long as the root `State` and are propagated to child states. They are
474    /// not affected by state rollbacks. If you need state-scoped values, use local values.
475    pub fn block_value<V: Any>(&mut self, key: &'static str) -> StateValue<'_, V> {
476        StateValue::new(self.block_values.entry(key))
477    }
478
479    /// Fetches a local state value entry.
480    ///
481    /// Local values only live as long as the current `State`, are dropped upon exiting to parent
482    /// state and child states start with an empty set. If you need longer-lived values, use block
483    /// values.
484    pub fn local_value<V: Any>(&mut self, key: &'static str) -> StateValue<'_, V> {
485        StateValue::new(self.local_values.entry(key))
486    }
487
488    /// Hides block values from the current state which will have an empty set of values after this
489    /// method returns. Hidden values will be restored upon exit to parent state.
490    pub fn hide_block_values(&mut self) {
491        if self.parent.is_none() {
492            // Allowing hiding on root state would prevent those values from ever being recovered.
493            panic!("cannot hide block values on root state");
494        }
495        if self.hidden_block_values.is_some() {
496            return; // Parent block values already hidden.
497        }
498
499        self.hidden_block_values = Some(mem::take(&mut self.block_values));
500    }
501
502    /// Emitted messages count returns the number of messages emitted so far across this and all
503    /// parent states.
504    pub fn emitted_messages_count(&self) -> usize {
505        self.messages.len()
506            + self
507                .parent
508                .as_ref()
509                .map(|p| p.emitted_messages_count())
510                .unwrap_or_default()
511    }
512
513    /// Emitted messages count returns the number of messages emitted so far in this state, not
514    /// counting any parent states.
515    pub fn emitted_messages_local_count(&self) -> usize {
516        self.messages.len()
517    }
518
519    /// Maximum number of messages that can be emitted.
520    pub fn emitted_messages_max<C: Context>(&self, ctx: &C) -> u32 {
521        if self.env.is_transaction() {
522            let limit = self.env.tx_auth_info().fee.consensus_messages;
523            if limit > 0 {
524                limit
525            } else {
526                ctx.max_messages() // Zero means an implicit limit by gas use.
527            }
528        } else {
529            ctx.max_messages()
530        }
531    }
532
533    /// Queue a message to be emitted by the runtime for consensus layer to process.
534    pub fn emit_message<C: Context>(
535        &mut self,
536        ctx: &C,
537        msg: roothash::Message,
538        hook: MessageEventHookInvocation,
539    ) -> Result<(), Error> {
540        // Check against maximum number of messages that can be emitted per round.
541        if self.emitted_messages_count() >= self.emitted_messages_max(ctx) as usize {
542            return Err(Error::OutOfMessageSlots);
543        }
544
545        self.messages.push((msg, hook));
546
547        Ok(())
548    }
549
550    /// Take all messages accumulated in the current state.
551    pub fn take_messages(&mut self) -> Vec<(roothash::Message, MessageEventHookInvocation)> {
552        mem::take(&mut self.messages)
553    }
554
555    /// Emit an event.
556    pub fn emit_event<E: Event>(&mut self, event: E) {
557        self.emit_event_raw(event.into_event_tag());
558    }
559
560    /// Emit a raw event.
561    pub fn emit_event_raw(&mut self, etag: EventTag) {
562        let events = self.events.entry(etag.key).or_default();
563        events.push(etag.value);
564    }
565
566    /// Emit an unconditional event.
567    ///
568    /// The only difference to regular events is that these are handled as a separate set.
569    pub fn emit_unconditional_event<E: Event>(&mut self, event: E) {
570        let etag = event.into_event_tag();
571        let events = self.unconditional_events.entry(etag.key).or_default();
572        events.push(etag.value);
573    }
574
575    /// Take all regular events accumulated in the current state.
576    pub fn take_events(&mut self) -> EventTags {
577        mem::take(&mut self.events)
578    }
579
580    /// Take all unconditional events accumulated in the current state.
581    pub fn take_unconditional_events(&mut self) -> EventTags {
582        mem::take(&mut self.unconditional_events)
583    }
584
585    /// Take all events accumulated in the current state and return the merged set.
586    pub fn take_all_events(&mut self) -> EventTags {
587        let mut events = self.take_events();
588        let unconditional_events = self.take_unconditional_events();
589
590        for (key, val) in unconditional_events {
591            let tag = events.entry(key).or_default();
592            tag.extend(val)
593        }
594
595        events
596    }
597
598    /// Store associated with the state.
599    ///
600    /// # Panics
601    ///
602    /// This method will panic if no store exists.
603    pub fn store(&mut self) -> &mut dyn Store {
604        self.store.as_mut().unwrap()
605    }
606
607    /// Whether the store associated with the state has any pending updates.
608    pub fn has_pending_store_updates(&self) -> bool {
609        self.store
610            .as_ref()
611            .map(|store| store.has_pending_updates())
612            .unwrap_or_default()
613    }
614
615    /// Size (in bytes) of any pending updates in the associated store.
616    pub fn pending_store_update_byte_size(&self) -> usize {
617        self.store
618            .as_ref()
619            .map(|store| store.pending_update_byte_size())
620            .unwrap_or_default()
621    }
622
623    /// Random number generator.
624    pub fn rng(&mut self) -> &mut RootRng {
625        self.rng.as_mut().unwrap()
626    }
627
628    /// Disables the RNG by replacing the instance with an invalid RNG.
629    fn disable_rng(&mut self) {
630        if self.parent.is_none() {
631            // Allowing hiding on root state would prevent the RNG from ever being recovered.
632            panic!("cannot hide the RNG on root state");
633        }
634        if self.hidden_rng.is_some() {
635            return; // Parent RNG already hidden.
636        }
637
638        self.hidden_rng = mem::replace(&mut self.rng, Some(RootRng::invalid()));
639    }
640
641    /// Environment information.
642    pub fn env(&self) -> &Environment {
643        &self.env
644    }
645
646    /// Origin environment information.
647    ///
648    /// The origin environment is the first non-internal environment in the hierarchy.
649    pub fn env_origin(&self) -> &Environment {
650        match self.parent {
651            Some(ref parent) if self.env.internal => parent.env_origin(),
652            _ => &self.env,
653        }
654    }
655
656    /// Returns the nesting level of the current state.
657    pub fn level(&self) -> usize {
658        if let Some(ref parent) = self.parent {
659            parent.level() + 1
660        } else {
661            0
662        }
663    }
664}
665
666thread_local! {
667    static CURRENT: RefCell<Vec<State>> = const { RefCell::new(Vec::new()) };
668}
669
670struct CurrentStateGuard;
671
672impl Drop for CurrentStateGuard {
673    fn drop(&mut self) {
674        CURRENT.with(|c| {
675            let root = c.borrow_mut().pop().expect("must have current state");
676            // Commit root state as it has been wrapped in an overlay.
677            let store = root
678                .store
679                .expect("must not have open child states after exiting root state");
680            store.commit();
681        });
682    }
683}
684
685struct TransactionGuard(usize);
686
687impl Drop for TransactionGuard {
688    fn drop(&mut self) {
689        let level = CurrentState::with(|state| state.level());
690
691        // If transaction hasn't been either committed or reverted, rollback.
692        if level == self.0 {
693            CurrentState::rollback_transaction();
694        }
695    }
696}
697
698/// Result of a transaction helper closure.
699pub enum TransactionResult<T> {
700    Commit(T),
701    Rollback(T),
702}
703
704impl From<()> for TransactionResult<()> {
705    fn from(_: ()) -> TransactionResult<()> {
706        TransactionResult::Commit(())
707    }
708}
709
710impl<R, E> From<Result<R, E>> for TransactionResult<Result<R, E>> {
711    fn from(v: Result<R, E>) -> TransactionResult<Result<R, E>> {
712        match v {
713            Ok(_) => TransactionResult::Commit(v),
714            Err(_) => TransactionResult::Rollback(v),
715        }
716    }
717}
718
719/// State attached to the current thread.
720pub struct CurrentState;
721
722impl CurrentState {
723    /// Attach a new state to the current thread and enter the state's context.
724    ///
725    /// The passed store is used as the root store.
726    ///
727    /// # Panics
728    ///
729    /// This method will panic if called from within a `CurrentState::with` block.
730    pub fn enter<S, F, R>(root: S, f: F) -> R
731    where
732        S: Store,
733        F: FnOnce() -> R,
734    {
735        Self::enter_opts(
736            Options {
737                mode: Some(Default::default()), // Make sure there is a default mode.
738                ..Options::default()
739            },
740            root,
741            f,
742        )
743    }
744
745    /// Attach a new state to the current thread and enter the state's context.
746    ///
747    /// The passed store is used as the root store.
748    ///
749    /// # Panics
750    ///
751    /// This method will panic if called from within a `CurrentState::with` block or if the mode
752    /// has not been explicitly set in `opts`.
753    pub fn enter_opts<S, F, R>(opts: Options, mut root: S, f: F) -> R
754    where
755        S: Store,
756        F: FnOnce() -> R,
757    {
758        let root = unsafe {
759            // SAFETY: Keeping the root store is safe as it can only be accessed from the current
760            // thread while we are running inside `CurrentState::enter` where we are holding a
761            // mutable reference on it.
762            std::mem::transmute::<&mut dyn Store, &mut (dyn Store + 'static)>(
763                &mut root as &mut dyn Store,
764            )
765        };
766        // Initialize the root state.
767        let mode = opts
768            .mode
769            .expect("mode must be explicitly set on root state");
770        let mut root = State {
771            parent: None,
772            store: Some(OverlayStore::new(Box::new(root) as Box<dyn Store>)),
773            events: EventTags::new(),
774            unconditional_events: EventTags::new(),
775            messages: Vec::new(),
776            block_values: BTreeMap::new(),
777            hidden_block_values: None,
778            local_values: BTreeMap::new(),
779            rng: Some(RootRng::new(mode)),
780            hidden_rng: None,
781            env: Default::default(),
782            always_rollback: false,
783        };
784        // Apply options to allow customization of the root state.
785        root.init(opts);
786
787        CURRENT.with(|c| {
788            c.try_borrow_mut()
789                .expect("must not re-enter from with block")
790                .push(root)
791        });
792        let _guard = CurrentStateGuard; // Ensure current state is popped once we return.
793
794        f()
795    }
796
797    /// Create an empty baseline state for the current thread.
798    ///
799    /// This should only be used in tests to have state always available.
800    ///
801    /// # Panics
802    ///
803    /// This method will panic if any states have been attached to the local thread or if called
804    /// within a `CurrentState::with` block.
805    #[doc(hidden)]
806    pub(crate) fn init_local_fallback() {
807        thread_local! {
808            static BASE_STATE_INIT: RefCell<bool> = const { RefCell::new(false) };
809        }
810
811        BASE_STATE_INIT.with(|initialized| {
812            // Initialize once per thread.
813            if *initialized.borrow() {
814                return;
815            }
816            *initialized.borrow_mut() = true;
817
818            let root = mkvs::OverlayTree::new(
819                mkvs::Tree::builder()
820                    .with_root_type(mkvs::RootType::State)
821                    .build(Box::new(mkvs::sync::NoopReadSyncer)),
822            );
823            let root = MKVSStore::new(root);
824
825            // Initialize the root state.
826            let root = State {
827                parent: None,
828                store: Some(OverlayStore::new(Box::new(root) as Box<dyn Store>)),
829                events: EventTags::new(),
830                unconditional_events: EventTags::new(),
831                messages: Vec::new(),
832                block_values: BTreeMap::new(),
833                hidden_block_values: None,
834                local_values: BTreeMap::new(),
835                rng: Some(RootRng::new(Default::default())),
836                hidden_rng: None,
837                env: Default::default(),
838                always_rollback: false,
839            };
840
841            CURRENT.with(|c| {
842                let mut current = c
843                    .try_borrow_mut()
844                    .expect("must not re-enter from with block");
845                assert!(
846                    current.is_empty(),
847                    "must have no prior states attached to local thread"
848                );
849
850                current.push(root);
851            });
852        });
853    }
854
855    /// Run a closure with the currently active state.
856    ///
857    /// # Panics
858    ///
859    /// This method will panic if called outside `CurrentState::enter` or if any transaction methods
860    /// are called from the closure.
861    pub fn with<F, R>(f: F) -> R
862    where
863        F: FnOnce(&mut State) -> R,
864    {
865        CURRENT.with(|c| {
866            let mut current_ref = c.try_borrow_mut().expect("must not re-enter with");
867            let current = current_ref.last_mut().expect("must enter context");
868
869            f(current)
870        })
871    }
872
873    /// Run a closure with the store of the currently active state.
874    ///
875    /// # Panics
876    ///
877    /// This method will panic if called outside `CurrentState::enter` or if any transaction methods
878    /// are called from the closure.
879    pub fn with_store<F, R>(f: F) -> R
880    where
881        F: FnOnce(&mut dyn Store) -> R,
882    {
883        Self::with(|state| f(state.store()))
884    }
885
886    /// Run a closure with the environment of the currently active state.
887    ///
888    /// # Panics
889    ///
890    /// This method will panic if called outside `CurrentState::enter` or if any transaction methods
891    /// are called from the closure.
892    pub fn with_env<F, R>(f: F) -> R
893    where
894        F: FnOnce(&Environment) -> R,
895    {
896        Self::with(|state| f(state.env()))
897    }
898
899    /// Run a closure with the origin environment of the currently active state.
900    ///
901    /// # Panics
902    ///
903    /// This method will panic if called outside `CurrentState::enter` or if any transaction methods
904    /// are called from the closure.
905    pub fn with_env_origin<F, R>(f: F) -> R
906    where
907        F: FnOnce(&Environment) -> R,
908    {
909        Self::with(|state| f(state.env_origin()))
910    }
911
912    /// Start a new transaction by opening a new child state.
913    ///
914    /// # Panics
915    ///
916    /// This method will panic if called outside `CurrentState::enter` or if called within a
917    /// `CurrentState::with` block.
918    pub fn start_transaction() {
919        Self::with(|state| state.open());
920    }
921
922    /// Commit a previously started transaction.
923    ///
924    /// # Panics
925    ///
926    /// This method will panic if called outside `CurrentState::enter`, if there is no currently
927    /// open transaction (started via `CurrentState::start_transaction`) or if called within a
928    /// `CurrentState::with` block.
929    pub fn commit_transaction() {
930        Self::with(|state| state.commit());
931    }
932
933    /// Rollback a previously started transaction.
934    ///
935    /// # Panics
936    ///
937    /// This method will panic if called outside `CurrentState::enter`, if there is no currently
938    /// open transaction (started via `CurrentState::start_transaction`) or if called within a
939    /// `CurrentState::with` block.
940    pub fn rollback_transaction() {
941        Self::with(|state| state.rollback());
942    }
943
944    /// Run a closure within a state transaction.
945    ///
946    /// If the closure returns `TransactionResult::Commit(R)` then the child state is committed,
947    /// otherwise the child state is rolled back.
948    pub fn with_transaction<F, R, Rs>(f: F) -> R
949    where
950        F: FnOnce() -> Rs,
951        Rs: Into<TransactionResult<R>>,
952    {
953        Self::with_transaction_opts(Options::default(), f)
954    }
955
956    /// Run a closure within a state transaction, allowing the caller to customize state.
957    ///
958    /// If the closure returns `TransactionResult::Commit(R)` then the child state is committed,
959    /// otherwise the child state is rolled back.
960    pub fn with_transaction_opts<F, R, Rs>(opts: Options, f: F) -> R
961    where
962        F: FnOnce() -> Rs,
963        Rs: Into<TransactionResult<R>>,
964    {
965        let level = Self::with(|state| {
966            state.open();
967            state.init(opts);
968            state.level()
969        });
970        let _guard = TransactionGuard(level); // Ensure transaction is always closed.
971
972        match f().into() {
973            TransactionResult::Commit(result) => {
974                Self::commit_transaction();
975                result
976            }
977            TransactionResult::Rollback(result) => {
978                Self::rollback_transaction();
979                result
980            }
981        }
982    }
983}
984
985/// A per-state arbitrary value.
986pub struct StateValue<'a, V> {
987    inner: Entry<'a, &'static str, Box<dyn Any>>,
988    _value: PhantomData<V>,
989}
990
991impl<'a, V: Any> StateValue<'a, V> {
992    fn new(inner: Entry<'a, &'static str, Box<dyn Any>>) -> Self {
993        Self {
994            inner,
995            _value: PhantomData,
996        }
997    }
998
999    /// Gets a reference to the specified per-state value.
1000    ///
1001    /// # Panics
1002    ///
1003    /// Panics if the retrieved type is not the type that was stored.
1004    pub fn get(self) -> Option<&'a V> {
1005        match self.inner {
1006            Entry::Occupied(oe) => Some(
1007                oe.into_mut()
1008                    .downcast_ref()
1009                    .expect("type should stay the same"),
1010            ),
1011            _ => None,
1012        }
1013    }
1014
1015    /// Gets a mutable reference to the specified per-state value.
1016    ///
1017    /// # Panics
1018    ///
1019    /// Panics if the retrieved type is not the type that was stored.
1020    pub fn get_mut(&mut self) -> Option<&mut V> {
1021        match &mut self.inner {
1022            Entry::Occupied(oe) => Some(
1023                oe.get_mut()
1024                    .downcast_mut()
1025                    .expect("type should stay the same"),
1026            ),
1027            _ => None,
1028        }
1029    }
1030
1031    /// Sets the context value, returning a mutable reference to the set value.
1032    ///
1033    /// # Panics
1034    ///
1035    /// Panics if the retrieved type is not the type that was stored.
1036    pub fn set(self, value: V) -> &'a mut V {
1037        let value = Box::new(value);
1038        match self.inner {
1039            Entry::Occupied(mut oe) => {
1040                oe.insert(value);
1041                oe.into_mut()
1042            }
1043            Entry::Vacant(ve) => ve.insert(value),
1044        }
1045        .downcast_mut()
1046        .expect("type should stay the same")
1047    }
1048
1049    /// Takes the context value, if it exists.
1050    ///
1051    /// # Panics
1052    ///
1053    /// Panics if the retrieved type is not the type that was stored.
1054    pub fn take(self) -> Option<V> {
1055        match self.inner {
1056            Entry::Occupied(oe) => {
1057                Some(*oe.remove().downcast().expect("type should stay the same"))
1058            }
1059            Entry::Vacant(_) => None,
1060        }
1061    }
1062}
1063
1064impl<'a, V: Any + Default> StateValue<'a, V> {
1065    /// Retrieves the existing value or inserts and returns the default.
1066    ///
1067    /// # Panics
1068    ///
1069    /// Panics if the retrieved type is not the type that was stored.
1070    pub fn or_default(self) -> &'a mut V {
1071        match self.inner {
1072            Entry::Occupied(oe) => oe.into_mut(),
1073            Entry::Vacant(ve) => ve.insert(Box::<V>::default()),
1074        }
1075        .downcast_mut()
1076        .expect("type should stay the same")
1077    }
1078}
1079
1080#[cfg(test)]
1081mod test {
1082    use oasis_core_runtime::{
1083        common::versioned::Versioned,
1084        consensus::{roothash, staking},
1085        storage::mkvs,
1086    };
1087
1088    use super::{CurrentState, Mode, Options, TransactionResult, TransactionWithMeta};
1089    use crate::{
1090        modules::core::Event,
1091        storage::{MKVSStore, Store},
1092        testing::mock::{self, Mock},
1093        types::message::MessageEventHookInvocation,
1094    };
1095
1096    #[test]
1097    fn test_value() {
1098        CurrentState::init_local_fallback();
1099
1100        CurrentState::with(|state| {
1101            let x = state.block_value::<u64>("module.TestKey").get();
1102            assert_eq!(x, None);
1103
1104            state.block_value::<u64>("module.TestKey").set(42);
1105
1106            let y = state.block_value::<u64>("module.TestKey").get();
1107            assert_eq!(y, Some(&42u64));
1108
1109            let z = state.block_value::<u64>("module.TestKey").take();
1110            assert_eq!(z, Some(42u64));
1111
1112            let y = state.block_value::<u64>("module.TestKey").get();
1113            assert_eq!(y, None);
1114        });
1115    }
1116
1117    #[test]
1118    #[should_panic]
1119    fn test_value_type_change_block_value() {
1120        CurrentState::init_local_fallback();
1121
1122        CurrentState::with(|state| {
1123            state.block_value::<u64>("module.TestKey").or_default();
1124            state.block_value::<u32>("module.TestKey").get();
1125        });
1126    }
1127
1128    #[test]
1129    #[should_panic]
1130    fn test_value_type_change_local_value() {
1131        CurrentState::init_local_fallback();
1132
1133        CurrentState::with(|state| {
1134            state.local_value::<u64>("module.TestKey").or_default();
1135            state.local_value::<u32>("module.TestKey").get();
1136        });
1137    }
1138
1139    #[test]
1140    fn test_value_hidden_block_values() {
1141        CurrentState::init_local_fallback();
1142
1143        CurrentState::with(|state| {
1144            state.block_value("module.TestKey").set(42u64);
1145
1146            state.open();
1147            state.hide_block_values();
1148
1149            let v = state.block_value::<u64>("module.TestKey").get();
1150            assert!(v.is_none(), "block values should not propagate when hidden");
1151
1152            state.block_value("module.TestKey").set(48u64);
1153
1154            state.commit();
1155
1156            let v = state.block_value::<u64>("module.TestKey").get();
1157            assert_eq!(
1158                v,
1159                Some(&42u64),
1160                "block values should not propagate when hidden"
1161            );
1162        });
1163    }
1164
1165    #[test]
1166    fn test_value_local() {
1167        CurrentState::init_local_fallback();
1168
1169        CurrentState::with(|state| {
1170            state.block_value("module.TestKey").set(42u64);
1171
1172            state.open();
1173
1174            let mut y = state.block_value::<u64>("module.TestKey");
1175            let y = y.get_mut().unwrap();
1176            assert_eq!(*y, 42);
1177            *y = 48;
1178
1179            let a = state.local_value::<u64>("module.TestTxKey").get();
1180            assert_eq!(a, None);
1181            state.local_value::<u64>("module.TestTxKey").set(65);
1182
1183            let b = state.local_value::<u64>("module.TestTxKey").get();
1184            assert_eq!(b, Some(&65));
1185
1186            let c = state
1187                .local_value::<u64>("module.TestTakeTxKey")
1188                .or_default();
1189            *c = 67;
1190            let d = state.local_value::<u64>("module.TestTakeTxKey").take();
1191            assert_eq!(d, Some(67));
1192            let e = state.local_value::<u64>("module.TestTakeTxKey").get();
1193            assert_eq!(e, None);
1194
1195            state.rollback(); // Block values are always propagated.
1196
1197            let x = state.block_value::<u64>("module.TestKey").get();
1198            assert_eq!(x, Some(&48));
1199
1200            state.open();
1201
1202            let z = state.block_value::<u64>("module.TestKey").take();
1203            assert_eq!(z, Some(48));
1204
1205            let a = state.local_value::<u64>("module.TestTxKey").get();
1206            assert_eq!(a, None, "local values should not be propagated");
1207
1208            state.rollback(); // Block values are always propagated.
1209
1210            let y = state.block_value::<u64>("module.TestKey").get();
1211            assert_eq!(y, None);
1212        });
1213    }
1214
1215    #[test]
1216    fn test_emit_messages() {
1217        let mut mock = Mock::default(); // Also creates local fallback state.
1218        let max_messages = mock.max_messages as usize;
1219        let ctx = mock.create_ctx();
1220
1221        CurrentState::with(|state| {
1222            state.open();
1223
1224            assert_eq!(state.emitted_messages_count(), 0);
1225            assert_eq!(state.emitted_messages_local_count(), 0);
1226
1227            state
1228                .emit_message(
1229                    &ctx,
1230                    roothash::Message::Staking(Versioned::new(
1231                        0,
1232                        roothash::StakingMessage::Transfer(staking::Transfer::default()),
1233                    )),
1234                    MessageEventHookInvocation::new("test".to_string(), ""),
1235                )
1236                .expect("message emission should succeed");
1237            assert_eq!(state.emitted_messages_count(), 1);
1238            assert_eq!(state.emitted_messages_local_count(), 1);
1239            assert_eq!(state.emitted_messages_max(&ctx), max_messages as u32);
1240
1241            state.open(); // Start child state.
1242
1243            assert_eq!(state.emitted_messages_local_count(), 0);
1244
1245            state
1246                .emit_message(
1247                    &ctx,
1248                    roothash::Message::Staking(Versioned::new(
1249                        0,
1250                        roothash::StakingMessage::Transfer(staking::Transfer::default()),
1251                    )),
1252                    MessageEventHookInvocation::new("test".to_string(), ""),
1253                )
1254                .expect("message emission should succeed");
1255            assert_eq!(state.emitted_messages_count(), 2);
1256            assert_eq!(state.emitted_messages_local_count(), 1);
1257            assert_eq!(state.emitted_messages_max(&ctx), max_messages as u32);
1258
1259            state.rollback(); // Rollback.
1260
1261            assert_eq!(
1262                state.emitted_messages_count(),
1263                1,
1264                "emitted message should have been rolled back"
1265            );
1266            assert_eq!(state.emitted_messages_local_count(), 1);
1267
1268            state.open(); // Start child state.
1269
1270            assert_eq!(state.emitted_messages_local_count(), 0);
1271
1272            state
1273                .emit_message(
1274                    &ctx,
1275                    roothash::Message::Staking(Versioned::new(
1276                        0,
1277                        roothash::StakingMessage::Transfer(staking::Transfer::default()),
1278                    )),
1279                    MessageEventHookInvocation::new("test".to_string(), ""),
1280                )
1281                .expect("message emission should succeed");
1282            assert_eq!(state.emitted_messages_count(), 2);
1283            assert_eq!(state.emitted_messages_local_count(), 1);
1284
1285            state.commit(); // Commit.
1286
1287            assert_eq!(
1288                state.emitted_messages_count(),
1289                2,
1290                "emitted message should have been committed"
1291            );
1292
1293            // Emit some more messages.
1294            for _ in 0..max_messages - 2 {
1295                state
1296                    .emit_message(
1297                        &ctx,
1298                        roothash::Message::Staking(Versioned::new(
1299                            0,
1300                            roothash::StakingMessage::Transfer(staking::Transfer::default()),
1301                        )),
1302                        MessageEventHookInvocation::new("test".to_string(), ""),
1303                    )
1304                    .expect("message emission should succeed");
1305            }
1306            assert_eq!(state.emitted_messages_count(), max_messages);
1307
1308            // Emitting one more message should be rejected.
1309            state
1310                .emit_message(
1311                    &ctx,
1312                    roothash::Message::Staking(Versioned::new(
1313                        0,
1314                        roothash::StakingMessage::Transfer(staking::Transfer::default()),
1315                    )),
1316                    MessageEventHookInvocation::new("test".to_string(), ""),
1317                )
1318                .expect_err("message emission should fail due to out of slots");
1319            assert_eq!(state.emitted_messages_count(), max_messages);
1320
1321            state.rollback(); // Rollback.
1322
1323            assert_eq!(state.emitted_messages_count(), 0);
1324        });
1325
1326        // Change the maximum amount of messages.
1327        let mut tx = mock::transaction();
1328        tx.auth_info.fee.consensus_messages = 1; // Limit amount of messages.
1329        CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || {
1330            CurrentState::with(|state| {
1331                assert_eq!(state.emitted_messages_max(&ctx), 1);
1332            });
1333        });
1334
1335        let mut tx = mock::transaction();
1336        tx.auth_info.fee.consensus_messages = 0; // Zero means an implicit limit by gas use.
1337        CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || {
1338            CurrentState::with(|state| {
1339                assert_eq!(state.emitted_messages_max(&ctx), max_messages as u32);
1340            });
1341        });
1342    }
1343
1344    #[test]
1345    fn test_emit_events() {
1346        CurrentState::init_local_fallback();
1347
1348        CurrentState::with(|state| {
1349            state.open();
1350
1351            state.open();
1352
1353            state.emit_event(Event::GasUsed { amount: 41 });
1354            state.emit_event(Event::GasUsed { amount: 42 });
1355            state.emit_event(Event::GasUsed { amount: 43 });
1356
1357            state.emit_unconditional_event(Event::GasUsed { amount: 10 });
1358
1359            state.commit();
1360
1361            let events = state.take_events();
1362            assert_eq!(events.len(), 1, "events should have been propagated");
1363            let event_key = b"core\x00\x00\x00\x01".to_vec();
1364            assert_eq!(events[&event_key].len(), 3);
1365
1366            let events = state.take_unconditional_events();
1367            assert_eq!(
1368                events.len(),
1369                1,
1370                "unconditional events should have been propagated"
1371            );
1372            let event_key = b"core\x00\x00\x00\x01".to_vec();
1373            assert_eq!(events[&event_key].len(), 1);
1374
1375            state.emit_event(Event::GasUsed { amount: 41 });
1376            state.emit_event(Event::GasUsed { amount: 42 });
1377            state.emit_event(Event::GasUsed { amount: 43 });
1378
1379            state.emit_unconditional_event(Event::GasUsed { amount: 20 });
1380
1381            state.rollback();
1382
1383            let events = state.take_events();
1384            assert_eq!(events.len(), 0, "events should not have been propagated");
1385
1386            let events = state.take_unconditional_events();
1387            assert_eq!(
1388                events.len(),
1389                0,
1390                "unconditional events should not have been propagated"
1391            );
1392        });
1393    }
1394
1395    fn test_store_basic() {
1396        CurrentState::start_transaction();
1397
1398        assert!(
1399            !CurrentState::with(|state| state.has_pending_store_updates()),
1400            "should not have pending updates"
1401        );
1402
1403        CurrentState::with_store(|store| {
1404            store.insert(b"test", b"value");
1405        });
1406
1407        assert!(
1408            CurrentState::with(|state| state.has_pending_store_updates()),
1409            "should have pending updates after insert"
1410        );
1411
1412        // Transaction helper.
1413        CurrentState::with_transaction(|| {
1414            assert!(
1415                !CurrentState::with(|state| state.has_pending_store_updates()),
1416                "should not have pending updates"
1417            );
1418
1419            CurrentState::with_store(|store| {
1420                store.insert(b"test", b"b0rken");
1421            });
1422
1423            assert!(
1424                CurrentState::with(|state| state.has_pending_store_updates()),
1425                "should have pending updates after insert"
1426            );
1427
1428            TransactionResult::Rollback(())
1429        });
1430
1431        // Transaction helper with options.
1432        CurrentState::with_transaction_opts(
1433            Options::new()
1434                .with_mode(Mode::Check)
1435                .with_internal(true)
1436                .with_tx(TransactionWithMeta {
1437                    data: mock::transaction(),
1438                    size: 888,
1439                    index: 42,
1440                    hash: Default::default(),
1441                }),
1442            || {
1443                CurrentState::with_env(|env| {
1444                    assert!(env.is_check_only(), "environment should be updated");
1445                    assert!(env.is_internal(), "environment should be updated");
1446                    assert!(env.is_transaction(), "environment should be updated");
1447                    assert_eq!(env.tx_index(), 42, "environment should be updated");
1448                    assert_eq!(env.tx_size(), 888, "environment should be updated");
1449                });
1450
1451                CurrentState::with_env_origin(|env_origin| {
1452                    assert!(
1453                        !env_origin.is_check_only(),
1454                        "origin environment should be correct"
1455                    );
1456                    assert!(
1457                        !env_origin.is_transaction(),
1458                        "origin environment should be correct"
1459                    );
1460                });
1461
1462                CurrentState::with_transaction(|| {
1463                    // Check environment propagation.
1464                    CurrentState::with_env(|env| {
1465                        assert!(env.is_check_only(), "environment should propagate");
1466                        assert!(env.is_internal(), "environment should propagate");
1467                        assert!(env.is_transaction(), "environment should propagate");
1468                        assert_eq!(env.tx_index(), 42, "environment should propagate");
1469                        assert_eq!(env.tx_size(), 888, "environment should propagate");
1470                    });
1471
1472                    TransactionResult::Rollback(())
1473                });
1474
1475                TransactionResult::Rollback(())
1476            },
1477        );
1478
1479        CurrentState::with_env(|env| {
1480            assert!(!env.is_transaction(), "environment should not leak");
1481        });
1482
1483        // Nested entering, but with a different store.
1484        let unrelated = mkvs::OverlayTree::new(
1485            mkvs::Tree::builder()
1486                .with_root_type(mkvs::RootType::State)
1487                .build(Box::new(mkvs::sync::NoopReadSyncer)),
1488        );
1489        let mut unrelated = MKVSStore::new(unrelated);
1490
1491        CurrentState::enter(&mut unrelated, || {
1492            CurrentState::start_transaction();
1493
1494            CurrentState::with_store(|store| {
1495                store.insert(b"test", b"should not touch the original root");
1496            });
1497
1498            CurrentState::commit_transaction();
1499        });
1500
1501        CurrentState::with_store(|store| {
1502            store.insert(b"another", b"value 2");
1503        });
1504
1505        CurrentState::commit_transaction();
1506    }
1507
1508    #[test]
1509    fn test_basic() {
1510        let root = mkvs::OverlayTree::new(
1511            mkvs::Tree::builder()
1512                .with_root_type(mkvs::RootType::State)
1513                .build(Box::new(mkvs::sync::NoopReadSyncer)),
1514        );
1515        let mut root = MKVSStore::new(root);
1516
1517        CurrentState::enter(&mut root, || {
1518            test_store_basic();
1519        });
1520
1521        let value = root.get(b"test").unwrap();
1522        assert_eq!(value, b"value");
1523    }
1524
1525    #[test]
1526    fn test_local_fallback() {
1527        // Initialize the local fallback store.
1528        CurrentState::init_local_fallback();
1529        CurrentState::init_local_fallback(); // Should be no-op.
1530
1531        // Test the basic store -- note, no need to enter as fallback current store is available.
1532        test_store_basic();
1533
1534        CurrentState::with_store(|store| {
1535            let value = store.get(b"test").unwrap();
1536            assert_eq!(value, b"value");
1537        });
1538
1539        // It should be possible to override the fallback by entering explicitly.
1540        let root = mkvs::OverlayTree::new(
1541            mkvs::Tree::builder()
1542                .with_root_type(mkvs::RootType::State)
1543                .build(Box::new(mkvs::sync::NoopReadSyncer)),
1544        );
1545        let mut root = MKVSStore::new(root);
1546
1547        CurrentState::enter(&mut root, || {
1548            CurrentState::with_store(|store| {
1549                assert!(store.get(b"test").is_none(), "store should be empty");
1550                store.insert(b"unrelated", b"unrelated");
1551            });
1552
1553            test_store_basic();
1554        });
1555
1556        let value = root.get(b"test").unwrap();
1557        assert_eq!(value, b"value");
1558        let value = root.get(b"unrelated").unwrap();
1559        assert_eq!(value, b"unrelated");
1560
1561        // Changes should not leak to fallback store.
1562        CurrentState::with_store(|store| {
1563            assert!(store.get(b"unrelated").is_none(), "changes should not leak");
1564        });
1565    }
1566
1567    #[test]
1568    #[should_panic(expected = "must enter context")]
1569    fn test_fail_not_entered() {
1570        test_store_basic(); // Should panic due to no current store being available.
1571    }
1572
1573    #[test]
1574    #[should_panic(expected = "must not re-enter with")]
1575    fn test_fail_reenter_with() {
1576        CurrentState::init_local_fallback();
1577
1578        CurrentState::with(|_| {
1579            CurrentState::with(|_| {
1580                // Should panic.
1581            });
1582        });
1583    }
1584
1585    #[test]
1586    #[should_panic(expected = "must not re-enter with")]
1587    fn test_fail_reenter_with_start_transaction() {
1588        CurrentState::init_local_fallback();
1589
1590        CurrentState::with(|_| {
1591            CurrentState::start_transaction(); // Should panic.
1592        });
1593    }
1594
1595    #[test]
1596    #[should_panic(expected = "must not re-enter with")]
1597    fn test_fail_reenter_with_commit_transaction() {
1598        CurrentState::init_local_fallback();
1599
1600        CurrentState::with(|_| {
1601            CurrentState::commit_transaction(); // Should panic.
1602        });
1603    }
1604
1605    #[test]
1606    #[should_panic(expected = "must not re-enter with")]
1607    fn test_fail_reenter_with_rollback_transaction() {
1608        CurrentState::init_local_fallback();
1609
1610        CurrentState::with(|_| {
1611            CurrentState::rollback_transaction(); // Should panic.
1612        });
1613    }
1614
1615    #[test]
1616    #[should_panic(expected = "must not re-enter from with block")]
1617    fn test_fail_reenter_with_enter() {
1618        CurrentState::init_local_fallback();
1619
1620        CurrentState::with(|_| {
1621            let unrelated = mkvs::OverlayTree::new(
1622                mkvs::Tree::builder()
1623                    .with_root_type(mkvs::RootType::State)
1624                    .build(Box::new(mkvs::sync::NoopReadSyncer)),
1625            );
1626            let mut unrelated = MKVSStore::new(unrelated);
1627
1628            CurrentState::enter(&mut unrelated, || {
1629                // Should panic.
1630            });
1631        });
1632    }
1633
1634    #[test]
1635    #[should_panic(expected = "must not re-enter from with block")]
1636    fn test_fail_local_fallback_within_with() {
1637        let root = mkvs::OverlayTree::new(
1638            mkvs::Tree::builder()
1639                .with_root_type(mkvs::RootType::State)
1640                .build(Box::new(mkvs::sync::NoopReadSyncer)),
1641        );
1642        let mut root = MKVSStore::new(root);
1643
1644        CurrentState::enter(&mut root, || {
1645            CurrentState::with(|_| {
1646                CurrentState::init_local_fallback(); // Should panic.
1647            })
1648        });
1649    }
1650
1651    #[test]
1652    #[should_panic(expected = "must have no prior states attached to local thread")]
1653    fn test_fail_local_fallback_within_enter() {
1654        let root = mkvs::OverlayTree::new(
1655            mkvs::Tree::builder()
1656                .with_root_type(mkvs::RootType::State)
1657                .build(Box::new(mkvs::sync::NoopReadSyncer)),
1658        );
1659        let mut root = MKVSStore::new(root);
1660
1661        CurrentState::enter(&mut root, || {
1662            CurrentState::init_local_fallback(); // Should panic.
1663        });
1664    }
1665
1666    #[test]
1667    #[should_panic(expected = "cannot commit on root state")]
1668    fn test_fail_commit_transaction_must_exist() {
1669        CurrentState::init_local_fallback();
1670
1671        CurrentState::commit_transaction(); // Should panic.
1672    }
1673
1674    #[test]
1675    #[should_panic(expected = "cannot rollback on root state")]
1676    fn test_fail_rollback_transaction_must_exist() {
1677        CurrentState::init_local_fallback();
1678
1679        CurrentState::rollback_transaction(); // Should panic.
1680    }
1681}