essential_vm/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
//! The essential VM implementation.
//!
//! ## Reading State
//!
//! The primary entrypoint for this crate is the [`Vm` type][Vm].
//!
//! The `Vm` allows for executing arbitrary [essential ASM][asm] ops.
//! The primary use-case is executing [`Program`][essential_types::predicate::Program]s
//! that make up a [`Predicate`][essential_types::predicate::Predicate]'s program graph
//! during [`Solution`][essential_types::solution::Solution] validation.
//!
//! ## Executing Ops
//!
//! There are three primary methods available for executing operations:
//!
//! - [`Vm::exec_ops`]
//! - [`Vm::exec_bytecode`]
//! - [`Vm::exec_bytecode_iter`]
//!
//! Each have slightly different performance implications, so be sure to read
//! the docs before selecting a method.
//!
//! ## Execution Future
//!
//! The `Vm::exec_*` functions all return `Future`s that not only yield on
//! async operations, but yield based on a user-specified gas limit too. See the
//! [`ExecFuture`] docs for further details on the implementation.
#![deny(missing_docs, unsafe_code)]

pub use access::{mut_keys, mut_keys_set, mut_keys_slices, Access};
pub use cached::LazyCache;
#[doc(inline)]
pub use essential_asm::{self as asm, Op};
pub use essential_types as types;
use essential_types::ContentAddress;
#[doc(inline)]
pub use future::ExecFuture;
#[doc(inline)]
pub use memory::Memory;
#[doc(inline)]
pub use op_access::OpAccess;
#[doc(inline)]
pub use repeat::Repeat;
#[doc(inline)]
pub use stack::Stack;
#[doc(inline)]
pub use state_read::StateRead;
#[doc(inline)]
pub use total_control_flow::ProgramControlFlow;
#[doc(inline)]
pub use vm::Vm;

mod access;
mod alu;
pub mod bytecode;
mod cached;
mod crypto;
pub mod error;
mod future;
mod memory;
mod op_access;
mod pred;
mod repeat;
mod sets;
mod stack;
mod state_read;
pub mod sync;
mod total_control_flow;
mod vm;

/// Shorthand for the `BytecodeMapped` type representing a mapping to/from [`Op`]s.
pub type BytecodeMapped<Bytes = Vec<u8>> = bytecode::BytecodeMapped<Op, Bytes>;
/// Shorthand for the `BytecodeMappedSlice` type for mapping [`Op`]s.
pub type BytecodeMappedSlice<'a> = bytecode::BytecodeMappedSlice<'a, Op>;
/// Shorthand for the `BytecodeMappedLazy` type for mapping [`Op`]s.
pub type BytecodeMappedLazy<I> = bytecode::BytecodeMappedLazy<Op, I>;

/// Unit used to measure gas.
pub type Gas = u64;

/// Gas limits.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct GasLimit {
    /// The amount that may be spent synchronously until the execution future should yield.
    pub per_yield: Gas,
    /// The total amount of gas that may be spent.
    pub total: Gas,
}

/// Distinguish between sync and async ops to ease `Future` implementation.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub(crate) enum OpKind {
    /// Operations that yield immediately.
    Sync(OpSync),
    /// Operations returning a future.
    Async(OpAsync),
}

/// The set of operations that are performed asynchronously.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct OpAsync(asm::StateRead);

/// The set of operations that are performed synchronously.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub enum OpSync {
    /// `[asm::Access]` operations.
    Access(asm::Access),
    /// `[asm::Alu]` operations.
    Alu(asm::Alu),
    /// `[asm::TotalControlFlow]` operations.
    ControlFlow(asm::TotalControlFlow),
    /// `[asm::Crypto]` operations.
    Crypto(asm::Crypto),
    /// `[asm::Memory]` operations.
    Memory(asm::Memory),
    /// `[asm::Pred]` operations.
    Pred(asm::Pred),
    /// `[asm::Stack]` operations.
    Stack(asm::Stack),
}

/// A mapping from an operation to its gas cost.
pub trait OpGasCost {
    /// The gas cost associated with the given op.
    fn op_gas_cost(&self, op: &Op) -> Gas;
}

impl GasLimit {
    /// The default value used for the `per_yield` limit.
    // TODO: Adjust this to match recommended poll time limit on supported validator
    // hardware.
    pub const DEFAULT_PER_YIELD: Gas = 4_096;

    /// Unlimited gas limit with default gas-per-yield.
    pub const UNLIMITED: Self = Self {
        per_yield: Self::DEFAULT_PER_YIELD,
        total: Gas::MAX,
    };
}

impl From<Op> for OpKind {
    fn from(op: Op) -> Self {
        match op {
            Op::Access(op) => OpKind::Sync(OpSync::Access(op)),
            Op::Alu(op) => OpKind::Sync(OpSync::Alu(op)),
            Op::Crypto(op) => OpKind::Sync(OpSync::Crypto(op)),
            Op::Memory(op) => OpKind::Sync(OpSync::Memory(op)),
            Op::Pred(op) => OpKind::Sync(OpSync::Pred(op)),
            Op::Stack(op) => OpKind::Sync(OpSync::Stack(op)),
            Op::StateRead(op) => OpKind::Async(OpAsync(op)),
            Op::TotalControlFlow(op) => OpKind::Sync(OpSync::ControlFlow(op)),
        }
    }
}

impl<F> OpGasCost for F
where
    F: Fn(&Op) -> Gas,
{
    fn op_gas_cost(&self, op: &Op) -> Gas {
        (*self)(op)
    }
}

/// Trace the operation at the given program counter.
///
/// In the success case, also emits the resulting stack.
///
/// In the error case, emits a debug log with the error.
#[cfg(feature = "tracing")]
pub(crate) fn trace_op_res<OA, T, E>(
    oa: &mut OA,
    pc: usize,
    stack: &Stack,
    memory: &Memory,
    op_res: Result<T, E>,
) where
    OA: OpAccess,
    OA::Op: core::fmt::Debug,
    E: core::fmt::Display,
{
    let op = oa
        .op_access(pc)
        .expect("must exist as retrieved previously")
        .expect("must exist as retrieved previously");
    let pc_op = format!("0x{:02X}: {op:?}", pc);
    match op_res {
        Ok(_) => {
            tracing::trace!("{pc_op}\n  ├── {:?}\n  └── {:?}", stack, memory)
        }
        Err(ref err) => {
            tracing::trace!("{pc_op}");
            tracing::debug!("{err}");
        }
    }
}