diff --git a/Cargo.lock b/Cargo.lock index 85fed02a7d..732bfc6072 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1535,6 +1535,12 @@ dependencies = [ "adler", ] +[[package]] +name = "multi-stash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685a9ac4b61f4e728e1d2c6a7844609c16527aeb5e6c865915c08e619c16410f" + [[package]] name = "nix" version = "0.27.1" @@ -1579,6 +1585,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfb77679af88f8b125209d354a202862602672222e7f2313fdd6dc349bad4712" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "num-integer" version = "0.1.45" @@ -2703,10 +2720,13 @@ dependencies = [ [[package]] name = "wasmi" -version = "0.31.1" +version = "0.32.0-beta.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acfc1e384a36ca532d070a315925887247f3c7e23567e23e0ac9b1c5d6b8bf76" +checksum = "6b962e531cc387ce15b1da00f06f727f9a9063619e74d1499c3e443d940378b0" dependencies = [ + "multi-stash", + "num-derive", + "num-traits", "smallvec", "spin", "wasmi_arena", @@ -2722,9 +2742,9 @@ checksum = "401c1f35e413fac1846d4843745589d9ec678977ab35a384db8ae7830525d468" [[package]] name = "wasmi_core" -version = "0.13.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf1a7db34bff95b85c261002720c00c3a6168256dcb93041d3fa2054d19856a" +checksum = "3ac482df6761020b2b75c9aade41c105993c5b9f64156c349bb7ccad226a6ecd" dependencies = [ "downcast-rs", "libm", diff --git a/full-node/src/consensus_service.rs b/full-node/src/consensus_service.rs index 4d68948210..32849349e9 100644 --- a/full-node/src/consensus_service.rs +++ b/full-node/src/consensus_service.rs @@ -328,7 +328,7 @@ impl ConsensusService { executor::host::HostVmPrototype::new(executor::host::Config { module: finalized_code, heap_pages, - exec_hint: executor::vm::ExecHint::CompileAheadOfTime, // TODO: probably should be decided by the optimisticsync + exec_hint: executor::vm::ExecHint::ValidateAndCompile, // TODO: probably should be decided by the optimisticsync allow_unresolved_imports: false, }) .map_err(InitError::FinalizedRuntimeInit)? @@ -2570,7 +2570,7 @@ impl SyncBackground { } all::ProcessOne::WarpSyncBuildRuntime(build_runtime) => { let (new_sync, outcome) = - build_runtime.build(all::ExecHint::CompileAheadOfTime, true); + build_runtime.build(all::ExecHint::ValidateAndCompile, true); self.sync = new_sync; if let Err(err) = outcome { self.log_callback.log( @@ -3165,7 +3165,7 @@ pub async fn execute_block_and_insert( let vm = host::HostVmPrototype::new(host::Config { module: &new_code, heap_pages: new_heap_pages, - exec_hint: executor::vm::ExecHint::CompileAheadOfTime, + exec_hint: executor::vm::ExecHint::ValidateAndCompile, allow_unresolved_imports: false, }) .map_err(ExecuteBlockInvalidBlockError::InvalidNewRuntime)?; diff --git a/full-node/src/json_rpc_service/runtime_caches_service.rs b/full-node/src/json_rpc_service/runtime_caches_service.rs index 943ec816fa..fb443b62b8 100644 --- a/full-node/src/json_rpc_service/runtime_caches_service.rs +++ b/full-node/src/json_rpc_service/runtime_caches_service.rs @@ -105,7 +105,7 @@ impl RuntimeCachesService { executor::host::Config { module: &code, heap_pages, - exec_hint: executor::vm::ExecHint::CompileAheadOfTime, + exec_hint: executor::vm::ExecHint::ValidateAndCompile, allow_unresolved_imports: true, // TODO: configurable? or if not, document }, ) diff --git a/full-node/src/lib.rs b/full-node/src/lib.rs index 1ac6ac8be8..a7f3ad10dd 100644 --- a/full-node/src/lib.rs +++ b/full-node/src/lib.rs @@ -855,7 +855,7 @@ async fn open_database( genesis_storage.value(b":heappages"), ) .unwrap(), - exec_hint: executor::vm::ExecHint::Oneshot, + exec_hint: executor::vm::ExecHint::ValidateAndExecuteOnce, allow_unresolved_imports: true, }) .unwrap() diff --git a/fuzz/fuzz_targets/wasm-module-wasmi.rs b/fuzz/fuzz_targets/wasm-module-wasmi.rs index f287e61ca7..21c21e9d2a 100644 --- a/fuzz/fuzz_targets/wasm-module-wasmi.rs +++ b/fuzz/fuzz_targets/wasm-module-wasmi.rs @@ -21,7 +21,9 @@ libfuzzer_sys::fuzz_target!(|data: &[u8]| { let _ = smoldot::executor::host::HostVmPrototype::new(smoldot::executor::host::Config { module: data, heap_pages: smoldot::executor::DEFAULT_HEAP_PAGES, - exec_hint: smoldot::executor::vm::ExecHint::ForceWasmi, + exec_hint: smoldot::executor::vm::ExecHint::ForceWasmi { + lazy_validation: false, + }, allow_unresolved_imports: true, }); }); diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 8c69ab763f..5def2cd512 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -77,7 +77,7 @@ siphasher = { version = "1.0.0", default-features = false } slab = { version = "0.4.8", default-features = false } smallvec = { version = "1.11.0", default-features = false } twox-hash = { version = "1.6.3", default-features = false } -wasmi = { version = "0.31.0", default-features = false } +wasmi = { version = "0.32.0-beta.5", default-features = false } x25519-dalek = { version = "2.0.0-rc.3", default-features = false, features = ["alloc", "precomputed-tables", "static_secrets", "zeroize"] } zeroize = { version = "1.6.0", default-features = false, features = ["alloc"] } diff --git a/lib/src/chain_spec.rs b/lib/src/chain_spec.rs index 6823c8f927..79184aae25 100644 --- a/lib/src/chain_spec.rs +++ b/lib/src/chain_spec.rs @@ -116,7 +116,7 @@ impl ChainSpec { let vm_prototype = executor::host::HostVmPrototype::new(executor::host::Config { module: &wasm_code, heap_pages, - exec_hint: executor::vm::ExecHint::Oneshot, + exec_hint: executor::vm::ExecHint::ValidateAndExecuteOnce, allow_unresolved_imports: true, }) .map_err(FromGenesisStorageError::VmInitialization)?; diff --git a/lib/src/database/full_sqlite.rs b/lib/src/database/full_sqlite.rs index c3b9bf388a..563bd0dfde 100644 --- a/lib/src/database/full_sqlite.rs +++ b/lib/src/database/full_sqlite.rs @@ -283,7 +283,8 @@ impl SqliteFullDatabase { let Ok(runtime) = host::HostVmPrototype::new(host::Config { module: code, heap_pages, - exec_hint: executor::vm::ExecHint::Oneshot, + exec_hint: + executor::vm::ExecHint::ExecuteOnceWithNonDeterministicValidation, allow_unresolved_imports: true, }) else { todo!() diff --git a/lib/src/executor/host.rs b/lib/src/executor/host.rs index f1fe25afa6..e17debf12d 100644 --- a/lib/src/executor/host.rs +++ b/lib/src/executor/host.rs @@ -152,7 +152,7 @@ //! let prototype = HostVmPrototype::new(Config { //! module: &wasm_binary_code, //! heap_pages: HeapPages::from(2048), -//! exec_hint: smoldot::executor::vm::ExecHint::Oneshot, +//! exec_hint: smoldot::executor::vm::ExecHint::ValidateAndExecuteOnce, //! allow_unresolved_imports: false //! }).unwrap(); //! prototype.run_no_param("Core_version").unwrap().into() diff --git a/lib/src/executor/runtime_call.rs b/lib/src/executor/runtime_call.rs index 6237b959ec..179d807711 100644 --- a/lib/src/executor/runtime_call.rs +++ b/lib/src/executor/runtime_call.rs @@ -1792,7 +1792,7 @@ impl Inner { let vm_prototype = match host::HostVmPrototype::new(host::Config { module: req.wasm_code(), heap_pages: executor::DEFAULT_HEAP_PAGES, - exec_hint: vm::ExecHint::Oneshot, + exec_hint: vm::ExecHint::ValidateAndExecuteOnce, allow_unresolved_imports: false, // TODO: what is a correct value here? }) { Ok(w) => w, diff --git a/lib/src/executor/runtime_call/tests.rs b/lib/src/executor/runtime_call/tests.rs index 1bc93e17cb..bddccbd4f9 100644 --- a/lib/src/executor/runtime_call/tests.rs +++ b/lib/src/executor/runtime_call/tests.rs @@ -74,7 +74,7 @@ fn execute_blocks() { host::HostVmPrototype::new(host::Config { module: code, heap_pages, - exec_hint: crate::executor::vm::ExecHint::Oneshot, + exec_hint: crate::executor::vm::ExecHint::ExecuteOnceWithNonDeterministicValidation, allow_unresolved_imports: false, }) .unwrap() diff --git a/lib/src/executor/vm.rs b/lib/src/executor/vm.rs index 18cf5c5219..f55a52e4dc 100644 --- a/lib/src/executor/vm.rs +++ b/lib/src/executor/vm.rs @@ -157,7 +157,7 @@ impl VirtualMachinePrototype { ), feature = "wasmtime" ))] - ExecHint::CompileAheadOfTime => VirtualMachinePrototypeInner::Jit( + ExecHint::ValidateAndCompile => VirtualMachinePrototypeInner::Jit( jit::JitPrototype::new(config.module_bytes, config.symbols)?, ), #[cfg(not(all( @@ -175,13 +175,41 @@ impl VirtualMachinePrototype { ), feature = "wasmtime" )))] - ExecHint::CompileAheadOfTime => VirtualMachinePrototypeInner::Interpreter( - interpreter::InterpreterPrototype::new(config.module_bytes, config.symbols)?, + ExecHint::ValidateAndCompile => VirtualMachinePrototypeInner::Interpreter( + interpreter::InterpreterPrototype::new( + config.module_bytes, + interpreter::CompilationMode::Eager, + config.symbols, + )?, ), - ExecHint::Oneshot | ExecHint::Untrusted | ExecHint::ForceWasmi => { + ExecHint::ValidateAndExecuteOnce | ExecHint::Untrusted => { VirtualMachinePrototypeInner::Interpreter( interpreter::InterpreterPrototype::new( config.module_bytes, + interpreter::CompilationMode::Eager, + config.symbols, + )?, + ) + } + ExecHint::CompileWithNonDeterministicValidation + | ExecHint::ExecuteOnceWithNonDeterministicValidation => { + VirtualMachinePrototypeInner::Interpreter( + interpreter::InterpreterPrototype::new( + config.module_bytes, + interpreter::CompilationMode::Lazy, + config.symbols, + )?, + ) + } + ExecHint::ForceWasmi { lazy_validation } => { + VirtualMachinePrototypeInner::Interpreter( + interpreter::InterpreterPrototype::new( + config.module_bytes, + if lazy_validation { + interpreter::CompilationMode::Lazy + } else { + interpreter::CompilationMode::Eager + }, config.symbols, )?, ) @@ -829,18 +857,37 @@ impl fmt::Debug for VirtualMachine { pub enum ExecHint { /// The WebAssembly code will be instantiated once and run many times. /// If possible, compile this WebAssembly code ahead of time. - CompileAheadOfTime, + ValidateAndCompile, + + /// The WebAssembly code will be instantiated once and run many times. + /// Contrary to [`ExecHint::ValidateAndCompile`], the WebAssembly code isn't fully validated + /// ahead of time, meaning that invalid WebAssembly modules might successfully be compiled, + /// which is an indesirable property in some contexts. + CompileWithNonDeterministicValidation, + /// The WebAssembly code is expected to be only run once. /// /// > **Note**: This isn't a hard requirement but a hint. - Oneshot, + ValidateAndExecuteOnce, + + /// The WebAssembly code will be instantiated once and run many times. + /// Contrary to [`ExecHint::ValidateAndExecuteOnce`], the WebAssembly code isn't fully + /// validated ahead of time, meaning that invalid WebAssembly modules might successfully be + /// compiled, which is an indesirable property in some contexts. + ExecuteOnceWithNonDeterministicValidation, + /// The WebAssembly code running through this VM is untrusted. Untrusted, /// Forces using the `wasmi` backend. /// /// This variant is useful for testing purposes. - ForceWasmi, + ForceWasmi { + /// If `true`, lazy validation is enabled. This leads to a faster initialization time, + /// but can also successfully validate invalid modules, which is an indesirable property + /// in some contexts. + lazy_validation: bool, + }, /// Forces using the `wasmtime` backend. /// /// This variant is useful for testing purposes. @@ -885,7 +932,10 @@ impl ExecHint { /// /// > **Note**: This function is most useful for testing purposes. pub fn available_engines() -> impl Iterator { - iter::once(ExecHint::ForceWasmi).chain(Self::force_wasmtime_if_available()) + iter::once(ExecHint::ForceWasmi { + lazy_validation: false, + }) + .chain(Self::force_wasmtime_if_available()) } /// Returns `ForceWasmtime` if it is available on the current platform, and `None` otherwise. diff --git a/lib/src/executor/vm/interpreter.rs b/lib/src/executor/vm/interpreter.rs index 8aedb447f9..7224d8c835 100644 --- a/lib/src/executor/vm/interpreter.rs +++ b/lib/src/executor/vm/interpreter.rs @@ -25,6 +25,8 @@ use super::{ use alloc::{borrow::ToOwned as _, string::ToString as _, sync::Arc, vec::Vec}; use core::fmt; +pub use wasmi::CompilationMode; + /// See [`super::VirtualMachinePrototype`]. pub struct InterpreterPrototype { /// Base components that can be used to recreate a prototype later if desired. @@ -52,6 +54,7 @@ impl InterpreterPrototype { /// See [`super::VirtualMachinePrototype::new`]. pub fn new( module_bytes: &[u8], + compilation_mode: CompilationMode, symbols: &mut dyn FnMut(&str, &str, &Signature) -> Result, ) -> Result { let engine = { @@ -66,6 +69,7 @@ impl InterpreterPrototype { config.wasm_mutable_global(false); config.wasm_saturating_float_to_int(false); config.wasm_tail_call(false); + config.compilation_mode(compilation_mode); wasmi::Engine::new(&config) }; @@ -129,7 +133,7 @@ impl InterpreterPrototype { &mut store, func_type.clone(), move |_caller, parameters, _ret| { - Err(wasmi::core::Trap::from(InterruptedTrap { + Err(wasmi::Error::host(InterruptedTrap { function_index, parameters: parameters .iter() diff --git a/lib/src/transactions/validate/tests.rs b/lib/src/transactions/validate/tests.rs index 30e040a699..56c3d52254 100644 --- a/lib/src/transactions/validate/tests.rs +++ b/lib/src/transactions/validate/tests.rs @@ -34,7 +34,7 @@ fn validate_from_proof() { module: hex::decode(&test.runtime_code).unwrap(), heap_pages: executor::DEFAULT_HEAP_PAGES, allow_unresolved_imports: true, - exec_hint: executor::vm::ExecHint::Oneshot, + exec_hint: executor::vm::ExecHint::ExecuteOnceWithNonDeterministicValidation, }) .unwrap(); diff --git a/light-base/src/runtime_service.rs b/light-base/src/runtime_service.rs index 8f73745961..d50dc0e79e 100644 --- a/light-base/src/runtime_service.rs +++ b/light-base/src/runtime_service.rs @@ -2862,7 +2862,7 @@ async fn compile_runtime( let module = code.as_ref().ok_or(RuntimeError::CodeNotFound)?; let heap_pages = executor::storage_heap_pages_to_value(heap_pages.as_deref()) .map_err(RuntimeError::InvalidHeapPages)?; - let exec_hint = executor::vm::ExecHint::CompileAheadOfTime; + let exec_hint = executor::vm::ExecHint::CompileWithNonDeterministicValidation; // We try once with `allow_unresolved_imports: false`. If this fails due to unresolved // import, we try again but with `allowed_unresolved_imports: true`. diff --git a/light-base/src/sync_service/standalone.rs b/light-base/src/sync_service/standalone.rs index e4be5712bf..d518b334aa 100644 --- a/light-base/src/sync_service/standalone.rs +++ b/light-base/src/sync_service/standalone.rs @@ -948,7 +948,11 @@ impl Task { // Warp syncing compiles the runtime. The compiled runtime will later be yielded // in the `WarpSyncFinished` variant, which is then provided as an event. let before_instant = self.platform.now(); - let (new_sync, error) = req.build(all::ExecHint::CompileAheadOfTime, true); + // Because the runtime being compiled has been validated by 2/3rds of the + // validators of the chain, we can assume that it is valid. Doing so significantly + // increases the compilation speed. + let (new_sync, error) = + req.build(all::ExecHint::CompileWithNonDeterministicValidation, true); let elapsed = self.platform.now() - before_instant; match error { Ok(()) => { diff --git a/wasm-node/CHANGELOG.md b/wasm-node/CHANGELOG.md index 84a6ba647f..b88d11ca8b 100644 --- a/wasm-node/CHANGELOG.md +++ b/wasm-node/CHANGELOG.md @@ -4,6 +4,7 @@ ### Changed +- The WebAssembly runtime is now compiled lazily, meaning that only the code that is executed is validated and translated. Thanks to this, compiling a runtime is now four times faster and the time to head of the chain is around 200ms faster. ([#1488](https://github.com/smol-dot/smoldot/pull/1488), [#1577](https://github.com/smol-dot/smoldot/pull/1577)) - Most of the log messages emitted by smoldot have been modified in order to unify their syntax and be easier to parse programatically. ([#1560](https://github.com/smol-dot/smoldot/pull/1560)) - Added support for the `ext_panic_handler_abort_on_panic_version_1` host function. ([#1573](https://github.com/smol-dot/smoldot/pull/1573))