entropy_programs_runtime/
lib.rs

1//! Contains the Wasm runtime and related types for evaluating programs.
2
3use thiserror::Error;
4use wasmtime::{
5    component::{bindgen, Component, Linker},
6    Config as WasmtimeConfig, Engine, Result, Store,
7};
8
9/// Note, this is wasmtime's bindgen, not wit-bindgen (modules)
10mod bindgen {
11    use super::bindgen;
12
13    bindgen!({
14        world: "program",
15    });
16}
17pub use bindgen::{Error as ProgramError, Program, SignatureRequest};
18
19/// Runtime `Error` type
20#[derive(Debug, Error)]
21pub enum RuntimeError {
22    /// Program bytecode is of zero length (core-side runtime error; Programs should probably not return this)
23    #[error("Bytecode length is zero")]
24    EmptyBytecode,
25    /// Program bytecode is not a valid WebAssembly component.
26    #[error("Invalid bytecode")]
27    InvalidBytecode,
28    /// Program error during execution.
29    #[error("Runtime error: {0}")]
30    Runtime(ProgramError),
31    /// Program exceeded fuel limits. Execute fewer instructions.
32    #[error("Out of fuel")]
33    OutOfFuel,
34}
35
36/// Config is for runtime parameters (eg instructions per program, additional runtime interfaces, etc).
37pub struct Config {
38    /// Max number of instructions the runtime will execute before returning an error.
39    pub fuel: u64,
40}
41
42impl Default for Config {
43    fn default() -> Self {
44        Self { fuel: 10_000 }
45    }
46}
47
48/// Runtime allows for the execution of programs. Instantiate with `Runtime::new()`.
49pub struct Runtime {
50    engine: Engine,
51    linker: Linker<()>,
52    store: Store<()>,
53}
54
55impl Default for Runtime {
56    fn default() -> Self {
57        Self::new(Config::default())
58    }
59}
60
61impl Runtime {
62    pub fn new(config: Config) -> Self {
63        let mut wasmtime_config = WasmtimeConfig::new();
64        wasmtime_config
65            .wasm_component_model(true)
66            .consume_fuel(true);
67
68        let engine = Engine::new(&wasmtime_config).unwrap();
69        let linker = Linker::new(&engine);
70        let mut store = Store::new(&engine, ());
71
72        store.add_fuel(config.fuel).unwrap();
73        Self {
74            engine,
75            linker,
76            store,
77        }
78    }
79}
80
81impl Runtime {
82    /// Evaluate a program with a given initial state.
83    pub fn evaluate(
84        &mut self,
85        program: &[u8],
86        signature_request: &SignatureRequest,
87        config: Option<&[u8]>,
88        oracle_data: Option<&[Vec<u8>]>,
89    ) -> Result<(), RuntimeError> {
90        if program.len() == 0 {
91            return Err(RuntimeError::EmptyBytecode);
92        }
93
94        let component = Component::from_binary(&self.engine, program)
95            .map_err(|_| RuntimeError::InvalidBytecode)?;
96        let (bindings, _) = Program::instantiate(&mut self.store, &component, &self.linker)
97            .map_err(|_| RuntimeError::InvalidBytecode)?;
98
99        bindings
100            .call_evaluate(&mut self.store, signature_request, config, oracle_data)
101            .map_err(|_| RuntimeError::OutOfFuel)?
102            .map_err(RuntimeError::Runtime)
103    }
104
105    /// Compute the `custom-hash` of a `message` from the program.
106    pub fn custom_hash(
107        &mut self,
108        program: &[u8],
109        message: &[u8],
110    ) -> Result<[u8; 32], RuntimeError> {
111        if program.len() == 0 {
112            return Err(RuntimeError::EmptyBytecode);
113        }
114
115        let component = Component::from_binary(&self.engine, program)
116            .map_err(|_| RuntimeError::InvalidBytecode)?;
117        let (bindings, _) = Program::instantiate(&mut self.store, &component, &self.linker)
118            .map_err(|_| RuntimeError::InvalidBytecode)?;
119
120        let hash_as_vec = bindings
121            .call_custom_hash(&mut self.store, message)
122            .unwrap().ok_or(RuntimeError::Runtime(ProgramError::InvalidSignatureRequest("`custom-hash` returns `None`. Implement the hash function in your program, or select a predefined `hash` in your signature request.".to_string())))?;
123        if hash_as_vec.len() != 32 {
124            return Err(RuntimeError::Runtime(
125                ProgramError::InvalidSignatureRequest(format!(
126                    "`custom-hash` must returns a Vec<u8> of length 32, not {}.",
127                    hash_as_vec.len()
128                )),
129            ));
130        }
131
132        let mut hash = [0u8; 32];
133        hash.copy_from_slice(&hash_as_vec);
134        Ok(hash)
135    }
136}