From 71560df06ff5f535bb877d1cecd10c1377165126 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Wed, 1 Nov 2023 15:53:22 +0100 Subject: [PATCH] piecrust: change `ObjectCode` into `Module` A `Module` now contains an actual `dusk_wasmtime::Module`, as opposed to just some bytes, since it allows us to check for some invariants. --- piecrust/src/session.rs | 6 +- piecrust/src/store.rs | 45 +++++++++------ piecrust/src/store/module.rs | 97 ++++++++++++++++++++++++++++++++ piecrust/src/store/objectcode.rs | 47 ---------------- piecrust/src/store/session.rs | 36 ++++++++---- piecrust/src/vm.rs | 4 +- 6 files changed, 156 insertions(+), 79 deletions(-) create mode 100644 piecrust/src/store/module.rs delete mode 100644 piecrust/src/store/objectcode.rs diff --git a/piecrust/src/session.rs b/piecrust/src/session.rs index 2fcae9f2..74a83e2e 100644 --- a/piecrust/src/session.rs +++ b/piecrust/src/session.rs @@ -28,7 +28,7 @@ use crate::call_tree::{CallTree, CallTreeElem}; use crate::contract::{ContractData, ContractMetadata, WrappedContract}; use crate::error::Error::{self, InitalizationError, PersistenceError}; use crate::instance::WrappedInstance; -use crate::store::{ContractSession, Objectcode, PAGE_SIZE}; +use crate::store::{ContractSession, PAGE_SIZE}; use crate::types::StandardBufSerializer; use crate::vm::HostQueries; @@ -275,7 +275,7 @@ impl Session { } let wrapped_contract = - WrappedContract::new(&self.engine, bytecode, None::)?; + WrappedContract::new(&self.engine, bytecode, None::<&[u8]>)?; let contract_metadata = ContractMetadata { contract_id, owner }; let metadata_bytes = Self::serialize_data(&contract_metadata)?; @@ -531,7 +531,7 @@ impl Session { let contract = WrappedContract::new( &self.engine, store_data.bytecode, - Some(store_data.objectcode), + Some(store_data.module.serialize()), )?; self.inner.current = contract_id; diff --git a/piecrust/src/store.rs b/piecrust/src/store.rs index bb4fa4cd..08fa5447 100644 --- a/piecrust/src/store.rs +++ b/piecrust/src/store.rs @@ -9,21 +9,22 @@ mod bytecode; mod memory; mod metadata; -mod objectcode; +mod module; mod session; mod tree; use std::collections::btree_map::Entry::*; use std::collections::{BTreeMap, BTreeSet}; +use std::fmt::{Debug, Formatter}; use std::path::{Path, PathBuf}; use std::sync::mpsc; use std::{fs, io, thread}; pub use bytecode::Bytecode; -use dusk_wasmtime::{Engine, Module}; +use dusk_wasmtime::Engine; pub use memory::{Memory, MAX_MEM_SIZE, PAGE_SIZE}; pub use metadata::Metadata; -pub use objectcode::Objectcode; +pub use module::Module; use piecrust_uplink::ContractId; use session::ContractDataEntry; pub use session::ContractSession; @@ -36,13 +37,24 @@ const OBJECTCODE_EXTENSION: &str = "a"; const METADATA_EXTENSION: &str = "m"; /// A store for all contract commits. -#[derive(Debug)] pub struct ContractStore { sync_loop: thread::JoinHandle<()>, + engine: Engine, + call: mpsc::Sender, root_dir: PathBuf, } +impl Debug for ContractStore { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ContractStore") + .field("sync_loop", &self.sync_loop) + .field("call", &self.call) + .field("root_dir", &self.root_dir) + .finish() + } +} + impl ContractStore { /// Loads a new contract store from the given `dir`ectory. /// @@ -53,13 +65,13 @@ impl ContractStore { /// [`commit`]: ContractSession::commit /// [`delete`]: ContractStore::delete_commit /// [`session spawning`]: ContractStore::session - pub fn new>(engine: &Engine, dir: P) -> io::Result { + pub fn new>(engine: Engine, dir: P) -> io::Result { let root_dir = dir.as_ref(); fs::create_dir_all(root_dir)?; let (call, calls) = mpsc::channel(); - let commits = read_all_commits(engine, root_dir)?; + let commits = read_all_commits(&engine, root_dir)?; let loop_root_dir = root_dir.to_path_buf(); @@ -71,6 +83,7 @@ impl ContractStore { Ok(Self { sync_loop, + engine, call, root_dir: root_dir.into(), }) @@ -144,7 +157,12 @@ impl ContractStore { } fn session_with_base(&self, base: Option) -> ContractSession { - ContractSession::new(&self.root_dir, base, self.call.clone()) + ContractSession::new( + &self.root_dir, + self.engine.clone(), + base, + self.call.clone(), + ) } } @@ -217,20 +235,13 @@ fn commit_from_dir>( // SAFETY it is safe to deserialize the file here, since we don't use // the module here. We just want to check if the file is valid. - if unsafe { - Module::deserialize_file(engine, &objectcode_path).is_err() - } { + if Module::from_file(engine, &objectcode_path).is_err() { let bytecode = Bytecode::from_file(bytecode_path)?; let module = Module::new(engine, bytecode.as_ref()).map_err(|err| { io::Error::new(io::ErrorKind::InvalidData, err) })?; - fs::write( - objectcode_path, - module.serialize().map_err(|err| { - io::Error::new(io::ErrorKind::InvalidData, err) - })?, - )?; + fs::write(objectcode_path, module.serialize())?; } let memory_dir = memory_dir.join(&contract_hex); @@ -537,7 +548,7 @@ fn write_commit_inner>( } } else { fs::write(bytecode_path, &contract_data.bytecode)?; - fs::write(objectcode_path, &contract_data.objectcode)?; + fs::write(objectcode_path, &contract_data.module.serialize())?; fs::write(metadata_path, &contract_data.metadata)?; } } diff --git a/piecrust/src/store/module.rs b/piecrust/src/store/module.rs new file mode 100644 index 00000000..6c6d85ce --- /dev/null +++ b/piecrust/src/store/module.rs @@ -0,0 +1,97 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +use std::io; +use std::ops::Deref; +use std::path::Path; + +use dusk_wasmtime::Engine; + +/// WASM object code belonging to a given contract. +#[derive(Debug, Clone)] +pub struct Module { + module: dusk_wasmtime::Module, +} + +fn check_single_memory(module: &dusk_wasmtime::Module) -> io::Result<()> { + // Ensure the module only has one memory + let n_memories = module + .exports() + .filter_map(|exp| exp.ty().memory().map(|_| ())) + .count(); + if n_memories != 1 { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + format!( + "module has {} memories, but only one is allowed", + n_memories + ), + )); + } + Ok(()) +} + +impl Module { + pub(crate) fn new>( + engine: &Engine, + bytes: B, + ) -> io::Result { + let module = unsafe { + dusk_wasmtime::Module::deserialize(engine, bytes).map_err(|e| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("failed to deserialize module: {}", e), + ) + })? + }; + + check_single_memory(&module)?; + + Ok(Self { module }) + } + + pub(crate) fn from_file>( + engine: &Engine, + path: P, + ) -> io::Result { + let module = unsafe { + dusk_wasmtime::Module::deserialize_file(engine, path).map_err( + |e| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("failed to deserialize module: {}", e), + ) + }, + )? + }; + + check_single_memory(&module)?; + + Ok(Self { module }) + } + + pub(crate) fn serialize(&self) -> Vec { + self.module + .serialize() + .expect("We don't use WASM components") + } + + pub(crate) fn is_64(&self) -> bool { + self.module + .exports() + .filter_map(|exp| exp.ty().memory().map(|mem_ty| mem_ty.is_64())) + .next() + .expect("We guarantee the module has one memory") + } +} + +impl Deref for Module { + type Target = dusk_wasmtime::Module; + + fn deref(&self) -> &Self::Target { + &self.module + } +} diff --git a/piecrust/src/store/objectcode.rs b/piecrust/src/store/objectcode.rs deleted file mode 100644 index 70433dd5..00000000 --- a/piecrust/src/store/objectcode.rs +++ /dev/null @@ -1,47 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. -// -// Copyright (c) DUSK NETWORK. All rights reserved. - -use std::fs::File; -use std::io; -use std::path::Path; -use std::sync::Arc; - -use memmap2::{Mmap, MmapOptions}; - -/// WASM object code belonging to a given contract. -#[derive(Debug, Clone)] -pub struct Objectcode { - mmap: Arc, -} - -impl Objectcode { - pub(crate) fn new>(bytes: B) -> io::Result { - let bytes = bytes.as_ref(); - - let mut mmap = MmapOptions::new().len(bytes.len()).map_anon()?; - mmap.copy_from_slice(bytes); - let mmap = mmap.make_read_only()?; - - Ok(Self { - mmap: Arc::new(mmap), - }) - } - - pub(crate) fn from_file>(path: P) -> io::Result { - let file = File::open(path)?; - let mmap = unsafe { Mmap::map(&file)? }; - - Ok(Self { - mmap: Arc::new(mmap), - }) - } -} - -impl AsRef<[u8]> for Objectcode { - fn as_ref(&self) -> &[u8] { - &self.mmap - } -} diff --git a/piecrust/src/store/session.rs b/piecrust/src/store/session.rs index 62a084be..2212828f 100644 --- a/piecrust/src/store/session.rs +++ b/piecrust/src/store/session.rs @@ -6,23 +6,25 @@ use std::collections::btree_map::Entry::{Occupied, Vacant}; use std::collections::BTreeMap; +use std::fmt::Debug; use std::path::{Path, PathBuf}; use std::sync::mpsc; use std::{io, mem}; -use crate::contract::ContractMetadata; +use dusk_wasmtime::Engine; use piecrust_uplink::ContractId; +use crate::contract::ContractMetadata; use crate::store::tree::Hash; use crate::store::{ - Bytecode, Call, Commit, Memory, Metadata, Objectcode, BYTECODE_DIR, - MEMORY_DIR, METADATA_EXTENSION, OBJECTCODE_EXTENSION, + Bytecode, Call, Commit, Memory, Metadata, Module, BYTECODE_DIR, MEMORY_DIR, + METADATA_EXTENSION, OBJECTCODE_EXTENSION, }; #[derive(Debug, Clone)] pub struct ContractDataEntry { pub bytecode: Bytecode, - pub objectcode: Objectcode, + pub module: Module, pub metadata: Metadata, pub memory: Memory, } @@ -36,9 +38,9 @@ pub struct ContractDataEntry { /// call to [`commit`]. /// /// [`commit`]: ContractSession::commit -#[derive(Debug)] pub struct ContractSession { contracts: BTreeMap, + engine: Engine, base: Option, root_dir: PathBuf, @@ -46,14 +48,26 @@ pub struct ContractSession { call: mpsc::Sender, } +impl Debug for ContractSession { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ContractSession") + .field("contracts", &self.contracts) + .field("base", &self.base) + .field("root_dir", &self.root_dir) + .finish() + } +} + impl ContractSession { pub(crate) fn new>( root_dir: P, + engine: Engine, base: Option, call: mpsc::Sender, ) -> Self { Self { contracts: BTreeMap::new(), + engine, base, root_dir: root_dir.as_ref().into(), call, @@ -154,8 +168,10 @@ impl ContractSession { base_dir.join(MEMORY_DIR).join(contract_hex); let bytecode = Bytecode::from_file(bytecode_path)?; - let objectcode = - Objectcode::from_file(objectcode_path)?; + let module = Module::from_file( + &self.engine, + objectcode_path, + )?; let metadata = Metadata::from_file(metadata_path)?; let memory = match base_commit.index.get(&contract) @@ -189,7 +205,7 @@ impl ContractSession { let contract = entry .insert(ContractDataEntry { bytecode, - objectcode, + module, metadata, memory, }) @@ -236,7 +252,7 @@ impl ContractSession { ) -> io::Result<()> { let memory = Memory::new()?; let bytecode = Bytecode::new(bytecode)?; - let objectcode = Objectcode::new(objectcode)?; + let module = Module::new(&self.engine, objectcode)?; let metadata = Metadata::new(metadata_bytes, metadata)?; // If the position is already filled in the tree, the contract cannot be @@ -254,7 +270,7 @@ impl ContractSession { contract_id, ContractDataEntry { bytecode, - objectcode, + module, metadata, memory, }, diff --git a/piecrust/src/vm.rs b/piecrust/src/vm.rs index 9418bd03..411b74a9 100644 --- a/piecrust/src/vm.rs +++ b/piecrust/src/vm.rs @@ -101,7 +101,7 @@ impl VM { "Configuration should be valid since its set at compile time", ); - let store = ContractStore::new(&engine, root_dir) + let store = ContractStore::new(engine, root_dir) .map_err(|err| PersistenceError(Arc::new(err)))?; Ok(Self { @@ -128,7 +128,7 @@ impl VM { "Configuration should be valid since its set at compile time", ); - let store = ContractStore::new(&engine, tmp) + let store = ContractStore::new(engine, tmp) .map_err(|err| PersistenceError(Arc::new(err)))?; Ok(Self {