use std::any::Any;
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::fmt::{self, Debug, Formatter};
use std::path::Path;
use std::sync::Arc;
use std::thread;
use dusk_wasmtime::{
Config, Engine, ModuleVersionStrategy, OperatorCost, OptLevel, Strategy,
WasmBacktraceDetails,
};
use piecrust_uplink::ContractId;
use tempfile::tempdir;
use crate::Error::{self, PersistenceError};
use crate::config::BYTE_STORE_COST;
use crate::session::{Session, SessionData};
use crate::store::ContractStore;
fn config() -> Config {
let mut config = Config::new();
config.wasm_backtrace(false);
config.wasm_backtrace_details(WasmBacktraceDetails::Disable);
config.native_unwind_info(false);
config.max_wasm_stack(0x80000);
config.consume_fuel(true);
config.strategy(Strategy::Cranelift);
config.cranelift_opt_level(OptLevel::SpeedAndSize);
config.cranelift_nan_canonicalization(true);
config.static_memory_forced(true);
config.static_memory_guard_size(0);
config.dynamic_memory_guard_size(0);
config.guard_before_linear_memory(false);
config.memory_init_cow(false);
config
.module_version(ModuleVersionStrategy::Custom(String::from("piecrust")))
.expect("Module version should be valid");
config.generate_address_map(false);
config.macos_use_mach_ports(false);
config.wasm_memory64(true);
const BYTE4_STORE_COST: i64 = 4 * BYTE_STORE_COST;
const BYTE8_STORE_COST: i64 = 8 * BYTE_STORE_COST;
const BYTE16_STORE_COST: i64 = 16 * BYTE_STORE_COST;
config.operator_cost(OperatorCost {
I32Store: BYTE4_STORE_COST,
F32Store: BYTE4_STORE_COST,
I32Store8: BYTE4_STORE_COST,
I32Store16: BYTE4_STORE_COST,
I32AtomicStore: BYTE4_STORE_COST,
I32AtomicStore8: BYTE4_STORE_COST,
I32AtomicStore16: BYTE4_STORE_COST,
I64Store: BYTE8_STORE_COST,
F64Store: BYTE8_STORE_COST,
I64Store8: BYTE8_STORE_COST,
I64Store16: BYTE8_STORE_COST,
I64Store32: BYTE8_STORE_COST,
I64AtomicStore: BYTE8_STORE_COST,
I64AtomicStore8: BYTE8_STORE_COST,
I64AtomicStore16: BYTE8_STORE_COST,
I64AtomicStore32: BYTE8_STORE_COST,
V128Store: BYTE16_STORE_COST,
V128Store8Lane: BYTE16_STORE_COST,
V128Store16Lane: BYTE16_STORE_COST,
V128Store32Lane: BYTE16_STORE_COST,
V128Store64Lane: BYTE16_STORE_COST,
..Default::default()
});
config
}
pub struct VM {
engine: Engine,
host_queries: HostQueries,
store: ContractStore,
}
impl Debug for VM {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("VM")
.field("config", self.engine.config())
.field("host_queries", &self.host_queries)
.field("store", &self.store)
.finish()
}
}
impl VM {
pub fn new<P: AsRef<Path>>(root_dir: P) -> Result<Self, Error> {
tracing::trace!("vm::new");
let config = config();
let engine = Engine::new(&config).expect(
"Configuration should be valid since its set at compile time",
);
tracing::trace!("before ContractStore::new");
let mut store = ContractStore::new(engine.clone(), root_dir)
.map_err(|err| PersistenceError(Arc::new(err)))?;
tracing::trace!("before ContractStore::finish_new");
store
.finish_new()
.map_err(|err| PersistenceError(Arc::new(err)))?;
tracing::trace!("after ContractStore::finish_new");
Ok(Self {
engine,
host_queries: HostQueries::default(),
store,
})
}
pub fn ephemeral() -> Result<Self, Error> {
let tmp = tempdir().map_err(|err| PersistenceError(Arc::new(err)))?;
let tmp = tmp.path().to_path_buf();
let config = config();
let engine = Engine::new(&config).expect(
"Configuration should be valid since its set at compile time",
);
let mut store = ContractStore::new(engine.clone(), tmp)
.map_err(|err| PersistenceError(Arc::new(err)))?;
store
.finish_new()
.map_err(|err| PersistenceError(Arc::new(err)))?;
Ok(Self {
engine,
host_queries: HostQueries::default(),
store,
})
}
pub fn register_host_query<Q, S>(&mut self, name: S, query: Q)
where
Q: 'static + HostQuery,
S: Into<Cow<'static, str>>,
{
self.host_queries.insert(name, query);
}
pub fn host_queries(&self) -> &HostQueries {
&self.host_queries
}
pub fn session(
&self,
data: impl Into<SessionData>,
) -> Result<Session, Error> {
let data = data.into();
let contract_session = match data.base {
Some(base) => self
.store
.session(base.into())
.map_err(|err| PersistenceError(Arc::new(err)))?,
_ => self.store.genesis_session(),
};
let mut host_queries = self.host_queries.clone();
for excluded in data.excluded_host_queries() {
host_queries.remove(excluded);
}
Ok(Session::new(
self.engine.clone(),
contract_session,
host_queries,
data,
))
}
pub fn commits(&self) -> Vec<[u8; 32]> {
self.store.commits().into_iter().map(Into::into).collect()
}
pub fn delete_commit(&self, root: [u8; 32]) -> Result<(), Error> {
self.store
.delete_commit(root.into())
.map_err(|err| PersistenceError(Arc::new(err)))
}
pub fn finalize_commit(&self, root: [u8; 32]) -> Result<(), Error> {
self.store
.finalize_commit(root.into())
.map_err(|err| PersistenceError(Arc::new(err)))
}
pub fn root_dir(&self) -> &Path {
self.store.root_dir()
}
pub fn sync_thread(&self) -> &thread::Thread {
self.store.sync_loop()
}
pub fn remove_module(&self, contract_id: ContractId) -> Result<(), Error> {
self.store
.remove_module(contract_id)
.map_err(|err| PersistenceError(Arc::new(err)))
}
pub fn recompile_module(
&self,
contract_id: ContractId,
) -> Result<(), Error> {
self.store
.recompile_module(contract_id)
.map_err(|err| PersistenceError(Arc::new(err)))
}
}
#[derive(Default, Clone)]
pub struct HostQueries {
map: BTreeMap<Cow<'static, str>, Arc<dyn HostQuery>>,
}
impl Debug for HostQueries {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_list().entries(self.map.keys()).finish()
}
}
impl HostQueries {
pub fn insert<Q, S>(&mut self, name: S, query: Q)
where
Q: 'static + HostQuery,
S: Into<Cow<'static, str>>,
{
self.map.insert(name.into(), Arc::new(query));
}
pub fn get(&self, name: &str) -> Option<&dyn HostQuery> {
self.map.get(name).map(|q| q.as_ref())
}
pub fn get_arc(&self, name: &str) -> Option<Arc<dyn HostQuery>> {
self.map.get(name).cloned()
}
pub fn remove(&mut self, name: &str) {
self.map.remove(name);
}
}
pub trait HostQuery: Send + Sync {
fn deserialize_and_price(
&self,
arg_buf: &[u8],
arg: &mut Box<dyn Any>,
) -> u64;
fn execute(&self, arg: &Box<dyn Any>, arg_buf: &mut [u8]) -> u32;
}
impl<F> HostQuery for F
where
F: Send + Sync + Fn(&mut [u8], u32) -> u32,
{
fn deserialize_and_price(
&self,
arg_buf: &[u8],
arg: &mut Box<dyn Any>,
) -> u64 {
let len = Box::new(arg_buf.len() as u32);
*arg = len;
0
}
fn execute(&self, arg: &Box<dyn Any>, arg_buf: &mut [u8]) -> u32 {
let arg_len = *arg.downcast_ref::<u32>().unwrap();
self(arg_buf, arg_len)
}
}