mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-26 14:37:57 +00:00
Integrate Wasmer into Substrate sandbox environment (#5920)
* Add comments and refactor Sandbox module * Adds some comments * Add wasmtime instance to the sandbox and delegate calls * Adds module imports stub * WIP state holder via *mut * My take at the problem * Brings back invoke and instantiate implementation details * Removes redundant bound * Code cleanup * Fixes invoke closure * Refactors FunctionExecutor to eliminate lifetime * Wraps `FunctionExecutor::sandbox_store` in `RefCell` * Renames `FunctionExecutor::heap` to `allocator` * Wraps `FunctionExecutor::allocator` in `RefCell` * Refactors FunctionExecutor to `Rc<Inner>` pattern * Implements scoped TLS for FunctionExecutor * Fixes wasmi instancing * Fixes sandbox asserts * Makes sandbox compile after wasmtime API change * Uses Vurich/wasmtime for the Lightbeam backend * Uses wasmtime instead of wasmi for sandbox API results * Refactors sandbox to use one of the execution backends at a time * Fixes wasmtime module instantiation * TEMP vurich branch stuff * Adds wasmer impl stub * Adds get global * Fixes warnings * Adds wasmer invoke impl * Implements host function interface for wasmer * Fixes wasmer instantiation result * Adds workaround to remove debug_assert * Fixes import object generation for wasmer * Attempt to propagate wasmer::Store through sandbox::Store * Wraps `sandbox::Store::memories` in `RefCell` * Moves `sandbox::instantiate` to `sandbox::Store` * Eliminate `RefCell<memories>` * Implements `HostState::memory_get/set`, removes accidental `borrow_mut` * Fixes sandbox memory handling for wasmi * Fix memory allocation * Resets Cargo.lock to match master * Fixes compilation * Refactors sandbox to use TLS for dispatch_thunk propagation to wasmer * Pass dispatch thunk to the sandbox as a TLS * Initialize dispatch thunk holder in `SandboxInstance` * Comment out Wasmtime/Lightbeam sandbox backend * Revert wasmtime back to mainstream * Adds SandboxExecutionMethod enum for cli param * Cleanup sandbox code * Allow wasmi to access wasmer memory regions * More cleanup * Remove debug logging, replace asserts with runtime errors * Revert "Adds SandboxExecutionMethod enum for cli param" This reverts commit dcb2b1d3b54145ab51ad2e3fef0d980ba215b596. * Fixes warnings * Fixes indentation and line width * Fix return types condition * Puts everything related under the `wasmer-sandbox` feature flag * Fixes warnings * Address grumbles * Split instantiate per backend * More splits * Refacmemory allocation * Nitpicks * Attempt to wrap wasmer memory in protoco enforcing type * Revert renaming * WIP wasm buffer proxy API * Reimplement util::wasmer::MemoryRef to use buffers instead of memory slices * Adds WasmiMemoryWrapper and MemoryTransfer trait * Refactor naming * Perform all memory transfers using MemoryTransfer * Adds allocating `read` * Adds comments * Removes unused imports * Removes now unused function * Pulls Cargo.lock from origin/master * Fix rustdoc * Removes unused `TransferError` * Update Cargo.lock * Removes unused import * cargo fmt * Fix feature dependency graph * Feature should flow from the top level crate * We should not assume a specific workspace structure * sc-executor-wasmi does not use the feature * sc-executor-wasmtime should not know about the feature * Fix doc typo * Enable wasmer-sandbox by default (for now) It will be removed before merge. It is so that the benchbot uses the wasmer sandbox. * cargo run --quiet --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_contracts --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/contracts/src/weights.rs --template=./.maintain/frame-weight-template.hbs * Revert "cargo run --quiet --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_contracts --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/contracts/src/weights.rs --template=./.maintain/frame-weight-template.hbs" This reverts commit d713590ba45387c4204b2ad97c8bd6f6ebabda4e. * cargo fmt * Add ci-check to prevent wasmer sandbox build breaking * Run tests with wasmer-sandbox enabled * Revert "Run tests with wasmer-sandbox enabled" This reverts commit cff63156a162f9ffdab23e7cb94a30f44e320f8a. Co-authored-by: Sergei Shulepov <s.pepyakin@gmail.com> Co-authored-by: Andrew Jones <ascjones@gmail.com> Co-authored-by: Alexander Theißen <alex.theissen@me.com> Co-authored-by: Parity Benchmarking Bot <admin@parity.io>
This commit is contained in:
@@ -0,0 +1,241 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
//! Utilities used by all backends
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use sp_wasm_interface::Pointer;
|
||||
use std::ops::Range;
|
||||
|
||||
/// Construct a range from an offset to a data length after the offset.
|
||||
/// Returns None if the end of the range would exceed some maximum offset.
|
||||
pub fn checked_range(offset: usize, len: usize, max: usize) -> Option<Range<usize>> {
|
||||
let end = offset.checked_add(len)?;
|
||||
if end <= max {
|
||||
Some(offset..end)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides safe memory access interface using an external buffer
|
||||
pub trait MemoryTransfer {
|
||||
/// Read data from a slice of memory into a newly allocated buffer.
|
||||
///
|
||||
/// Returns an error if the read would go out of the memory bounds.
|
||||
fn read(&self, source_addr: Pointer<u8>, size: usize) -> Result<Vec<u8>>;
|
||||
|
||||
/// Read data from a slice of memory into a destination buffer.
|
||||
///
|
||||
/// Returns an error if the read would go out of the memory bounds.
|
||||
fn read_into(&self, source_addr: Pointer<u8>, destination: &mut [u8]) -> Result<()>;
|
||||
|
||||
/// Write data to a slice of memory.
|
||||
///
|
||||
/// Returns an error if the write would go out of the memory bounds.
|
||||
fn write_from(&self, dest_addr: Pointer<u8>, source: &[u8]) -> Result<()>;
|
||||
}
|
||||
|
||||
/// Safe wrapper over wasmi memory reference
|
||||
pub mod wasmi {
|
||||
use super::*;
|
||||
|
||||
/// Wasmi provides direct access to its memory using slices.
|
||||
///
|
||||
/// This wrapper limits the scope where the slice can be taken to
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MemoryWrapper(::wasmi::MemoryRef);
|
||||
|
||||
impl MemoryWrapper {
|
||||
/// Take ownership of the memory region and return a wrapper object
|
||||
pub fn new(memory: ::wasmi::MemoryRef) -> Self {
|
||||
Self(memory)
|
||||
}
|
||||
|
||||
/// Clone the underlying memory object
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The sole purpose of `MemoryRef` is to protect the memory from uncontrolled
|
||||
/// access. By returning the memory object "as is" we bypass all of the checks.
|
||||
///
|
||||
/// Intended to use only during module initialization.
|
||||
pub unsafe fn clone_inner(&self) -> ::wasmi::MemoryRef {
|
||||
self.0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl super::MemoryTransfer for MemoryWrapper {
|
||||
fn read(&self, source_addr: Pointer<u8>, size: usize) -> Result<Vec<u8>> {
|
||||
self.0.with_direct_access(|source| {
|
||||
let range = checked_range(source_addr.into(), size, source.len())
|
||||
.ok_or_else(|| Error::Other("memory read is out of bounds".into()))?;
|
||||
|
||||
Ok(Vec::from(&source[range]))
|
||||
})
|
||||
}
|
||||
|
||||
fn read_into(&self, source_addr: Pointer<u8>, destination: &mut [u8]) -> Result<()> {
|
||||
self.0.with_direct_access(|source| {
|
||||
let range = checked_range(source_addr.into(), destination.len(), source.len())
|
||||
.ok_or_else(|| Error::Other("memory read is out of bounds".into()))?;
|
||||
|
||||
destination.copy_from_slice(&source[range]);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn write_from(&self, dest_addr: Pointer<u8>, source: &[u8]) -> Result<()> {
|
||||
self.0.with_direct_access_mut(|destination| {
|
||||
let range = checked_range(dest_addr.into(), source.len(), destination.len())
|
||||
.ok_or_else(|| Error::Other("memory write is out of bounds".into()))?;
|
||||
|
||||
&mut destination[range].copy_from_slice(source);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Routines specific to Wasmer runtime. Since sandbox can be invoked from both
|
||||
/// wasmi and wasmtime runtime executors, we need to have a way to deal with sanbox
|
||||
/// backends right from the start.
|
||||
#[cfg(feature = "wasmer-sandbox")]
|
||||
pub mod wasmer {
|
||||
use super::checked_range;
|
||||
use crate::error::{Error, Result};
|
||||
use sp_wasm_interface::Pointer;
|
||||
use std::{cell::RefCell, convert::TryInto, rc::Rc};
|
||||
|
||||
/// In order to enforce memory access protocol to the backend memory
|
||||
/// we wrap it with `RefCell` and encapsulate all memory operations.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MemoryWrapper {
|
||||
buffer: Rc<RefCell<wasmer::Memory>>,
|
||||
}
|
||||
|
||||
impl MemoryWrapper {
|
||||
/// Take ownership of the memory region and return a wrapper object
|
||||
pub fn new(memory: wasmer::Memory) -> Self {
|
||||
Self { buffer: Rc::new(RefCell::new(memory)) }
|
||||
}
|
||||
|
||||
/// Returns linear memory of the wasm instance as a slice.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Wasmer doesn't provide comprehensive documentation about the exact behavior of the data
|
||||
/// pointer. If a dynamic style heap is used the base pointer of the heap can change. Since
|
||||
/// growing, we cannot guarantee the lifetime of the returned slice reference.
|
||||
unsafe fn memory_as_slice(memory: &wasmer::Memory) -> &[u8] {
|
||||
let ptr = memory.data_ptr() as *const _;
|
||||
let len: usize =
|
||||
memory.data_size().try_into().expect("data size should fit into usize");
|
||||
|
||||
if len == 0 {
|
||||
&[]
|
||||
} else {
|
||||
core::slice::from_raw_parts(ptr, len)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns linear memory of the wasm instance as a slice.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// See `[memory_as_slice]`. In addition to those requirements, since a mutable reference is
|
||||
/// returned it must be ensured that only one mutable and no shared references to memory
|
||||
/// exists at the same time.
|
||||
unsafe fn memory_as_slice_mut(memory: &wasmer::Memory) -> &mut [u8] {
|
||||
let ptr = memory.data_ptr();
|
||||
let len: usize =
|
||||
memory.data_size().try_into().expect("data size should fit into usize");
|
||||
|
||||
if len == 0 {
|
||||
&mut []
|
||||
} else {
|
||||
core::slice::from_raw_parts_mut(ptr, len)
|
||||
}
|
||||
}
|
||||
|
||||
/// Clone the underlying memory object
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The sole purpose of `MemoryRef` is to protect the memory from uncontrolled
|
||||
/// access. By returning the memory object "as is" we bypass all of the checks.
|
||||
///
|
||||
/// Intended to use only during module initialization.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Will panic if `MemoryRef` is currently in use.
|
||||
pub unsafe fn clone_inner(&mut self) -> wasmer::Memory {
|
||||
// We take exclusive lock to ensure that we're the only one here
|
||||
self.buffer.borrow_mut().clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl super::MemoryTransfer for MemoryWrapper {
|
||||
fn read(&self, source_addr: Pointer<u8>, size: usize) -> Result<Vec<u8>> {
|
||||
let memory = self.buffer.borrow();
|
||||
|
||||
let data_size = memory.data_size().try_into().expect("data size does not fit");
|
||||
|
||||
let range = checked_range(source_addr.into(), size, data_size)
|
||||
.ok_or_else(|| Error::Other("memory read is out of bounds".into()))?;
|
||||
|
||||
let mut buffer = vec![0; range.len()];
|
||||
self.read_into(source_addr, &mut buffer)?;
|
||||
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
fn read_into(&self, source_addr: Pointer<u8>, destination: &mut [u8]) -> Result<()> {
|
||||
unsafe {
|
||||
let memory = self.buffer.borrow();
|
||||
|
||||
// This should be safe since we don't grow up memory while caching this reference
|
||||
// and we give up the reference before returning from this function.
|
||||
let source = Self::memory_as_slice(&memory);
|
||||
|
||||
let range = checked_range(source_addr.into(), destination.len(), source.len())
|
||||
.ok_or_else(|| Error::Other("memory read is out of bounds".into()))?;
|
||||
|
||||
destination.copy_from_slice(&source[range]);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn write_from(&self, dest_addr: Pointer<u8>, source: &[u8]) -> Result<()> {
|
||||
unsafe {
|
||||
let memory = self.buffer.borrow_mut();
|
||||
|
||||
// This should be safe since we don't grow up memory while caching this reference
|
||||
// and we give up the reference before returning from this function.
|
||||
let destination = Self::memory_as_slice_mut(&memory);
|
||||
|
||||
let range = checked_range(dest_addr.into(), source.len(), destination.len())
|
||||
.ok_or_else(|| Error::Other("memory write is out of bounds".into()))?;
|
||||
|
||||
&mut destination[range].copy_from_slice(source);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user