essential_vm/
lib.rs

1//! The essential VM implementation.
2//!
3//! ## Reading State
4//!
5//! The primary entrypoint for this crate is the [`Vm` type][Vm].
6//!
7//! The `Vm` allows for executing arbitrary [essential ASM][asm] ops.
8//! The primary use-case is executing [`Program`][essential_types::predicate::Program]s
9//! that make up a [`Predicate`][essential_types::predicate::Predicate]'s program graph
10//! during [`Solution`][essential_types::solution::Solution] validation.
11//!
12//! ## Executing Ops
13//!
14//! There are two primary methods available for executing operations:
15//!
16//! - [`Vm::exec_ops`]
17//! - [`Vm::exec_bytecode`]
18//!
19//! Each have slightly different performance implications, so be sure to read
20//! the docs before selecting a method.
21//!
22//! ## Execution Future
23//!
24//! The `Vm::exec_*` functions all return `Future`s that not only yield on
25//! async operations, but yield based on a user-specified gas limit too. See the
26//! [`ExecFuture`] docs for further details on the implementation.
27#![deny(missing_docs, unsafe_code)]
28
29pub use access::Access;
30pub use cached::LazyCache;
31#[doc(inline)]
32pub use essential_asm::{self as asm, Op};
33pub use essential_types as types;
34#[doc(inline)]
35pub use memory::Memory;
36#[doc(inline)]
37pub use op_access::OpAccess;
38#[doc(inline)]
39pub use repeat::Repeat;
40#[doc(inline)]
41pub use stack::Stack;
42#[doc(inline)]
43pub use state_read::StateRead;
44#[doc(inline)]
45pub use state_read::StateReads;
46#[doc(inline)]
47pub use total_control_flow::ProgramControlFlow;
48#[doc(inline)]
49pub use vm::Vm;
50
51mod access;
52mod alu;
53pub mod bytecode;
54mod cached;
55mod compute;
56mod crypto;
57pub mod error;
58mod memory;
59mod op_access;
60mod pred;
61mod repeat;
62mod sets;
63mod stack;
64mod state_read;
65pub mod sync;
66mod total_control_flow;
67mod vm;
68
69#[cfg(test)]
70pub(crate) mod utils {
71    use crate::{StateRead, StateReads};
72
73    pub struct EmptyState;
74    impl StateRead for EmptyState {
75        type Error = String;
76
77        fn key_range(
78            &self,
79            _contract_addr: essential_types::ContentAddress,
80            _key: essential_types::Key,
81            _num_values: usize,
82        ) -> Result<Vec<Vec<essential_asm::Word>>, Self::Error> {
83            Ok(vec![])
84        }
85    }
86
87    impl StateReads for EmptyState {
88        type Error = String;
89        type Pre = Self;
90        type Post = Self;
91
92        fn pre(&self) -> &Self::Pre {
93            self
94        }
95
96        fn post(&self) -> &Self::Post {
97            self
98        }
99    }
100}
101
102/// Shorthand for the `BytecodeMapped` type representing a mapping to/from [`Op`]s.
103pub type BytecodeMapped<Bytes = Vec<u8>> = bytecode::BytecodeMapped<Op, Bytes>;
104/// Shorthand for the `BytecodeMappedSlice` type for mapping [`Op`]s.
105pub type BytecodeMappedSlice<'a> = bytecode::BytecodeMappedSlice<'a, Op>;
106
107/// Unit used to measure gas.
108pub type Gas = u64;
109
110/// Gas limits.
111#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
112pub struct GasLimit {
113    /// The amount that may be spent synchronously until the execution future should yield.
114    pub per_yield: Gas,
115    /// The total amount of gas that may be spent.
116    pub total: Gas,
117}
118
119/// A mapping from an operation to its gas cost.
120pub trait OpGasCost: Send + Sync {
121    /// The gas cost associated with the given op.
122    fn op_gas_cost(&self, op: &Op) -> Gas;
123}
124
125impl GasLimit {
126    /// The default value used for the `per_yield` limit.
127    // TODO: Adjust this to match recommended poll time limit on supported validator
128    // hardware.
129    pub const DEFAULT_PER_YIELD: Gas = 4_096;
130
131    /// Unlimited gas limit with default gas-per-yield.
132    pub const UNLIMITED: Self = Self {
133        per_yield: Self::DEFAULT_PER_YIELD,
134        total: Gas::MAX,
135    };
136}
137
138impl<F> OpGasCost for F
139where
140    F: Fn(&Op) -> Gas + Send + Sync,
141{
142    fn op_gas_cost(&self, op: &Op) -> Gas {
143        (*self)(op)
144    }
145}
146
147/// Trace the operation at the given program counter.
148///
149/// In the success case, also emits the resulting stack.
150///
151/// In the error case, emits a debug log with the error.
152#[cfg(feature = "tracing")]
153pub(crate) fn trace_op_res<OA, T, E>(
154    oa: &OA,
155    pc: usize,
156    stack: &Stack,
157    memory: &Memory,
158    parent_memory: &Vec<std::sync::Arc<Memory>>,
159    halt: bool,
160    op_res: &Result<T, E>,
161) where
162    OA: OpAccess,
163    OA::Op: core::fmt::Debug,
164    E: core::fmt::Display,
165{
166    let op = oa
167        .op_access(pc)
168        .expect("must exist as retrieved previously")
169        .expect("must exist as retrieved previously");
170    let pc_op = format!("0x{:02X}: {op:?}", pc);
171    match op_res {
172        Ok(_) => {
173            if parent_memory.is_empty() {
174                tracing::trace!("{pc_op}\n  ├── {:?}\n  └── {:?}", stack, memory)
175            } else {
176                tracing::trace!(
177                    "{pc_op}\n  ├── {:?}\n  ├── {:?}\n  ├── {:?}\n  └── {:?}",
178                    stack,
179                    memory,
180                    parent_memory,
181                    halt
182                )
183            }
184        }
185        Err(ref err) => {
186            tracing::trace!("{pc_op}");
187            tracing::debug!("{err}");
188        }
189    }
190}