From 6edb73de199fafce0953f82af061ca1090ff911c Mon Sep 17 00:00:00 2001 From: Yaron Wittenstein Date: Mon, 13 Dec 2021 14:42:18 +0200 Subject: [PATCH] Runtime refactoring (#435) * Runtime - extracting code to `load_template` * Runtime#spawn uses `create_account` * Adding `GasTank` * Using the term `gas_limit` only in the context of the `Envelope` * Fixed a couple of tests * GasTank#unwrap * Returning back test assertions * cargo doc fix * Runtime#create_genesis_account (changes requested during PR review) --- crates/codec/src/api/json/receipt.rs | 8 + crates/codec/src/receipt/error.rs | 30 +- crates/runtime-ffi/src/api.rs | 17 +- crates/runtime-ffi/tests/api_tests.rs | 4 +- crates/runtime/src/func_env.rs | 38 +-- crates/runtime/src/lib.rs | 4 +- crates/runtime/src/price_registry.rs | 3 +- crates/runtime/src/runtime/call.rs | 12 +- crates/runtime/src/runtime/gas_tank.rs | 43 +++ crates/runtime/src/runtime/mod.rs | 4 + crates/runtime/src/runtime/price_cache.rs | 53 +++ crates/runtime/src/runtime/runtime.rs | 397 +++++++++++----------- crates/runtime/src/testing.rs | 28 +- crates/runtime/src/vmcalls/mod.rs | 1 - crates/runtime/tests/runtime_tests.rs | 2 +- crates/runtime/tests/vmcalls_tests.rs | 14 +- crates/state/src/account_storage.rs | 4 + crates/types/src/error.rs | 4 + crates/types/src/template/mod.rs | 21 +- crates/types/src/template/section.rs | 5 + 20 files changed, 411 insertions(+), 281 deletions(-) create mode 100644 crates/runtime/src/runtime/gas_tank.rs create mode 100644 crates/runtime/src/runtime/price_cache.rs diff --git a/crates/codec/src/api/json/receipt.rs b/crates/codec/src/api/json/receipt.rs index 7c554be51..720968bdd 100644 --- a/crates/codec/src/api/json/receipt.rs +++ b/crates/codec/src/api/json/receipt.rs @@ -99,6 +99,14 @@ fn decode_error(ty: &'static str, err: &RuntimeError, logs: &[ReceiptLog]) -> Va "func": func, "message": msg, }), + RuntimeError::FuncNotCtor { + template: template_addr, + func, + } => json!({ + "err_type": "function-not-ctor", + "template_addr": TemplateAddrWrapper::from(*template_addr), + "func": func, + }), RuntimeError::FuncNotAllowed { target: account_addr, template: template_addr, diff --git a/crates/codec/src/receipt/error.rs b/crates/codec/src/receipt/error.rs index e29fe46ae..e8ea82b60 100644 --- a/crates/codec/src/receipt/error.rs +++ b/crates/codec/src/receipt/error.rs @@ -65,6 +65,12 @@ //! | (20 bytes) | (20 bytes) | (String) | (UTF-8 String) | //! +-------------------+------------------+------------+----------------+ //! +//! * Function Not Ctor +//! +-------------------+-------------------+----------------+ +//! | Template Address | Function | Message | +//! | (20 bytes) | (String) | (UTF-8 String) | +//! +-------------------+-------------------+----------------+ +//! //! * Function Not Allowed //! +-------------------+-------------------+------------+----------------+ //! | Template Address | Account Address | Function | Message | @@ -128,6 +134,10 @@ impl Codec for RuntimeFailure { func.encode(w); encode_msg(&msg, w); } + RuntimeError::FuncNotCtor { template, func } => { + template.encode(w); + func.encode(w); + } RuntimeError::FuncNotAllowed { target, template, @@ -164,8 +174,9 @@ impl Codec for RuntimeFailure { 4 => instantiation_error(cursor), 5 => func_not_found(cursor), 6 => func_failed(cursor), - 7 => func_not_allowed(cursor), - 8 => func_invalid_sig(cursor), + 7 => func_not_ctor(cursor), + 8 => func_not_allowed(cursor), + 9 => func_invalid_sig(cursor), _ => unreachable!(), } }; @@ -193,8 +204,9 @@ fn encode_err_type(err: &RuntimeError, w: &mut impl WriteExt) { RuntimeError::InstantiationFailed { .. } => 4, RuntimeError::FuncNotFound { .. } => 5, RuntimeError::FuncFailed { .. } => 6, - RuntimeError::FuncNotAllowed { .. } => 7, - RuntimeError::FuncInvalidSignature { .. } => 8, + RuntimeError::FuncNotCtor { .. } => 7, + RuntimeError::FuncNotAllowed { .. } => 8, + RuntimeError::FuncInvalidSignature { .. } => 9, }; w.write_byte(ty); @@ -264,6 +276,16 @@ fn func_failed(cursor: &mut impl ReadExt) -> RuntimeError { } } +fn func_not_ctor(cursor: &mut impl ReadExt) -> RuntimeError { + let template_addr = TemplateAddr::decode(cursor).unwrap(); + let func = String::decode(cursor).unwrap(); + + RuntimeError::FuncNotCtor { + template: template_addr, + func, + } +} + fn func_not_allowed(cursor: &mut impl ReadExt) -> RuntimeError { let template_addr = TemplateAddr::decode(cursor).unwrap(); let account_addr = Address::decode(cursor).unwrap(); diff --git a/crates/runtime-ffi/src/api.rs b/crates/runtime-ffi/src/api.rs index 79e3744bb..226b09158 100644 --- a/crates/runtime-ffi/src/api.rs +++ b/crates/runtime-ffi/src/api.rs @@ -8,7 +8,7 @@ use std::panic::UnwindSafe; use std::slice; use svm_codec::Codec; -use svm_runtime::{PriceResolverRegistry, Runtime, ValidateError}; +use svm_runtime::{PriceResolverRegistry, Runtime, TemplatePriceCache, ValidateError}; use svm_state::GlobalState; use svm_types::{Address, BytesPrimitive, Context, Envelope, Layer, State}; @@ -68,7 +68,8 @@ pub unsafe extern "C" fn svm_runtime_create( } *initialized = true; - let imports = ("sm".to_string(), wasmer::Exports::new()); + // TODO: move both `GlobalState` and `TemplatePriceCache` to sit under `Env`. + // `Env` be a singleton living throughout the process' lifetime. let global_state = if path.is_null() { GlobalState::in_memory() } else { @@ -76,12 +77,8 @@ pub unsafe extern "C" fn svm_runtime_create( GlobalState::new(std::str::from_utf8(db_path).expect("Invalid UTF-8 path.")) }; - let runtime = Runtime::new( - imports, - global_state, - PriceResolverRegistry::default(), - None, - ); + let registry = PriceResolverRegistry::default(); + let runtime = Runtime::new(global_state, TemplatePriceCache::new(registry)); *runtime_ptr = RUNTIME_TRACKER.alloc(runtime); debug!("`svm_runtime_create` end"); @@ -545,7 +542,7 @@ pub unsafe extern "C" fn svm_get_account( /// Creates an account at genesis with a given balance and nonce counter. #[no_mangle] #[must_use] -pub unsafe extern "C" fn svm_create_account( +pub unsafe extern "C" fn svm_create_genesis_account( runtime_ptr: *mut c_void, addr: *const u8, balance: u64, @@ -556,7 +553,7 @@ pub unsafe extern "C" fn svm_create_account( let runtime = RUNTIME_TRACKER.get(runtime_ptr).unwrap(); let account_addr = Address::new(slice::from_raw_parts(addr, Address::N)); let counter = ((counter_upper_bits as u128) << 64) | (counter_lower_bits as u128); - runtime.create_account(&account_addr, "".to_string(), balance, counter)?; + runtime.create_genesis_account(&account_addr, "".to_string(), balance, counter)?; svm_result_t::OK }) diff --git a/crates/runtime-ffi/tests/api_tests.rs b/crates/runtime-ffi/tests/api_tests.rs index 74a6e4615..b8b4292dc 100644 --- a/crates/runtime-ffi/tests/api_tests.rs +++ b/crates/runtime-ffi/tests/api_tests.rs @@ -222,8 +222,8 @@ fn svm_transfer_success() { let mut runtime = std::ptr::null_mut(); api::svm_runtime_create(&mut runtime, std::ptr::null(), 0).unwrap(); - api::svm_create_account(runtime, src_addr.as_slice().as_ptr(), 1000, 0, 0).unwrap(); - api::svm_create_account(runtime, dst_addr.as_slice().as_ptr(), 0, 0, 0).unwrap(); + api::svm_create_genesis_account(runtime, src_addr.as_slice().as_ptr(), 1000, 0, 0).unwrap(); + api::svm_create_genesis_account(runtime, dst_addr.as_slice().as_ptr(), 0, 0, 0).unwrap(); api::svm_transfer( runtime, src_addr.as_slice().as_ptr(), diff --git a/crates/runtime/src/func_env.rs b/crates/runtime/src/func_env.rs index e822cc9be..baacc2002 100644 --- a/crates/runtime/src/func_env.rs +++ b/crates/runtime/src/func_env.rs @@ -28,7 +28,7 @@ impl FuncEnv { context: &Context, template_addr: TemplateAddr, target_addr: Address, - mode: ProtectedMode, + mode: AccessMode, ) -> Self { let inner = Inner::new(storage); @@ -39,7 +39,7 @@ impl FuncEnv { envelope: envelope.clone(), context: context.clone(), }; - env.set_protected_mode(mode); + env.set_access_mode(mode); env } @@ -52,11 +52,10 @@ impl FuncEnv { context: &Context, template_addr: TemplateAddr, target_addr: Address, - mode: ProtectedMode, + mode: AccessMode, ) -> Self { let env = Self::new(storage, envelope, context, template_addr, target_addr, mode); env.borrow_mut().set_memory(memory); - env } @@ -86,14 +85,14 @@ impl FuncEnv { .expect("Attempted write but RwLock is poisoned") } - /// Sets the [`ProtectedMode`] and overrides the existing value. - pub fn set_protected_mode(&self, mode: ProtectedMode) { + /// Sets the [`AccessMode`] and overrides the existing value. + pub fn set_access_mode(&self, mode: AccessMode) { let mut borrow = self.borrow_mut(); - borrow.set_protected_mode(mode); + borrow.set_access_mode(mode); } - /// Returns the current [`ProtectedMode`]. - pub fn protected_mode(&self) -> ProtectedMode { + /// Returns the current [`AccessMode`]. + pub fn access_mode(&self) -> AccessMode { let borrow = self.borrow(); borrow.mode } @@ -118,17 +117,22 @@ pub struct Inner { /// Pointer to `calldata`. Tuple stores `(offset, len)`. calldata: Option<(usize, usize)>, - mode: ProtectedMode, + /// The current [`AccessMode`] of the running transaction. + mode: AccessMode, + /// Set of [`Address`] that have been part of at least once `Coins Transfer` during transaction execution. touched_accounts: HashSet
, } /// Denotes the capabilities allowed to the executing Account at a given point in time. #[derive(Debug, Copy, Clone, PartialEq)] -pub enum ProtectedMode { +pub enum AccessMode { /// Access to [`AccountStorage`] is not allowed. AccessDenied, + /// Only `Read Access` to [AccountStorage]'s `Immutable Storage` is allowed. + ImmutableOnly, + /// Full-Access to [`AccountStorage`] is allowed. FullAccess, } @@ -146,7 +150,7 @@ impl Inner { calldata: None, returndata: None, used_memory: 0, - mode: ProtectedMode::AccessDenied, + mode: AccessMode::AccessDenied, touched_accounts, } } @@ -160,19 +164,17 @@ impl Inner { self.touched_accounts.clone() } - pub fn set_protected_mode(&mut self, mode: ProtectedMode) { + pub fn set_access_mode(&mut self, mode: AccessMode) { self.mode = mode; } pub fn storage(&self) -> &AccountStorage { assert!(self.can_read()); - &self.storage } pub fn storage_mut(&mut self) -> &mut AccountStorage { assert!(self.can_write()); - &mut self.storage } @@ -202,7 +204,6 @@ impl Inner { ); debug_assert!(self.returndata.is_none()); - self.returndata = Some((offset, len)); } @@ -216,7 +217,6 @@ impl Inner { pub fn memory(&self) -> &Memory { debug_assert!(self.memory.is_some()); - self.memory.as_ref().unwrap() } @@ -238,11 +238,11 @@ impl Inner { #[inline] fn can_read(&self) -> bool { - self.mode != ProtectedMode::AccessDenied + self.mode != AccessMode::AccessDenied } #[inline] fn can_write(&self) -> bool { - matches!(self.mode, ProtectedMode::FullAccess) + matches!(self.mode, AccessMode::FullAccess) } } diff --git a/crates/runtime/src/lib.rs b/crates/runtime/src/lib.rs index c0e05b0ca..8727a3947 100644 --- a/crates/runtime/src/lib.rs +++ b/crates/runtime/src/lib.rs @@ -20,7 +20,7 @@ pub mod testing; pub mod vmcalls; pub use error::ValidateError; -pub use func_env::{FuncEnv, ProtectedMode}; +pub use func_env::{AccessMode, FuncEnv}; pub use price_registry::PriceResolverRegistry; -pub use runtime::{compute_account_addr, compute_template_addr, Runtime}; +pub use runtime::{compute_account_addr, compute_template_addr, Runtime, TemplatePriceCache}; pub use wasm_store::new_store; diff --git a/crates/runtime/src/price_registry.rs b/crates/runtime/src/price_registry.rs index 3180c76e1..ef3726e75 100644 --- a/crates/runtime/src/price_registry.rs +++ b/crates/runtime/src/price_registry.rs @@ -26,8 +26,7 @@ impl PriceResolverRegistry { self.price_resolvers.insert(version, price_resolver); } - /// Retrieves the [`PriceResolver`] associated with a certain SVM version - /// within `self`. + /// Retrieves the [`PriceResolver`] associated with a certain SVM version within `self`. pub fn get(&self, version: u16) -> Option> { self.price_resolvers.get(&version).cloned() } diff --git a/crates/runtime/src/runtime/call.rs b/crates/runtime/src/runtime/call.rs index d81147c77..41dc235ad 100644 --- a/crates/runtime/src/runtime/call.rs +++ b/crates/runtime/src/runtime/call.rs @@ -1,6 +1,8 @@ -use svm_types::{Address, Context, Envelope, Gas, State, TemplateAddr}; +use svm_types::{Address, Context, Envelope, State, TemplateAddr}; -use crate::ProtectedMode; +use crate::AccessMode; + +use super::gas_tank::GasTank; /// Information regarding a Wasm function call. #[doc(hidden)] @@ -9,11 +11,11 @@ pub struct Call<'a> { pub func_name: &'a str, pub func_input: &'a [u8], pub target: Address, - pub template: TemplateAddr, + pub template_addr: TemplateAddr, pub state: &'a State, - pub gas_limit: Gas, + pub gas_left: GasTank, pub within_spawn: bool, pub context: &'a Context, pub envelope: &'a Envelope, - pub protected_mode: ProtectedMode, + pub access_mode: AccessMode, } diff --git a/crates/runtime/src/runtime/gas_tank.rs b/crates/runtime/src/runtime/gas_tank.rs new file mode 100644 index 000000000..76f4dd023 --- /dev/null +++ b/crates/runtime/src/runtime/gas_tank.rs @@ -0,0 +1,43 @@ +use svm_types::Gas; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum GasTank { + NonEmpty(u64), + Empty, +} + +impl GasTank { + pub fn new(gas: Gas) -> Self { + let gas = gas.unwrap_or(std::u64::MAX); + + if gas > 0 { + GasTank::NonEmpty(gas) + } else { + GasTank::Empty + } + } + + pub fn is_empty(&self) -> bool { + matches!(self, Self::Empty) + } + + pub fn consume(self, gas: u64) -> GasTank { + match self { + GasTank::Empty => GasTank::Empty, + GasTank::NonEmpty(left) => { + if left > gas { + GasTank::NonEmpty(left - gas) + } else { + GasTank::Empty + } + } + } + } + + pub fn unwrap(self) -> u64 { + match self { + GasTank::Empty => 0, + GasTank::NonEmpty(gas) => gas, + } + } +} diff --git a/crates/runtime/src/runtime/mod.rs b/crates/runtime/src/runtime/mod.rs index 0f9b1e118..d20ec212c 100644 --- a/crates/runtime/src/runtime/mod.rs +++ b/crates/runtime/src/runtime/mod.rs @@ -3,9 +3,13 @@ mod call; mod function; mod outcome; +mod price_cache; mod runtime; +mod gas_tank; + pub use call::Call; pub use function::Function; pub use outcome::Outcome; +pub use price_cache::TemplatePriceCache; pub use runtime::{compute_account_addr, compute_template_addr, Runtime}; diff --git a/crates/runtime/src/runtime/price_cache.rs b/crates/runtime/src/runtime/price_cache.rs new file mode 100644 index 000000000..64b5ef0e2 --- /dev/null +++ b/crates/runtime/src/runtime/price_cache.rs @@ -0,0 +1,53 @@ +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +use svm_gas::{FuncPrice, ProgramPricing}; +use svm_program::{Program, ProgramVisitor}; +use svm_types::TemplateAddr; + +use crate::PriceResolverRegistry; + +/// A naive cache for [`Template`](svm_types::Template)s' [`FuncPrice`]s. +/// +/// In the future, the cache key will also include an identifier for which +/// [`PriceResolver`](svm_gas::PriceResolver) should be used (possibly an `u16`?). +pub struct TemplatePriceCache { + registry: PriceResolverRegistry, + cache: Rc>>, +} + +impl TemplatePriceCache { + /// New Cache + pub fn new(registry: PriceResolverRegistry) -> Self { + Self { + registry, + cache: Rc::new(RefCell::new(HashMap::new())), + } + } + + /// We're using a naive memoization mechanism: we only ever add, never remove. + /// This means there's no cache invalidation at all. + /// We can easily afford to do this because the number of [`Template`](svm_types::Template)s upon Genesis is fixed and won't grow. + pub fn price_of(&self, template_addr: &TemplateAddr, program: &Program) -> FuncPrice { + let mut cache = self.cache.borrow_mut(); + + if let Some(prices) = cache.get(&template_addr) { + prices.clone() + } else { + let resolver = self.registry.get(0).expect("Missing pricing resolver"); + + let pp = ProgramPricing::new(resolver); + let prices = pp.visit(&program).unwrap(); + + cache.insert(template_addr.clone(), prices); + cache.get(template_addr).unwrap().clone() + } + } +} + +// let template_prices = template_prices.unwrap_or_default(); +// +// `template_prices` offers an easy way to inject an append-only, naive caching mechanism to +// the [`Template`] pricing logic; using a `None` will result in a new +// empty cache and on-the-fly calculation for all [`Template`]s. diff --git a/crates/runtime/src/runtime/runtime.rs b/crates/runtime/src/runtime/runtime.rs index 3eb460839..e97ec9238 100644 --- a/crates/runtime/src/runtime/runtime.rs +++ b/crates/runtime/src/runtime/runtime.rs @@ -2,24 +2,19 @@ use log::info; use svm_codec::Codec; use wasmer::{Instance, Module, WasmPtr, WasmTypeList}; -use std::cell::RefCell; -use std::collections::HashMap; -use std::rc::Rc; - -use svm_gas::{FuncPrice, ProgramPricing}; use svm_hash::{Blake3Hasher, Hasher}; -use svm_program::{Program, ProgramVisitor}; +use svm_program::Program; use svm_state::{AccountStorage, GlobalState, TemplateStorage}; use svm_types::{ Address, BytesPrimitive, CallReceipt, Context, DeployReceipt, Envelope, Gas, GasMode, Layer, - OOGError, ReceiptLog, RuntimeError, RuntimeFailure, Sections, SpawnAccount, SpawnReceipt, - State, Template, TemplateAddr, Transaction, + OOGError, ReceiptLog, RuntimeError, RuntimeFailure, SectionKind, Sections, SpawnAccount, + SpawnReceipt, State, Template, TemplateAddr, Transaction, }; -use super::{Call, Function, Outcome}; +use super::gas_tank::GasTank; +use super::{Call, Function, Outcome, TemplatePriceCache}; use crate::error::ValidateError; -use crate::price_registry::PriceResolverRegistry; -use crate::{vmcalls, FuncEnv, ProtectedMode}; +use crate::{vmcalls, AccessMode, FuncEnv}; type OutcomeResult = std::result::Result, RuntimeFailure>; type Result = std::result::Result; @@ -30,50 +25,26 @@ const ERR_VALIDATE_DEPLOY: &str = "Should have called `validate_deploy` first"; /// An SVM runtime implementation based on [`Wasmer`](https://wasmer.io). pub struct Runtime { - /// Provided host functions to be consumed by running transactions. - imports: (String, wasmer::Exports), + /// The [`GlobalState`] gs: GlobalState, - price_registry: PriceResolverRegistry, - /// A naive cache for [`Template`]s' [`FuncPrice`]s. The cache key will, in - /// the future, also include an identifier for which - /// [`PriceResolver`](svm_gas::PriceResolver) should be used (possibly an - /// `u16`?). - template_prices: Rc>>, + + /// A Cache for a [`Template`]'= functions prices. + template_price: TemplatePriceCache, } impl Runtime { /// Initializes a new [`Runtime`]. - /// - /// `template_prices` offers an easy way to inject an append-only, naive caching mechanism to - /// the [`Template`] pricing logic; using a `None` will result in a new - /// empty cache and on-the-fly calculation for all [`Template`]s. - pub fn new( - imports: (String, wasmer::Exports), - global_state: GlobalState, - price_registry: PriceResolverRegistry, - template_prices: Option>>>, - ) -> Self { - let template_prices = template_prices.unwrap_or_default(); - - Self { - imports, - gs: global_state, - template_prices, - price_registry, - } - } - - fn failure_to_receipt(&self, fail: RuntimeFailure) -> CallReceipt { - CallReceipt::from_err(fail.err, fail.logs) + pub fn new(gs: GlobalState, template_price: TemplatePriceCache) -> Self { + Self { gs, template_price } } fn call_ctor( &mut self, spawn: &SpawnAccount, target: Address, - gas_left: Gas, envelope: &Envelope, context: &Context, + gas_left: GasTank, ) -> SpawnReceipt { let template = spawn.template_addr().clone(); @@ -81,11 +52,11 @@ impl Runtime { func_name: spawn.ctor_name(), func_input: spawn.ctor_data(), state: &State::zeros(), - template, + template_addr: template, target: target.clone(), within_spawn: true, - gas_limit: gas_left, - protected_mode: ProtectedMode::FullAccess, + gas_left, + access_mode: AccessMode::FullAccess, envelope, context, }; @@ -102,8 +73,7 @@ impl Runtime { fn exec_call<'a, Args, Rets>(&'a mut self, call: &Call<'a>) -> CallReceipt { let result = self.exec::<(), (), _, _>(&call, |env, out| outcome_to_receipt(env, out)); - - result.unwrap_or_else(|fail| self.failure_to_receipt(fail)) + result.unwrap_or_else(|fail| CallReceipt::from_err(fail.err, fail.logs)) } fn exec(&self, call: &Call, f: F) -> Result @@ -113,24 +83,15 @@ impl Runtime { F: Fn(&FuncEnv, Outcome>) -> R, { let template = self.account_template(&call.target)?; - - let storage = AccountStorage::create( - self.gs.clone(), - &call.target, - "NAME_TODO".to_string(), - call.template, - 0, - 0, - ) - .unwrap(); + let storage = AccountStorage::load(self.gs.clone(), &call.target).unwrap(); let mut env = FuncEnv::new( storage, call.envelope, call.context, - call.template.clone(), + call.template_addr.clone(), call.target.clone(), - call.protected_mode, + call.access_mode, ); let store = crate::wasm_store::new_store(); @@ -152,9 +113,9 @@ impl Runtime { Args: WasmTypeList, Rets: WasmTypeList, { - self.validate_call_contents(call, template, func_env)?; + self.validate_func_usage(call, template, func_env)?; - let module = self.compile_template(store, func_env, &template, call.gas_limit)?; + let module = self.compile_template(store, func_env, &template, call.gas_left)?; let instance = self.instantiate(func_env, &module, import_object)?; set_memory(func_env, &instance); @@ -180,7 +141,7 @@ impl Runtime { Ok(out) } - Err(..) => Err(RuntimeFailure::new(RuntimeError::OOG, out.logs)), + Err(_) => Err(RuntimeFailure::new(RuntimeError::OOG, out.logs)), } } @@ -216,11 +177,11 @@ impl Runtime { env: &FuncEnv, size: usize, ) -> OutcomeResult> { - // Backups the current [`ProtectedMode`]. - let origin_mode = env.protected_mode(); + // Backups the current [`AccessMode`]. + let origin_mode = env.access_mode(); // Sets `Access Denied` mode while running `svm_alloc`. - env.set_protected_mode(ProtectedMode::AccessDenied); + env.set_access_mode(AccessMode::AccessDenied); let func_name = "svm_alloc"; @@ -228,7 +189,7 @@ impl Runtime { if func.is_err() { // ### Notes: // - // We don't restore the original [`ProtectedMode`] + // We don't restore the original [`AccessMode`] // since `svm_alloc` has failed and the transaction will halt. let err = err::func_not_found(env, func_name); return Err(err); @@ -246,8 +207,8 @@ impl Runtime { WasmPtr::new(offset) }); - // Restores the original [`ProtectedMode`]. - env.set_protected_mode(origin_mode); + // Restores the original [`AccessMode`]. + env.set_access_mode(origin_mode); Ok(out) } @@ -331,31 +292,39 @@ impl Runtime { ) -> wasmer::ImportObject { let mut import_object = wasmer::ImportObject::new(); - // Registering SVM internals - let mut internals = wasmer::Exports::new(); - vmcalls::wasmer_register(store, env, &mut internals); - import_object.register("svm", internals); - - // Registering the externals provided to the Runtime - let (name, exports) = &self.imports; - debug_assert_ne!(name, "svm"); - - import_object.register(name, exports.clone()); - + // Registering SVM host functions. + let mut exports = wasmer::Exports::new(); + vmcalls::wasmer_register(store, env, &mut exports); + import_object.register("svm", exports); import_object } fn account_template( &self, account_addr: &Address, - ) -> std::result::Result { - let accounts = AccountStorage::load(self.gs.clone(), account_addr).unwrap(); - let template_addr = accounts.template_addr().unwrap(); + ) -> std::result::Result { + // TODO: + // + // * Return a `RuntimeFailure` when `account_addr` doesn't exist. + let account = AccountStorage::load(self.gs.clone(), account_addr).unwrap(); + let template_addr = account.template_addr().unwrap(); + self.load_template(&template_addr) + } + + fn load_template( + &self, + template_addr: &TemplateAddr, + ) -> std::result::Result { let template_storage = TemplateStorage::load(self.gs.clone(), &template_addr).unwrap(); let sections = template_storage.sections().unwrap(); + let template = Template::from_sections(sections); - // TODO: Only fetch core sections. - Ok(Template::from_sections(sections)) + // TODO: + // + // * Return a `RuntimeFailure` when `Template` doesn't exist. + // * Fetch only the `Core Sections`. + // * Add `non_core` sections to be fetched as a param (optional) + Ok(template) } fn compile_template( @@ -363,15 +332,13 @@ impl Runtime { store: &wasmer::Store, env: &FuncEnv, template: &Template, - gas_left: Gas, + _gas_left: GasTank, ) -> std::result::Result { let module_res = Module::from_binary(store, template.code()); - let _gas_left = gas_left.unwrap_or(0); - module_res.map_err(|err| err::compilation_failed(env, err)) } - fn validate_call_contents( + fn validate_func_usage( &self, call: &Call, template: &Template, @@ -382,24 +349,85 @@ impl Runtime { // * call // * other factors - let spawning = call.within_spawn; - let ctor = template.is_ctor(call.func_name); + if call.within_spawn { + self.ensure_ctor(&call.template_addr, template, &call.func_name) + } else { + self.ensure_not_ctor(template, env, call) + } + } - if spawning && !ctor { - let msg = "expected function to be a constructor"; - let err = err::func_not_allowed(env, call.func_name, msg); + fn ensure_ctor( + &self, + template_addr: &TemplateAddr, + template: &Template, + func_name: &str, + ) -> std::result::Result<(), RuntimeFailure> { + debug_assert!(template.contains(SectionKind::Ctors)); - return Err(err); + if template.is_ctor(func_name) { + Ok(()) + } else { + let err = err::func_not_ctor(template_addr, func_name); + Err(err) } + } - if !spawning && ctor { - let msg = "expected function to be a non-constructor"; + fn ensure_not_ctor( + &self, + template: &Template, + env: &FuncEnv, + call: &Call, + ) -> std::result::Result<(), RuntimeFailure> { + if template.is_ctor(call.func_name) { + let msg = "expected function not to be a constructor"; let err = err::func_not_allowed(env, call.func_name, msg); + Err(err) + } else { + Ok(()) + } + } - return Err(err); + fn check_gas_for_payload( + &self, + _envelope: &Envelope, + message: &[u8], + gas_left: GasTank, + ) -> GasTank { + if gas_left.is_empty() { + return GasTank::Empty; } - Ok(()) + // TODO: take into account the `Envelope` as well (not only the `Message`) + let payload_price = svm_gas::transaction::spawn(message); + gas_left.consume(payload_price) + } + + fn check_gas_for_func( + &self, + template_addr: &TemplateAddr, + template: &Template, + func_name: &str, + gas_left: GasTank, + ) -> GasTank { + if gas_left.is_empty() { + return GasTank::Empty; + } + + let code_section = template.code_section(); + let code = code_section.code(); + let gas_mode = code_section.gas_mode(); + let program = Program::new(code, false).unwrap(); + + match gas_mode { + GasMode::Fixed => { + let func_index = program.exports().get(func_name).unwrap(); + let func_price = self.template_price.price_of(&template_addr, &program); + + let price = func_price.get(func_index) as u64; + gas_left.consume(price) + } + GasMode::Metering => unreachable!("Not supported yet... (TODO)"), + } } fn build_call<'a>( @@ -407,9 +435,10 @@ impl Runtime { tx: &'a Transaction, envelope: &'a Envelope, context: &'a Context, - protected_mode: ProtectedMode, + access_mode: AccessMode, func_name: &'a str, func_input: &'a [u8], + gas_left: GasTank, ) -> Call<'a> { let target = tx.target(); let account_storage = AccountStorage::load(self.gs.clone(), target).unwrap(); @@ -419,17 +448,17 @@ impl Runtime { func_name, func_input, target: target.clone(), - template, + template_addr: template, state: context.state(), - gas_limit: envelope.gas_limit(), - protected_mode, + gas_left, + access_mode, within_spawn: false, envelope, context, } } - /// Returns the state root hash and layer ID of the last layer. + /// Returns the [`State`] root hash and [`Layer`] of the last layer. pub fn current_layer(&mut self) -> (Layer, State) { self.gs.current_layer().unwrap() } @@ -446,10 +475,31 @@ impl Runtime { Ok(()) } - /// Creates a new account at genesis with the given information. + /// Creates a new `Genesis Account`. + pub fn create_genesis_account( + &mut self, + account_addr: &Address, + name: String, + balance: u64, + counter: u128, + ) -> Result<()> { + self.create_account( + account_addr, + TemplateAddr::god_template(), + name, + balance, + counter, + ) + .unwrap(); + + Ok(()) + } + + /// Creates a new `Account` with the given params. pub fn create_account( &mut self, account_addr: &Address, + template_addr: TemplateAddr, name: String, balance: u64, counter: u128, @@ -458,7 +508,7 @@ impl Runtime { self.gs.clone(), account_addr, name, - TemplateAddr::god_template(), + template_addr, balance, counter, ) @@ -506,16 +556,21 @@ impl Runtime { let sections = Sections::decode_bytes(message).expect(ERR_VALIDATE_DEPLOY); let template = Template::from_sections(sections); - let gas_limit = envelope.gas_limit(); - let install_price = svm_gas::transaction::deploy(message); + let gas_left = envelope.gas_limit(); + let deploy_price = svm_gas::transaction::deploy(message); - if gas_limit < install_price { + if gas_left < deploy_price { return DeployReceipt::new_oog(); } - let gas_used = Gas::with(install_price); + let gas_used = Gas::with(deploy_price); let addr = compute_template_addr(&template); + // TODO: + // + // * Create a `Deploy Section` to be added to `TemplateStorage` + // * Have `template.core_sections() and `template.noncore_sections()` + // * Pass to `TemplateStorage` `core sections` and `non-core sections` TemplateStorage::create( self.gs.clone(), &addr, @@ -534,98 +589,44 @@ impl Runtime { message: &[u8], context: &Context, ) -> SpawnReceipt { - // TODO: refactor this function (it has got a bit lengthy...) - info!("Runtime `spawn`"); - let gas_limit = envelope.gas_limit(); - let base = SpawnAccount::decode_bytes(message).expect(ERR_VALIDATE_SPAWN); + let gas_left = GasTank::new(envelope.gas_limit()); + let spawn = SpawnAccount::decode_bytes(message).expect(ERR_VALIDATE_SPAWN); + let template_addr = spawn.account.template_addr(); + let template = self.load_template(&template_addr); - let template_addr = base.account.template_addr(); + if let Err(fail) = template { + return SpawnReceipt::from_err(fail.err, fail.logs); + } - // TODO: load only the `Sections` relevant for spawning - let template_storage = TemplateStorage::load(self.gs.clone(), template_addr).unwrap(); - let sections = template_storage.sections().unwrap(); - let template = Template::from_sections(sections); + let template = template.unwrap(); + let ctor = spawn.ctor_name(); - let code_section = template.code_section(); - let code = code_section.code(); - let gas_mode = code_section.gas_mode(); - let program = Program::new(code, false).unwrap(); + if let Err(fail) = self.ensure_ctor(&template_addr, &template, ctor) { + return SpawnReceipt::from_err(fail.err, fail.logs); + } - // We're using a naive memoization mechanism: we only ever add, never - // remove. This means there's no cache invalidation at all. We can - // easily afford to do this because the number of templates that exist - // at genesis is fixed and won't grow. - let mut template_prices = self.template_prices.borrow_mut(); - let func_price = if let Some(prices) = template_prices.get(&template_addr) { - prices - } else { - let pricer = self - .price_registry - .get(0) - .expect("Missing pricing utility."); - let program_pricing = ProgramPricing::new(pricer); - let prices = program_pricing.visit(&program).unwrap(); - - template_prices.insert(template_addr.clone(), prices); - template_prices.get(template_addr).unwrap() - }; + let gas_left = self.check_gas_for_payload(envelope, message, gas_left); + let gas_left = self.check_gas_for_func(&template_addr, &template, ctor, gas_left); - let spawn = base; - - if !template.is_ctor(spawn.ctor_name()) { - // The [`Template`] is faulty. - let account = spawn.account(); - let account_addr = compute_account_addr(&spawn); - - return SpawnReceipt::from_err( - RuntimeError::FuncNotAllowed { - target: account_addr, - template: account.template_addr().clone(), - func: spawn.ctor_name().to_string(), - msg: "The given function is not a `ctor`.".to_string(), - }, - vec![], - ); + if gas_left.is_empty() { + return SpawnReceipt::new_oog(Vec::new()); } - match gas_mode { - GasMode::Fixed => { - let ctor_func_index = program.exports().get(spawn.ctor_name()).unwrap(); - let price = func_price.get(ctor_func_index) as u64; - if gas_limit <= price { - return SpawnReceipt::new_oog(vec![]); - } - } - GasMode::Metering => unreachable!("Not supported yet... (TODO)"), - } + let account = spawn.account(); + let target = compute_account_addr(&spawn); - // We don't need this anymore! - drop(template_prices); + self.create_account( + &target, + template_addr.clone(), + account.name().to_string(), + 0, + 0, + ) + .unwrap(); - let payload_price = svm_gas::transaction::spawn(message); - let gas_left = gas_limit - payload_price; - - match gas_left { - Ok(gas_left) => { - let account = spawn.account(); - let target = compute_account_addr(&spawn); - - AccountStorage::create( - self.gs.clone(), - &target, - account.name().to_string(), - account.template_addr().clone(), - 0, - 0, - ) - .unwrap(); - - self.call_ctor(&spawn, target, gas_left, envelope, context) - } - Err(..) => SpawnReceipt::new_oog(Vec::new()), - } + self.call_ctor(&spawn, target, envelope, context, gas_left) } /// Verifies a [`Transaction`](svm_types::Transaction) before execution. @@ -636,6 +637,7 @@ impl Runtime { context: &Context, ) -> CallReceipt { let tx = Transaction::decode_bytes(message).expect(ERR_VALIDATE_CALL); + let gas_left = GasTank::new(envelope.gas_limit()); // ### Important: // @@ -648,9 +650,10 @@ impl Runtime { &tx, envelope, context, - ProtectedMode::AccessDenied, + AccessMode::AccessDenied, "svm_verify", tx.verifydata(), + gas_left, ); // TODO: override the `call.gas_limit` with `VERIFY_MAX_GAS` @@ -662,14 +665,16 @@ impl Runtime { /// This function should be called only if the `verify` stage has passed. pub fn call(&mut self, envelope: &Envelope, message: &[u8], context: &Context) -> CallReceipt { let tx = Transaction::decode_bytes(message).expect(ERR_VALIDATE_CALL); + let gas_left = GasTank::new(envelope.gas_limit()); let call = self.build_call( &tx, envelope, context, - ProtectedMode::FullAccess, + AccessMode::FullAccess, tx.func_name(), tx.calldata(), + gas_left, ); self.exec_call::<(), ()>(&call) @@ -871,6 +876,14 @@ mod err { .into() } + pub fn func_not_ctor(template_addr: &TemplateAddr, func_name: &str) -> RuntimeFailure { + RuntimeError::FuncNotCtor { + template: template_addr.clone(), + func: func_name.to_string(), + } + .into() + } + pub fn func_not_allowed(env: &FuncEnv, func_name: &str, msg: &str) -> RuntimeFailure { RuntimeError::FuncNotAllowed { target: env.target_addr().clone(), diff --git a/crates/runtime/src/testing.rs b/crates/runtime/src/testing.rs index 153fd2b19..c684d987a 100644 --- a/crates/runtime/src/testing.rs +++ b/crates/runtime/src/testing.rs @@ -8,8 +8,7 @@ use svm_types::{ TemplateAddr, Transaction, }; -use crate::price_registry::PriceResolverRegistry; -use crate::Runtime; +use crate::{PriceResolverRegistry, Runtime, TemplatePriceCache}; /// Hold a Wasm file in textual or binary form pub enum WasmFile<'a> { @@ -42,28 +41,16 @@ impl<'a> From<&'a [u8]> for WasmFile<'a> { } } -/// Creates an in-memory `Runtime` backed by a `state_kv`. +/// Creates an [`Runtime`] backed by an in-memory [`GlobalState`]. pub fn create_memory_runtime() -> Runtime { - let imports = ("sm".to_string(), wasmer::Exports::new()); - - Runtime::new( - imports, - GlobalState::in_memory(), - PriceResolverRegistry::default(), - None, - ) + let registry = PriceResolverRegistry::default(); + Runtime::new(GlobalState::in_memory(), TemplatePriceCache::new(registry)) } -/// Creates an in-memory `Runtime` backed by a `state_kv`. +/// Creates an [`Runtime`] backed by the [`GlobalState`]. pub fn create_db_runtime(path: &str) -> Runtime { - let imports = ("sm".to_string(), wasmer::Exports::new()); - - Runtime::new( - imports, - GlobalState::new(path), - PriceResolverRegistry::default(), - None, - ) + let registry = PriceResolverRegistry::default(); + Runtime::new(GlobalState::new(path), TemplatePriceCache::new(registry)) } /// Builds a binary `Deploy Template` transaction. @@ -87,7 +74,6 @@ pub fn build_deploy( /// Builds a binary `Spawn Account` transaction. pub fn build_spawn(template: &TemplateAddr, name: &str, ctor: &str, calldata: &[u8]) -> Vec { let spawn = SpawnAccount::new(0, template, name, ctor, calldata); - spawn.encode_to_vec() } diff --git a/crates/runtime/src/vmcalls/mod.rs b/crates/runtime/src/vmcalls/mod.rs index 518d9a5d2..4fac4df58 100644 --- a/crates/runtime/src/vmcalls/mod.rs +++ b/crates/runtime/src/vmcalls/mod.rs @@ -46,7 +46,6 @@ pub fn wasmer_register(store: &Store, env: &FuncEnv, ns: &mut Exports) { ns.insert("svm_store160", func!(store, env, store160)); ns.insert("svm_log", func!(store, env, log)); - ns.insert("svm_transfer", func!(store, env, svm_transfer)); } diff --git a/crates/runtime/tests/runtime_tests.rs b/crates/runtime/tests/runtime_tests.rs index bb2011cdd..1d06ddbf9 100644 --- a/crates/runtime/tests/runtime_tests.rs +++ b/crates/runtime/tests/runtime_tests.rs @@ -227,7 +227,7 @@ fn memory_runtime_spawn_invoking_non_ctor_fails() { assert!(matches!( receipt.error.unwrap(), - RuntimeError::FuncNotAllowed { .. } + RuntimeError::FuncNotCtor { .. } )); } diff --git a/crates/runtime/tests/vmcalls_tests.rs b/crates/runtime/tests/vmcalls_tests.rs index 6d9c74b7f..a4faf847d 100644 --- a/crates/runtime/tests/vmcalls_tests.rs +++ b/crates/runtime/tests/vmcalls_tests.rs @@ -4,7 +4,7 @@ use wasmer::{imports, FromToNativeWasmType, NativeFunc}; use svm_layout::FixedLayout; use svm_runtime::testing::WasmFile; -use svm_runtime::{vmcalls, FuncEnv, ProtectedMode}; +use svm_runtime::{vmcalls, AccessMode, FuncEnv}; use svm_state::{AccountStorage, GlobalState}; use svm_types::{Address, BytesPrimitive, Context, Envelope, ReceiptLog, TemplateAddr}; @@ -134,7 +134,7 @@ fn vmcalls_get32_set32() { &context, template_addr, target_addr, - ProtectedMode::FullAccess, + AccessMode::FullAccess, ); let import_object = imports! { @@ -180,7 +180,7 @@ fn vmcalls_get64_set64() { &context, template_addr, target_addr, - ProtectedMode::FullAccess, + AccessMode::FullAccess, ); let import_object = imports! { @@ -228,7 +228,7 @@ fn vmcalls_load160() { &context, template_addr, target_addr.clone(), - ProtectedMode::FullAccess, + AccessMode::FullAccess, ); let import_object = imports! { @@ -282,7 +282,7 @@ fn vmcalls_store160() { &context, template_addr, target_addr.clone(), - ProtectedMode::FullAccess, + AccessMode::FullAccess, ); let import_object = imports! { @@ -331,7 +331,7 @@ fn vmcalls_log() { &context, template_addr, target_addr, - ProtectedMode::AccessDenied, + AccessMode::AccessDenied, ); let import_object = imports! { @@ -390,7 +390,7 @@ fn setup_svm_transfer_test() -> ( &context, template, src_addr.clone(), - ProtectedMode::FullAccess, + AccessMode::FullAccess, ); let import_object = imports! { diff --git a/crates/state/src/account_storage.rs b/crates/state/src/account_storage.rs index fd71e3099..35d2ad508 100644 --- a/crates/state/src/account_storage.rs +++ b/crates/state/src/account_storage.rs @@ -19,7 +19,11 @@ pub struct AccountStorage { /// The owner's [`Address`] of this [`AccountStorage`]. pub address: Address, + + /// The [`TemplateAddr`] associated with `Account`. template_addr: TemplateAddr, + + /// The `Account`'s layout. layout: FixedLayout, } diff --git a/crates/types/src/error.rs b/crates/types/src/error.rs index 12ef98f5f..e5fb65b8b 100644 --- a/crates/types/src/error.rs +++ b/crates/types/src/error.rs @@ -29,6 +29,10 @@ pub enum RuntimeError { func: String, msg: String, }, + FuncNotCtor { + template: TemplateAddr, + func: String, + }, FuncNotAllowed { target: Address, template: TemplateAddr, diff --git a/crates/types/src/template/mod.rs b/crates/types/src/template/mod.rs index 7079c1b94..cca184f39 100644 --- a/crates/types/src/template/mod.rs +++ b/crates/types/src/template/mod.rs @@ -105,7 +105,6 @@ impl Template { /// Panics if there is no `Header Section` pub fn header_section(&self) -> &HeaderSection { let section = self.get(SectionKind::Header); - section.as_header() } @@ -116,7 +115,6 @@ impl Template { /// Panics if there is no `Code Section` pub fn code_section(&self) -> &CodeSection { let section = self.get(SectionKind::Code); - section.as_code() } @@ -127,7 +125,6 @@ impl Template { /// Panics if there is no `Code Section` pub fn code(&self) -> &[u8] { let section = self.code_section(); - section.code() } @@ -138,7 +135,6 @@ impl Template { /// Panics if there is no `Data Section` pub fn data_section(&self) -> &DataSection { let section = self.get(SectionKind::Data); - section.as_data() } @@ -149,7 +145,6 @@ impl Template { let layouts = data.layouts(); let layout = layouts.first().unwrap(); - layout.as_fixed() } @@ -160,7 +155,6 @@ impl Template { /// Panics if there is no `Ctors Section` pub fn ctors_section(&self) -> &CtorsSection { let section = self.get(SectionKind::Ctors); - section.as_ctors() } @@ -171,7 +165,6 @@ impl Template { /// Panics if there is no `Ctors Section` pub fn ctors(&self) -> &[String] { let section = self.ctors_section(); - section.ctors() } @@ -182,11 +175,7 @@ impl Template { /// Panics if there is no `Ctors Section` pub fn is_ctor(&self, func_name: &str) -> bool { let ctors = self.ctors(); - - ctors - .iter() - .find(|ctor| ctor.as_str() == func_name) - .is_some() + ctors.iter().any(|ctor| ctor.as_str() == func_name) } /// Borrows the `Schema Section` @@ -196,7 +185,6 @@ impl Template { /// Panics if there is no `Schema Section` pub fn schema_section(&self) -> &SchemaSection { let section = self.get(SectionKind::Schema); - section.as_schema() } @@ -214,7 +202,6 @@ impl Template { /// Panics if there is no `Deploy Section` pub fn deploy_section(&self) -> &DeploySection { let section = self.get(SectionKind::Deploy); - section.as_deploy() } @@ -225,7 +212,6 @@ impl Template { /// Panics if there is no `Deploy Section` pub fn template_addr(&self) -> &TemplateAddr { let section = self.deploy_section(); - section.template() } @@ -244,4 +230,9 @@ impl Template { pub fn try_get(&self, kind: SectionKind) -> Option<&Section> { self.sections.try_get(kind) } + + /// Returns whether Section exists + pub fn contains(&self, kind: SectionKind) -> bool { + self.sections.try_get(kind).is_some() + } } diff --git a/crates/types/src/template/section.rs b/crates/types/src/template/section.rs index 8ab45a9dc..c015457df 100644 --- a/crates/types/src/template/section.rs +++ b/crates/types/src/template/section.rs @@ -262,6 +262,11 @@ impl Sections { self.inner.contains_key(&kind) } + /// Returns the [`Section`]s kinds held. + pub fn kinds(&self) -> indexmap::map::Keys { + self.inner.keys() + } + /// Returns the [`Section`] of the requested `kind`. /// /// # Panics