From 0db40eadc03948a9d41b5c0fbc8b078b5ab92339 Mon Sep 17 00:00:00 2001 From: JesseAbram <33698952+JesseAbram@users.noreply.github.com> Date: Fri, 5 Jan 2024 14:06:21 -0800 Subject: [PATCH] Infinte loop (#47) * add consume fuel to config * add fuel * increase add fuel * quick fix * added Config for Runtime * add infinite loop example * add error * infinite loop test * update infinite loop * rename * fix tests * clarified new fuel error variant * updated readme, cleanup --------- Co-authored-by: jakehemmerle --- README.md | 21 +++++++++----- examples/infinite-loop/Cargo.toml | 26 +++++++++++++++++ examples/infinite-loop/src/lib.rs | 27 +++++++++++++++++ runtime/README.md | 2 +- runtime/src/lib.rs | 48 ++++++++++++++++++++++--------- runtime/tests/runtime.rs | 29 +++++++++++++++---- 6 files changed, 126 insertions(+), 27 deletions(-) create mode 100644 examples/infinite-loop/Cargo.toml create mode 100644 examples/infinite-loop/src/lib.rs diff --git a/README.md b/README.md index fae340f..a606f7e 100644 --- a/README.md +++ b/README.md @@ -19,19 +19,27 @@ cargo install cargo-component --version 0.2.0 && cargo install wasm-tools ``` -## Example Program: `barebones` +## Example Program: `template-barebones` -To get started, clone this repository and build the example `barebones` program: +An example of a barebones program is at [`examples/barebones/src/lib.rs`](./examples/barebones/src/lib.rs). This example does a simple check on the length of the message to be signed. + +You can compile the program by running: ```bash -git clone https://github.com/entropyxyz/constraints -cd constraints cargo component build --release -p template-barebones --target wasm32-unknown-unknown ``` -This creates the program as a Wasm component at `target/wasm32-unknown-unknown/release/template_barebones.wasm`. +This builds the program as a Wasm component at `target/wasm32-unknown-unknown/release/template_barebones.wasm`. + +## Running Tests -Since this program is used in tests for the program runtime (`ec-runtime`), you can see the program get used by running `cargo test -p ec-runtime`. +Before running the runtime tests, you need to build the `template-barebones` and `infinite-loop` components. To do this, execute: + +```bash +cargo component build --release -p template-barebones -p infinite-loop --target wasm32-unknown-unknown` +``` + +This will create the components in `target/wasm32-unknown-unknown/release/`. ## Licensing @@ -43,4 +51,3 @@ There are some exceptions however: `Apache License 2.0`. Modifications made by Entropy to these crates are licensed under `AGPL-3.0`. - diff --git a/examples/infinite-loop/Cargo.toml b/examples/infinite-loop/Cargo.toml new file mode 100644 index 0000000..040f2a3 --- /dev/null +++ b/examples/infinite-loop/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "infinite-loop" +version = "0.1.0" +authors = ["Entropy Cryptography "] +homepage = "https://entropy.xyz/" +license = "Unlicense" +repository = "https://github.com/entropyxyz/constraints" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +# This is required to compile constraints to a wasm module +[lib] +crate-type = ["cdylib"] + +[dependencies] +ec-core = { workspace = true } + +# These are used by `cargo component` +[package.metadata.component] +package = "entropy:infinite-loop" + +[package.metadata.component.target] +path = "../../wit" + +[package.metadata.component.dependencies] diff --git a/examples/infinite-loop/src/lib.rs b/examples/infinite-loop/src/lib.rs new file mode 100644 index 0000000..54468a1 --- /dev/null +++ b/examples/infinite-loop/src/lib.rs @@ -0,0 +1,27 @@ +#![no_std] + +extern crate alloc; +use alloc::vec::Vec; +use ec_core::{bindgen::Error, bindgen::*, export_program, prelude::*}; + +// TODO confirm this isn't an issue for audit +register_custom_getrandom!(always_fail); + +pub struct InfiniteLoop; + +impl Program for InfiniteLoop { + /// This is the only function required by the program runtime. `message` is the preimage of the curve element to be + /// signed, eg. RLP-serialized Ethereum transaction request, raw x86_64 executable, etc. + fn evaluate(_signature_request: SignatureRequest) -> Result<(), Error> { + loop {} + #[allow(unreachable_code)] + Ok(()) + } + + /// Since we don't use a custom hash function, we can just return `None` here. + fn custom_hash(_data: Vec) -> Option> { + None + } +} + +export_program!(InfiniteLoop); diff --git a/runtime/README.md b/runtime/README.md index 13fdfa1..3df87fd 100644 --- a/runtime/README.md +++ b/runtime/README.md @@ -4,4 +4,4 @@ This contains the Wasm runtime for evaluaing, testing, and simulating constraint ## Running Tests -Before running the tests, you need to build the `barebones` component. Be sure to have `cargo component` installed, and run `cargo component build --release -p template-barebones --target wasm32-unknown-unknown`. This will create the files needed for testing at `target/wasm32-unknown-unknown/release/`. +Before running the tests, you need to build the `template-barebones` and `infinite-loop` components. Be sure to have `cargo component` installed, and run `cargo component build --release -p template-barebones -p infinite-loop --target wasm32-unknown-unknown`. This will create the files needed for testing at `target/wasm32-unknown-unknown/release/`. diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index a2c44b0..be3cfcf 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -3,7 +3,7 @@ use thiserror::Error; use wasmtime::{ component::{bindgen, Component, Linker}, - Config, Engine, Result, Store, + Config as WasmtimeConfig, Engine, Result, Store, }; /// Note, this is wasmtime's bindgen, not wit-bindgen (modules) @@ -26,9 +26,26 @@ pub enum RuntimeError { /// Program bytecode is not a valid WebAssembly component. #[error("Invalid bytecode")] InvalidBytecode, - /// Runtime error during execution. + /// Program error during execution. #[error("Runtime error: {0}")] Runtime(ProgramError), + /// Program exceeded fuel limits. Execute fewer instructions. + #[error("Out of fuel")] + OutOfFuel, +} + +/// Config is for runtime parameters (eg instructions per program, additional runtime interfaces, etc). +pub struct Config { + /// Max number of instructions the runtime will execute before returning an error. + pub fuel: u64, +} + +impl Default for Config { + fn default() -> Self { + Self { + fuel: 10_000, + } + } } /// Runtime allows for the execution of programs. Instantiate with `Runtime::new()`. @@ -40,11 +57,22 @@ pub struct Runtime { impl Default for Runtime { fn default() -> Self { - let mut config = Config::new(); - config.wasm_component_model(true); - let engine = Engine::new(&config).unwrap(); + Self::new(Config::default()) + } +} + +impl Runtime { + pub fn new(config: Config) -> Self { + let mut wasmtime_config = WasmtimeConfig::new(); + wasmtime_config + .wasm_component_model(true) + .consume_fuel(true); + + let engine = Engine::new(&wasmtime_config).unwrap(); let linker = Linker::new(&engine); - let store = Store::new(&engine, ()); + let mut store = Store::new(&engine, ()); + + store.add_fuel(config.fuel).unwrap(); Self { engine, linker, @@ -53,12 +81,6 @@ impl Default for Runtime { } } -impl Runtime { - pub fn new() -> Self { - Self::default() - } -} - impl Runtime { /// Evaluate a program with a given initial state. pub fn evaluate( @@ -77,7 +99,7 @@ impl Runtime { bindings .call_evaluate(&mut self.store, signature_request) - .unwrap() + .map_err(|_| RuntimeError::OutOfFuel)? .map_err(RuntimeError::Runtime) } diff --git a/runtime/tests/runtime.rs b/runtime/tests/runtime.rs index 896a643..6ac2f53 100644 --- a/runtime/tests/runtime.rs +++ b/runtime/tests/runtime.rs @@ -4,12 +4,16 @@ const BAREBONES_COMPONENT_WASM: &[u8] = const CUSTOM_HASH_COMPONENT_WASM: &[u8] = include_bytes!("../../target/wasm32-unknown-unknown/release/example_custom_hash.wasm"); +/// Points to the `infinite-loop` program binary. +const INFINITE_LOOP_WASM: &[u8] = + include_bytes!("../../target/wasm32-unknown-unknown/release/infinite_loop.wasm"); + use ec_runtime::{Runtime, SignatureRequest}; use blake3; #[test] fn test_barebones_component() { - let mut runtime = Runtime::new(); + let mut runtime = Runtime::default(); // The barebones example simply validates that the length of the data to be signed is greater than 10. let longer_than_10 = "asdfasdfasdfasdf".to_string(); @@ -24,7 +28,7 @@ fn test_barebones_component() { #[test] fn test_barebones_component_fails_with_data_length_less_than_10() { - let mut runtime = Runtime::new(); + let mut runtime = Runtime::default(); // Since the barebones example verifies that the length of the data to be signed is greater than 10, this should fail. let shorter_than_10 = "asdf".to_string(); @@ -39,7 +43,7 @@ fn test_barebones_component_fails_with_data_length_less_than_10() { #[test] fn test_empty_bytecode_fails() { - let mut runtime = Runtime::new(); + let mut runtime = Runtime::default(); let signature_request = SignatureRequest { message: vec![], @@ -50,9 +54,22 @@ fn test_empty_bytecode_fails() { assert_eq!(res.unwrap_err().to_string(), "Bytecode length is zero"); } +#[test] +fn test_infinite_loop() { + let mut runtime = Runtime::default(); + + let signature_request = SignatureRequest { + message: vec![], + auxilary_data: None, + }; + + let res = runtime.evaluate(INFINITE_LOOP_WASM, &signature_request); + assert_eq!(res.unwrap_err().to_string(), "Out of fuel"); +} + #[test] fn test_custom_hash() { - let mut runtime = Runtime::new(); + let mut runtime = Runtime::default(); let message = "some_data_to_be_hashed".to_string().into_bytes(); @@ -69,7 +86,7 @@ fn test_custom_hash() { #[test] fn test_custom_hash_errors_when_returning_none() { - let mut runtime = Runtime::new(); + let mut runtime = Runtime::default(); let message = "some_data_to_be_hashed".to_string().into_bytes(); @@ -85,4 +102,4 @@ fn test_custom_hash_errors_when_returning_none() { ); } -// TODO add test for custom hash returning a vec of length != 32 \ No newline at end of file +// TODO add test for custom hash returning a vec of length != 32