From cb772af19862a8920ac8e959a48db4be2a1cf19c Mon Sep 17 00:00:00 2001 From: Stan Bondi Date: Thu, 28 Jul 2022 18:01:44 +0400 Subject: [PATCH] refactor: clean up duplicate memory code in package Decouples package from wasm module loading --- dan_layer/engine/src/crypto.rs | 27 +-- dan_layer/engine/src/instruction/error.rs | 2 +- dan_layer/engine/src/instruction/mod.rs | 2 +- dan_layer/engine/src/instruction/processor.rs | 2 +- dan_layer/engine/src/lib.rs | 3 +- dan_layer/engine/src/package.rs | 219 ------------------ dan_layer/engine/src/packager/error.rs | 29 +++ dan_layer/engine/src/packager/mod.rs | 30 +++ .../engine/src/packager/module_loader.rs | 30 +++ dan_layer/engine/src/packager/package.rs | 92 ++++++++ dan_layer/engine/src/{ => wasm}/compile.rs | 7 +- dan_layer/engine/src/wasm/env.rs | 33 ++- dan_layer/engine/src/wasm/error.rs | 30 ++- dan_layer/engine/src/wasm/mod.rs | 8 +- dan_layer/engine/src/wasm/module.rs | 115 ++++++++- dan_layer/engine/src/wasm/process.rs | 25 +- dan_layer/engine/tests/test.rs | 8 +- 17 files changed, 376 insertions(+), 286 deletions(-) delete mode 100644 dan_layer/engine/src/package.rs create mode 100644 dan_layer/engine/src/packager/error.rs create mode 100644 dan_layer/engine/src/packager/mod.rs create mode 100644 dan_layer/engine/src/packager/module_loader.rs create mode 100644 dan_layer/engine/src/packager/package.rs rename dan_layer/engine/src/{ => wasm}/compile.rs (93%) diff --git a/dan_layer/engine/src/crypto.rs b/dan_layer/engine/src/crypto.rs index 9413c91fbd..37f855003f 100644 --- a/dan_layer/engine/src/crypto.rs +++ b/dan_layer/engine/src/crypto.rs @@ -20,30 +20,19 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use digest::Digest; use rand::rngs::OsRng; use tari_common_types::types::{PrivateKey, PublicKey}; -use tari_crypto::{ - hash::blake2::Blake256, - hashing::{DomainSeparatedHasher, DomainSeparation}, - keys::PublicKey as PublicKeyT, -}; +use tari_crypto::{hash::blake2::Blake256, hash_domain, hashing::DomainSeparatedHasher, keys::PublicKey as PublicKeyT}; -pub fn create_key_pair() -> (PrivateKey, PublicKey) { - PublicKey::random_keypair(&mut OsRng) -} +hash_domain!(TariEngineHashDomain, "tari.dan.engine", 0); -pub struct TariEngineDomainSeparation; +pub type TariEngineHasher = DomainSeparatedHasher; -impl DomainSeparation for TariEngineDomainSeparation { - fn version() -> u8 { - 0 - } - - fn domain() -> &'static str { - "tari.dan.engine" - } +pub fn hasher(label: &'static str) -> impl Digest { + TariEngineHasher::new(label) } -pub fn domain_separated_hasher(label: &'static str) -> DomainSeparatedHasher { - DomainSeparatedHasher::new(label) +pub fn create_key_pair() -> (PrivateKey, PublicKey) { + PublicKey::random_keypair(&mut OsRng) } diff --git a/dan_layer/engine/src/instruction/error.rs b/dan_layer/engine/src/instruction/error.rs index 6f1cf4eed4..35926900a7 100644 --- a/dan_layer/engine/src/instruction/error.rs +++ b/dan_layer/engine/src/instruction/error.rs @@ -20,7 +20,7 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::{package::PackageId, wasm::WasmExecutionError}; +use crate::{packager::PackageId, wasm::WasmExecutionError}; #[derive(Debug, thiserror::Error)] pub enum InstructionError { diff --git a/dan_layer/engine/src/instruction/mod.rs b/dan_layer/engine/src/instruction/mod.rs index 6a80aebd9d..286e4b5b40 100644 --- a/dan_layer/engine/src/instruction/mod.rs +++ b/dan_layer/engine/src/instruction/mod.rs @@ -30,7 +30,7 @@ pub use processor::InstructionProcessor; mod signature; -use crate::{instruction::signature::InstructionSignature, package::PackageId}; +use crate::{instruction::signature::InstructionSignature, packager::PackageId}; #[derive(Debug, Clone)] pub enum Instruction { diff --git a/dan_layer/engine/src/instruction/processor.rs b/dan_layer/engine/src/instruction/processor.rs index 48e29581c8..0aa9f529b8 100644 --- a/dan_layer/engine/src/instruction/processor.rs +++ b/dan_layer/engine/src/instruction/processor.rs @@ -24,7 +24,7 @@ use std::{collections::HashMap, sync::Arc}; use crate::{ instruction::{error::InstructionError, Instruction, InstructionSet}, - package::{Package, PackageId}, + packager::{Package, PackageId}, runtime::{Runtime, RuntimeInterface}, traits::Invokable, wasm::{ExecutionResult, Process}, diff --git a/dan_layer/engine/src/lib.rs b/dan_layer/engine/src/lib.rs index 9481b84728..c2d7b34f9d 100644 --- a/dan_layer/engine/src/lib.rs +++ b/dan_layer/engine/src/lib.rs @@ -10,10 +10,9 @@ pub mod models; pub mod state; pub mod wasm; -pub mod compile; pub mod crypto; pub mod instruction; -pub mod package; +pub mod packager; pub mod runtime; pub mod traits; diff --git a/dan_layer/engine/src/package.rs b/dan_layer/engine/src/package.rs deleted file mode 100644 index 2fbb3fd1be..0000000000 --- a/dan_layer/engine/src/package.rs +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright 2022. The Tari Project -// -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following -// disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the -// following disclaimer in the documentation and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote -// products derived from this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, -// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -use std::{cell::Cell, collections::HashMap, convert::TryInto}; - -use rand::{rngs::OsRng, RngCore}; -use tari_common_types::types::FixedHash; -use tari_template_abi::TemplateDef; -use wasmer::{imports, Extern, Function, Instance, Memory, Module, Store, Val}; - -use crate::{crypto, wasm::LoadedWasmModule}; - -#[derive(Debug, Clone, Default)] -pub struct PackageBuilder { - wasm_code: Vec>, -} - -impl PackageBuilder { - pub fn new() -> Self { - Self { wasm_code: Vec::new() } - } - - pub fn add_wasm_template(&mut self, wasm_code: Vec) -> &mut Self { - self.wasm_code.push(wasm_code); - self - } - - pub fn build(&self) -> Result { - let mut wasm_modules = HashMap::with_capacity(self.wasm_code.len()); - let store = Store::default(); - let id = new_package_id(); - for code in &self.wasm_code { - let module = load_wasm_module(&store, code)?; - wasm_modules.insert(module.template_name().to_string(), module); - } - - Ok(Package { - id, - wasm_modules, - _store: store, - }) - } -} - -fn new_package_id() -> PackageId { - let v = OsRng.next_u32(); - crypto::domain_separated_hasher("package") - // TODO: Proper package id - .chain(&v.to_le_bytes()) - .finalize() - .as_ref() - .try_into() - .unwrap() -} - -fn load_wasm_module(store: &Store, code: &[u8]) -> Result { - let module = Module::new(store, code)?; - - fn stub(_op: i32, _args_ptr: i32, _args_len: i32) -> i32 { - 0 - } - - fn debug(_args_ptr: i32, _args_len: i32) {} - - let imports = imports! { - "env" => { - "tari_engine" => Function::new_native(store, stub), - "debug" => Function::new_native(store, debug), - } - }; - let instance = Instance::new(&module, &imports)?; - validate_instance(&instance)?; - - let template = initialize_and_load_template_abi(&instance)?; - Ok(LoadedWasmModule::new(template, module)) -} - -fn initialize_and_load_template_abi(instance: &Instance) -> Result { - let abi_func = instance - .exports - .iter() - .find_map(|(name, export)| match export { - Extern::Function(f) if name.ends_with("_abi") => Some(f), - _ => None, - }) - .ok_or(PackageError::NoAbiDefinition)?; - - // Initialize ABI memory - let ret = abi_func.call(&[])?; - let ptr = match ret.get(0) { - Some(Val::I32(ptr)) => *ptr as u32, - Some(_) | None => return Err(PackageError::InvalidReturnTypeFromAbiFunc), - }; - - // Load ABI from memory - let memory = instance.exports.get_memory("memory")?; - let data = copy_abi_data_from_memory_checked(memory, ptr)?; - let decoded = tari_template_abi::decode(&data).map_err(|_| PackageError::AbiDecodeError)?; - Ok(decoded) -} - -fn copy_abi_data_from_memory_checked(memory: &Memory, ptr: u32) -> Result, PackageError> { - // Check memory bounds - if memory.data_size() < u64::from(ptr) { - return Err(PackageError::AbiPointerOutOfBounds); - } - - let view = memory.uint8view().subarray(ptr, memory.data_size() as u32 - 1); - let data = &*view; - if data.len() < 4 { - return Err(PackageError::MemoryUnderflow { - required: 4, - remaining: data.len(), - }); - } - - fn copy_from_cell_slice(src: &[Cell], dest: &mut [u8], len: usize) { - for i in 0..len { - dest[i] = src[i].get(); - } - } - - let mut buf = [0u8; 4]; - copy_from_cell_slice(data, &mut buf, 4); - let len = u32::from_le_bytes(buf) as usize; - const MAX_ABI_DATA_LEN: usize = 1024 * 1024; - if len > MAX_ABI_DATA_LEN { - return Err(PackageError::AbiDataTooLarge { - max: MAX_ABI_DATA_LEN, - size: len, - }); - } - if data.len() < 4 + len { - return Err(PackageError::MemoryUnderflow { - required: 4 + len, - remaining: data.len(), - }); - } - - let mut data = vec![0u8; len]; - let src = view.subarray(4, 4 + len as u32); - copy_from_cell_slice(&*src, &mut data, len); - Ok(data) -} - -pub fn validate_instance(instance: &Instance) -> Result<(), PackageError> { - if let Ok(mem) = instance.exports.get_memory("memory") { - if mem.size().bytes().0 > 2 * 1024 * 1024 { - return Err(PackageError::MaxMemorySizeExceeded); - } - } - // TODO other package validations - - Ok(()) -} - -pub type PackageId = FixedHash; - -#[derive(Debug, Clone)] -pub struct Package { - id: PackageId, - wasm_modules: HashMap, - _store: Store, -} - -impl Package { - pub fn get_module_by_name(&self, name: &str) -> Option<&LoadedWasmModule> { - self.wasm_modules.get(name) - } - - pub fn id(&self) -> PackageId { - self.id - } -} - -#[derive(Debug, thiserror::Error)] -pub enum PackageError { - #[error(transparent)] - CompileError(#[from] wasmer::CompileError), - #[error(transparent)] - InstantiationError(#[from] wasmer::InstantiationError), - #[error(transparent)] - RuntimeError(#[from] wasmer::RuntimeError), - #[error(transparent)] - ExportError(#[from] wasmer::ExportError), - #[error("Failed to decode ABI")] - AbiDecodeError, - #[error("maximum module memory size exceeded")] - MaxMemorySizeExceeded, - #[error("package did not contain an ABI definition")] - NoAbiDefinition, - #[error("package ABI function returned an invalid type")] - InvalidReturnTypeFromAbiFunc, - #[error("package ABI function returned an out of bounds pointer")] - AbiPointerOutOfBounds, - #[error("memory underflow: {required} bytes required but {remaining} remaining")] - MemoryUnderflow { required: usize, remaining: usize }, - #[error("ABI data is too large: a maximum of {max} bytes allowed but size is {size}")] - AbiDataTooLarge { max: usize, size: usize }, -} diff --git a/dan_layer/engine/src/packager/error.rs b/dan_layer/engine/src/packager/error.rs new file mode 100644 index 0000000000..bac307265c --- /dev/null +++ b/dan_layer/engine/src/packager/error.rs @@ -0,0 +1,29 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::wasm::WasmExecutionError; + +#[derive(Debug, thiserror::Error)] +pub enum PackageError { + #[error(transparent)] + WasmModuleError(#[from] WasmExecutionError), +} diff --git a/dan_layer/engine/src/packager/mod.rs b/dan_layer/engine/src/packager/mod.rs new file mode 100644 index 0000000000..edc894fbf3 --- /dev/null +++ b/dan_layer/engine/src/packager/mod.rs @@ -0,0 +1,30 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +mod error; +pub use error::PackageError; + +mod package; +pub use package::{Package, PackageBuilder, PackageId}; + +mod module_loader; +pub use module_loader::PackageModuleLoader; diff --git a/dan_layer/engine/src/packager/module_loader.rs b/dan_layer/engine/src/packager/module_loader.rs new file mode 100644 index 0000000000..871d9558b6 --- /dev/null +++ b/dan_layer/engine/src/packager/module_loader.rs @@ -0,0 +1,30 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::packager::PackageError; + +pub trait PackageModuleLoader { + type Loaded; + type Error: Into; + + fn load_module(&self) -> Result; +} diff --git a/dan_layer/engine/src/packager/package.rs b/dan_layer/engine/src/packager/package.rs new file mode 100644 index 0000000000..f78128e0c0 --- /dev/null +++ b/dan_layer/engine/src/packager/package.rs @@ -0,0 +1,92 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::collections::HashMap; + +use digest::Digest; +use rand::{rngs::OsRng, RngCore}; +use tari_common_types::types::FixedHash; + +use crate::{ + crypto, + packager::{error::PackageError, PackageModuleLoader}, + wasm::{LoadedWasmModule, WasmModule}, +}; + +pub type PackageId = FixedHash; + +#[derive(Debug, Clone)] +pub struct Package { + id: PackageId, + wasm_modules: HashMap, +} + +impl Package { + pub fn builder() -> PackageBuilder { + PackageBuilder::new() + } + + pub fn get_module_by_name(&self, name: &str) -> Option<&LoadedWasmModule> { + self.wasm_modules.get(name) + } + + pub fn id(&self) -> PackageId { + self.id + } +} + +#[derive(Debug, Clone, Default)] +pub struct PackageBuilder { + wasm_modules: Vec, +} + +impl PackageBuilder { + pub fn new() -> Self { + Self { + wasm_modules: Vec::new(), + } + } + + pub fn add_wasm_module(&mut self, wasm_module: WasmModule) -> &mut Self { + self.wasm_modules.push(wasm_module); + self + } + + pub fn build(&self) -> Result { + let mut wasm_modules = HashMap::with_capacity(self.wasm_modules.len()); + let id = new_package_id(); + for wasm in &self.wasm_modules { + let loaded = wasm.load_module()?; + wasm_modules.insert(loaded.template_name().to_string(), loaded); + } + + Ok(Package { id, wasm_modules }) + } +} + +fn new_package_id() -> PackageId { + let v = OsRng.next_u32(); + crypto::hasher("package") + // TODO: Proper package id + .chain(&v.to_le_bytes()) + .finalize().into() +} diff --git a/dan_layer/engine/src/compile.rs b/dan_layer/engine/src/wasm/compile.rs similarity index 93% rename from dan_layer/engine/src/compile.rs rename to dan_layer/engine/src/wasm/compile.rs index a22dc6f3d6..61b50e3fcc 100644 --- a/dan_layer/engine/src/compile.rs +++ b/dan_layer/engine/src/wasm/compile.rs @@ -24,7 +24,9 @@ use std::{fs, io, io::ErrorKind, path::Path, process::Command}; use cargo_toml::{Manifest, Product}; -pub fn compile_template>(package_dir: P) -> io::Result> { +use super::module::WasmModule; + +pub fn build_wasm_module_from_source>(package_dir: P) -> io::Result { let status = Command::new("cargo") .current_dir(package_dir.as_ref()) .args(["build", "--target", "wasm32-unknown-unknown", "--release"]) @@ -65,5 +67,6 @@ pub fn compile_template>(package_dir: P) -> io::Result> { path.set_extension("wasm"); // return - fs::read(path) + let code = fs::read(path)?; + Ok(WasmModule::from_code(code)) } diff --git a/dan_layer/engine/src/wasm/env.rs b/dan_layer/engine/src/wasm/env.rs index f27577ac1a..be8fbf928c 100644 --- a/dan_layer/engine/src/wasm/env.rs +++ b/dan_layer/engine/src/wasm/env.rs @@ -25,7 +25,7 @@ use std::{ fmt::{Debug, Formatter}, }; -use wasmer::{LazyInit, Memory, NativeFunc, WasmerEnv}; +use wasmer::{imports, Function, LazyInit, Memory, NativeFunc, Pages, Resolver, Store, WasmerEnv}; use crate::{ runtime::{Runtime, RuntimeInterface}, @@ -85,7 +85,7 @@ impl WasmEnv { Ok(()) } - pub(super) fn read_from_memory_with_len(&self, ptr: u32) -> Result, WasmExecutionError> { + pub(super) fn read_memory_with_embedded_len(&self, ptr: u32) -> Result, WasmExecutionError> { let memory = self.get_memory()?; let view = memory.uint8view().subarray(ptr, memory.data_size() as u32 - 1); let view_bytes = &*view; @@ -131,7 +131,7 @@ impl WasmEnv { fn get_mem_alloc_func(&self) -> Result<&NativeFunc, WasmExecutionError> { self.mem_alloc .get_ref() - .ok_or_else(|| WasmExecutionError::MissingFunction { + .ok_or_else(|| WasmExecutionError::MissingAbiFunction { function: "tari_alloc".into(), }) } @@ -139,7 +139,7 @@ impl WasmEnv { fn get_mem_free_func(&self) -> Result<&NativeFunc, WasmExecutionError> { self.mem_free .get_ref() - .ok_or_else(|| WasmExecutionError::MissingFunction { + .ok_or_else(|| WasmExecutionError::MissingAbiFunction { function: "tari_free".into(), }) } @@ -147,6 +147,31 @@ impl WasmEnv { fn get_memory(&self) -> Result<&Memory, WasmExecutionError> { self.memory.get_ref().ok_or(WasmExecutionError::MemoryNotInitialized) } + + pub fn mem_size(&self) -> Pages { + self.memory.get_ref().map(|mem| mem.size()).unwrap_or(Pages(0)) + } + + pub fn create_resolver(&self, store: &Store, tari_engine: Function) -> impl Resolver { + imports! { + "env" => { + "tari_engine" => tari_engine, + "debug" => Function::new_native_with_env(store, self.clone(), Self::debug_handler), + } + } + } + + fn debug_handler(env: &Self, arg_ptr: i32, arg_len: i32) { + const WASM_DEBUG_LOG_TARGET: &str = "tari::dan::wasm"; + match env.read_from_memory(arg_ptr as u32, arg_len as u32) { + Ok(arg) => { + eprintln!("DEBUG: {}", String::from_utf8_lossy(&arg)); + }, + Err(err) => { + log::error!(target: WASM_DEBUG_LOG_TARGET, "Failed to read from memory: {}", err); + }, + } + } } impl Debug for WasmEnv { diff --git a/dan_layer/engine/src/wasm/error.rs b/dan_layer/engine/src/wasm/error.rs index 4369b960e4..b632fd41bc 100644 --- a/dan_layer/engine/src/wasm/error.rs +++ b/dan_layer/engine/src/wasm/error.rs @@ -16,6 +16,16 @@ pub enum WasmError { #[derive(Debug, thiserror::Error)] pub enum WasmExecutionError { + #[error(transparent)] + InstantiationError(#[from] InstantiationError), + #[error(transparent)] + ExportError(#[from] ExportError), + #[error(transparent)] + WasmRuntimeError(#[from] wasmer::RuntimeError), + #[error(transparent)] + HostEnvInitError(#[from] HostEnvInitError), + #[error(transparent)] + CompileError(#[from] wasmer::CompileError), #[error("Function {name} not found")] FunctionNotFound { name: String }, #[error("Expected function {function} to return a pointer")] @@ -26,14 +36,6 @@ pub enum WasmExecutionError { MemoryUnderflow { required: usize, remaining: usize }, #[error("memory pointer out of range: memory size of {size} but pointer is {pointer}")] MemoryPointerOutOfRange { size: u64, pointer: u64, len: u64 }, - #[error(transparent)] - InstantiationError(#[from] InstantiationError), - #[error(transparent)] - ExportError(#[from] ExportError), - #[error(transparent)] - WasmRuntimeError(#[from] wasmer::RuntimeError), - #[error(transparent)] - HostEnvInitError(#[from] HostEnvInitError), #[error("Memory allocation failed")] MemoryAllocationFailed, #[error("Memory not initialized")] @@ -41,9 +43,19 @@ pub enum WasmExecutionError { #[error("Invalid operation {op}")] InvalidOperation { op: i32 }, #[error("Missing function {function}")] - MissingFunction { function: String }, + MissingAbiFunction { function: String }, #[error("Runtime error: {0}")] RuntimeError(#[from] RuntimeError), #[error("Failed to decode argument for engine call: {0}")] EngineArgDecodeFailed(io::Error), + #[error("maximum module memory size exceeded")] + MaxMemorySizeExceeded, + #[error("Failed to decode ABI")] + AbiDecodeError, + #[error("package ABI function returned an invalid type")] + InvalidReturnTypeFromAbiFunc, + #[error("package did not contain an ABI definition")] + NoAbiDefinition, + #[error("Unexpected ABI function {name}")] + UnexpectedAbiFunction { name: String }, } diff --git a/dan_layer/engine/src/wasm/mod.rs b/dan_layer/engine/src/wasm/mod.rs index 330c5532fb..5c5f6f80ef 100644 --- a/dan_layer/engine/src/wasm/mod.rs +++ b/dan_layer/engine/src/wasm/mod.rs @@ -7,13 +7,15 @@ mod wasm_module_factory; pub use wasm_module_definition::WasmModuleDefinition; pub use wasm_module_factory::WasmModuleFactory; +pub mod compile; + mod error; pub use error::{WasmError, WasmExecutionError}; +mod env; + mod module; -pub use module::LoadedWasmModule; +pub use module::{LoadedWasmModule, WasmModule}; mod process; pub use process::{ExecutionResult, Process}; - -mod env; diff --git a/dan_layer/engine/src/wasm/module.rs b/dan_layer/engine/src/wasm/module.rs index 44ec5deee1..e2d4547fb6 100644 --- a/dan_layer/engine/src/wasm/module.rs +++ b/dan_layer/engine/src/wasm/module.rs @@ -20,7 +20,81 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use tari_template_abi::{FunctionDef, TemplateDef}; +use std::sync::Arc; + +use tari_template_abi::{FunctionDef, LogLevel, TemplateDef}; +use wasmer::{Extern, Function, Instance, Module, Store, Val, WasmerEnv}; + +use crate::{ + models::{Component, ComponentId}, + packager::PackageModuleLoader, + runtime::{Runtime, RuntimeError, RuntimeInterface}, + wasm::{env::WasmEnv, WasmExecutionError}, +}; + +#[derive(Debug, Clone)] +pub struct WasmModule { + code: Vec, +} + +impl WasmModule { + pub fn from_code(code: Vec) -> Self { + Self { code } + } + + pub fn code(&self) -> &[u8] { + &self.code + } +} + +impl PackageModuleLoader for WasmModule { + type Error = WasmExecutionError; + type Loaded = LoadedWasmModule; + + fn load_module(&self) -> Result { + let store = Store::default(); + let module = Module::new(&store, &self.code)?; + let runtime = Runtime::new(Arc::new(NoopInterface)); + let mut env = WasmEnv::new(runtime); + + fn stub(_env: &WasmEnv, _op: i32, _arg_ptr: i32, _arg_len: i32) -> i32 { + panic!("WASM module called engine while loading ABI") + } + + let stub = Function::new_native_with_env(&store, env.clone(), stub); + let imports = env.create_resolver(&store, stub); + let instance = Instance::new(&module, &imports)?; + env.init_with_instance(&instance)?; + validate_instance(&instance)?; + validate_environment(&env)?; + + let template = initialize_and_load_template_abi(&instance, &env)?; + Ok(LoadedWasmModule::new(template, module)) + } +} + +fn initialize_and_load_template_abi(instance: &Instance, env: &WasmEnv) -> Result { + let abi_func = instance + .exports + .iter() + .find_map(|(name, export)| match export { + Extern::Function(f) if name.ends_with("_abi") => Some(f), + _ => None, + }) + .ok_or(WasmExecutionError::NoAbiDefinition)?; + + // Initialize ABI memory + let ret = abi_func.call(&[])?; + let ptr = match ret.get(0) { + Some(Val::I32(ptr)) => *ptr as u32, + Some(_) | None => return Err(WasmExecutionError::InvalidReturnTypeFromAbiFunc), + }; + + // Load ABI from memory + let data = env.read_memory_with_embedded_len(ptr)?; + let decoded = tari_template_abi::decode(&data).map_err(|_| WasmExecutionError::AbiDecodeError)?; + Ok(decoded) +} #[derive(Debug, Clone)] pub struct LoadedWasmModule { @@ -45,3 +119,42 @@ impl LoadedWasmModule { self.template.functions.iter().find(|f| f.name == *function_name) } } + +fn validate_environment(env: &WasmEnv) -> Result<(), WasmExecutionError> { + const MAX_MEM_SIZE: usize = 2 * 1024 * 1024; + let mem_size = env.mem_size(); + if mem_size.bytes().0 > MAX_MEM_SIZE { + return Err(WasmExecutionError::MaxMemorySizeExceeded); + } + // TODO other package validations + + Ok(()) +} + +fn validate_instance(instance: &Instance) -> Result<(), WasmExecutionError> { + // Enforce that only permitted functions are allowed + let unexpected_abi_func = instance + .exports + .iter() + .functions() + .find(|(name, _)| !is_func_permitted(name)); + if let Some((name, _)) = unexpected_abi_func { + return Err(WasmExecutionError::UnexpectedAbiFunction { name: name.to_string() }); + } + + Ok(()) +} + +fn is_func_permitted(name: &str) -> bool { + name.ends_with("_abi") || name.ends_with("_main") || name == "tari_alloc" || name == "tari_free" +} + +struct NoopInterface; + +impl RuntimeInterface for NoopInterface { + fn emit_log(&self, _level: LogLevel, _message: &str) {} + + fn create_component(&self, _component: Component) -> Result { + panic!("create_component called on NoopInterface") + } +} diff --git a/dan_layer/engine/src/wasm/process.rs b/dan_layer/engine/src/wasm/process.rs index 343093bf3e..46dd14503d 100644 --- a/dan_layer/engine/src/wasm/process.rs +++ b/dan_layer/engine/src/wasm/process.rs @@ -24,7 +24,7 @@ use std::io; use borsh::{BorshDeserialize, BorshSerialize}; use tari_template_abi::{decode, encode_into, encode_with_len, ops, CallInfo, CreateComponentArg, EmitLogArg, Type}; -use wasmer::{imports, Function, Instance, Module, Store, Val, WasmerEnv}; +use wasmer::{Function, Instance, Module, Store, Val, WasmerEnv}; use crate::{ runtime::Runtime, @@ -49,13 +49,9 @@ impl Process { pub fn start(module: LoadedWasmModule, state: Runtime) -> Result { let store = Store::default(); let mut env = WasmEnv::new(state); - let imports = imports! { - "env" => { - "tari_engine" => Function::new_native_with_env(&store, env.clone(), Self::tari_engine_entrypoint), - "debug" => Function::new_native_with_env(&store, env.clone(), Self::debug), - } - }; - let instance = Instance::new(module.wasm_module(), &imports)?; + let tari_engine = Function::new_native_with_env(&store, env.clone(), Self::tari_engine_entrypoint); + let resolver = env.create_resolver(&store, tari_engine); + let instance = Instance::new(module.wasm_module(), &resolver)?; env.init_with_instance(&instance)?; Ok(Self { module, env, instance }) } @@ -73,17 +69,6 @@ impl Process { self.module.wasm_module() } - fn debug(env: &WasmEnv, arg_ptr: i32, arg_len: i32) { - let arg = match env.read_from_memory(arg_ptr as u32, arg_len as u32) { - Ok(arg) => arg, - Err(err) => { - log::error!(target: LOG_TARGET, "Failed to read from memory: {}", err); - return; - }, - }; - eprintln!("DEBUG: {}", String::from_utf8_lossy(&arg)); - } - fn tari_engine_entrypoint(env: &WasmEnv, op: i32, arg_ptr: i32, arg_len: i32) -> i32 { let arg = match env.read_from_memory(arg_ptr as u32, arg_len as u32) { Ok(arg) => arg, @@ -155,7 +140,7 @@ impl Invokable for Process { .ok_or(WasmExecutionError::ExpectedPointerReturn { function: main_name })?; // Read response from memory - let raw = self.env.read_from_memory_with_len(ptr as u32)?; + let raw = self.env.read_memory_with_embedded_len(ptr as u32)?; // TODO: decode raw as per function def Ok(ExecutionResult { diff --git a/dan_layer/engine/tests/test.rs b/dan_layer/engine/tests/test.rs index 89d0e6c594..a70ea39952 100644 --- a/dan_layer/engine/tests/test.rs +++ b/dan_layer/engine/tests/test.rs @@ -27,11 +27,11 @@ use mock_runtime_interface::MockRuntimeInterface; use tari_common_types::types::FixedHash; use tari_crypto::ristretto::RistrettoSecretKey; use tari_dan_engine::{ - compile::compile_template, crypto::create_key_pair, instruction::{Instruction, InstructionBuilder, InstructionProcessor}, models::ComponentId, - package::PackageBuilder, + packager::Package, + wasm::compile::build_wasm_module_from_source, }; use tari_template_abi::encode_with_len; @@ -77,8 +77,8 @@ impl TemplateTest { let mut processor = InstructionProcessor::new(MockRuntimeInterface::new()); let (secret_key, _pk) = create_key_pair(); - let wasm = compile_template(template_path).unwrap(); - let package = PackageBuilder::new().add_wasm_template(wasm).build().unwrap(); + let wasm = build_wasm_module_from_source(template_path).unwrap(); + let package = Package::builder().add_wasm_module(wasm).build().unwrap(); let package_id = package.id(); processor.load(package);