wit_bindgen_host_wasmer_rust::export!({
custom_error: true,
paths: ["contract_system_api.wit"],
});
wit_bindgen_host_wasmer_rust::export!({
custom_error: true,
paths: ["service_system_api.wit"],
});
wit_bindgen_host_wasmer_rust::export!({
custom_error: true,
paths: ["view_system_api.wit"],
});
wit_bindgen_host_wasmer_rust::import!("contract.wit");
wit_bindgen_host_wasmer_rust::import!("service.wit");
#[path = "conversions_from_wit.rs"]
mod conversions_from_wit;
#[path = "conversions_to_wit.rs"]
mod conversions_to_wit;
use super::{
common::{self, ApplicationRuntimeContext, WasmRuntimeContext},
module_cache::ModuleCache,
runtime_actor::{BaseRequest, ContractRequest, SendRequestExt, ServiceRequest},
ApplicationCallResult, SessionCallResult, WasmContract, WasmExecutionError, WasmService,
};
use crate::{
Bytecode, CalleeContext, ExecutionError, MessageContext, OperationContext, QueryContext,
RawExecutionResult,
};
use bytes::Bytes;
use futures::channel::mpsc;
use linera_base::identifiers::SessionId;
use linera_views::batch::Batch;
use once_cell::sync::Lazy;
use std::{marker::PhantomData, sync::Arc};
use tokio::sync::Mutex;
use wasmer::{
imports, wasmparser::Operator, CompilerConfig, Engine, EngineBuilder, Instance, Module,
RuntimeError, Singlepass, Store,
};
use wasmer_middlewares::metering::{self, Metering, MeteringPoints};
use wit_bindgen_host_wasmer_rust::Le;
static SERVICE_ENGINE: Lazy<Engine> = Lazy::new(|| {
let compiler_config = Singlepass::default();
EngineBuilder::new(compiler_config).into()
});
static CONTRACT_CACHE: Lazy<Mutex<ModuleCache<CachedContractModule>>> = Lazy::new(Mutex::default);
static SERVICE_CACHE: Lazy<Mutex<ModuleCache<Module>>> = Lazy::new(Mutex::default);
pub struct Contract {
contract: contract::Contract,
}
impl ApplicationRuntimeContext for Contract {
type Store = Store;
type Error = RuntimeError;
type Extra = WasmerContractExtra;
fn configure_initial_fuel(context: &mut WasmRuntimeContext<Self>) {
let remaining_points = context
.extra
.runtime
.send_request(|response_sender| ContractRequest::RemainingFuel { response_sender })
.and_then(|response_receiver| {
response_receiver
.recv()
.map_err(|oneshot::RecvError| WasmExecutionError::MissingRuntimeResponse)
})
.unwrap_or_else(|_| {
tracing::debug!("Failed to read initial fuel for transaction");
0
});
metering::set_remaining_points(
&mut context.store,
&context.extra.instance,
remaining_points,
);
}
fn persist_remaining_fuel(context: &mut WasmRuntimeContext<Self>) -> Result<(), ()> {
let remaining_fuel =
match metering::get_remaining_points(&mut context.store, &context.extra.instance) {
MeteringPoints::Exhausted => 0,
MeteringPoints::Remaining(fuel) => fuel,
};
context
.extra
.runtime
.send_sync_request(|response_sender| ContractRequest::SetRemainingFuel {
remaining_fuel,
response_sender,
})
.map_err(|_| ())
}
}
pub struct Service {
service: service::Service,
}
impl ApplicationRuntimeContext for Service {
type Store = Store;
type Error = RuntimeError;
type Extra = ();
fn configure_initial_fuel(_context: &mut WasmRuntimeContext<Self>) {}
fn persist_remaining_fuel(_context: &mut WasmRuntimeContext<Self>) -> Result<(), ()> {
Ok(())
}
}
impl WasmContract {
pub async fn new_with_wasmer(contract_bytecode: Bytecode) -> Result<Self, WasmExecutionError> {
let mut contract_cache = CONTRACT_CACHE.lock().await;
let (engine, module) = contract_cache
.get_or_insert_with(contract_bytecode, CachedContractModule::new)
.map_err(WasmExecutionError::LoadContractModule)?
.create_execution_instance()
.map_err(WasmExecutionError::LoadContractModule)?;
Ok(WasmContract::Wasmer { engine, module })
}
pub fn prepare_contract_runtime_with_wasmer(
contract_engine: &Engine,
contract_module: &Module,
runtime: mpsc::UnboundedSender<ContractRequest>,
) -> Result<WasmRuntimeContext<Contract>, WasmExecutionError> {
let mut store = Store::new(contract_engine);
let mut imports = imports! {};
let contract_system_api = ContractSystemApi::new(runtime.clone());
let view_system_api = ContractViewSystemApi::new(runtime.clone());
let system_api_setup =
contract_system_api::add_to_imports(&mut store, &mut imports, contract_system_api);
let views_api_setup =
view_system_api::add_to_imports(&mut store, &mut imports, view_system_api);
let (contract, instance) =
contract::Contract::instantiate(&mut store, contract_module, &mut imports)
.map_err(WasmExecutionError::LoadContractModule)?;
let application = Contract { contract };
system_api_setup(&instance, &store).map_err(WasmExecutionError::LoadContractModule)?;
views_api_setup(&instance, &store).map_err(WasmExecutionError::LoadContractModule)?;
Ok(WasmRuntimeContext {
application,
store,
extra: WasmerContractExtra { runtime, instance },
})
}
fn operation_cost(operator: &Operator) -> u64 {
match operator {
Operator::Nop
| Operator::Drop
| Operator::Block { .. }
| Operator::Loop { .. }
| Operator::Unreachable
| Operator::Else
| Operator::End => 0,
_ => 1,
}
}
}
impl WasmService {
pub async fn new_with_wasmer(service_bytecode: Bytecode) -> Result<Self, WasmExecutionError> {
let mut service_cache = SERVICE_CACHE.lock().await;
let module = service_cache
.get_or_insert_with(service_bytecode, |bytecode| {
Module::new(&*SERVICE_ENGINE, bytecode)
.map_err(wit_bindgen_host_wasmer_rust::anyhow::Error::from)
})
.map_err(WasmExecutionError::LoadServiceModule)?;
Ok(WasmService::Wasmer { module })
}
pub fn prepare_service_runtime_with_wasmer(
service_module: &Module,
runtime: mpsc::UnboundedSender<ServiceRequest>,
) -> Result<WasmRuntimeContext<Service>, WasmExecutionError> {
let mut store = Store::new(&*SERVICE_ENGINE);
let mut imports = imports! {};
let service_system_api = ServiceSystemApi::new(runtime.clone());
let view_system_api = ServiceViewSystemApi::new(runtime);
let system_api_setup =
service_system_api::add_to_imports(&mut store, &mut imports, service_system_api);
let views_api_setup =
view_system_api::add_to_imports(&mut store, &mut imports, view_system_api);
let (service, instance) =
service::Service::instantiate(&mut store, service_module, &mut imports)
.map_err(WasmExecutionError::LoadServiceModule)?;
let application = Service { service };
system_api_setup(&instance, &store).map_err(WasmExecutionError::LoadServiceModule)?;
views_api_setup(&instance, &store).map_err(WasmExecutionError::LoadServiceModule)?;
Ok(WasmRuntimeContext {
application,
store,
extra: (),
})
}
}
impl common::Contract for Contract {
fn initialize(
&self,
store: &mut Store,
context: OperationContext,
argument: Vec<u8>,
) -> Result<Result<RawExecutionResult<Vec<u8>>, String>, RuntimeError> {
contract::Contract::initialize(&self.contract, store, context.into(), &argument)
.map(|inner| inner.map(RawExecutionResult::from))
}
fn execute_operation(
&self,
store: &mut Store,
context: OperationContext,
operation: Vec<u8>,
) -> Result<Result<RawExecutionResult<Vec<u8>>, String>, RuntimeError> {
contract::Contract::execute_operation(&self.contract, store, context.into(), &operation)
.map(|inner| inner.map(RawExecutionResult::from))
}
fn execute_message(
&self,
store: &mut Store,
context: MessageContext,
message: Vec<u8>,
) -> Result<Result<RawExecutionResult<Vec<u8>>, String>, RuntimeError> {
contract::Contract::execute_message(&self.contract, store, context.into(), &message)
.map(|inner| inner.map(RawExecutionResult::from))
}
fn handle_application_call(
&self,
store: &mut Store,
context: CalleeContext,
argument: Vec<u8>,
forwarded_sessions: Vec<SessionId>,
) -> Result<Result<ApplicationCallResult, String>, RuntimeError> {
let forwarded_sessions = forwarded_sessions
.into_iter()
.map(contract::SessionId::from)
.collect::<Vec<_>>();
contract::Contract::handle_application_call(
&self.contract,
store,
context.into(),
&argument,
&forwarded_sessions,
)
.map(|inner| inner.map(ApplicationCallResult::from))
}
fn handle_session_call(
&self,
store: &mut Store,
context: CalleeContext,
session: Vec<u8>,
argument: Vec<u8>,
forwarded_sessions: Vec<SessionId>,
) -> Result<Result<(SessionCallResult, Vec<u8>), String>, RuntimeError> {
let forwarded_sessions = forwarded_sessions
.into_iter()
.map(contract::SessionId::from)
.collect::<Vec<_>>();
contract::Contract::handle_session_call(
&self.contract,
store,
context.into(),
&session,
&argument,
&forwarded_sessions,
)
.map(|inner| inner.map(<(SessionCallResult, Vec<u8>)>::from))
}
}
impl common::Service for Service {
fn handle_query(
&self,
store: &mut Store,
context: QueryContext,
argument: Vec<u8>,
) -> Result<Result<Vec<u8>, String>, RuntimeError> {
service::Service::handle_query(&self.service, store, context.into(), &argument)
}
}
pub struct ContractSystemApi {
runtime: mpsc::UnboundedSender<ContractRequest>,
}
impl ContractSystemApi {
pub fn new(runtime: mpsc::UnboundedSender<ContractRequest>) -> Self {
ContractSystemApi { runtime }
}
}
impl_contract_system_api!(ContractSystemApi, wasmer::RuntimeError);
pub struct ServiceSystemApi {
runtime: mpsc::UnboundedSender<ServiceRequest>,
}
impl ServiceSystemApi {
pub fn new(runtime: mpsc::UnboundedSender<ServiceRequest>) -> Self {
ServiceSystemApi { runtime }
}
}
impl_service_system_api!(ServiceSystemApi, wasmer::RuntimeError);
pub struct ContractViewSystemApi {
runtime: mpsc::UnboundedSender<ContractRequest>,
}
impl ContractViewSystemApi {
pub fn new(runtime: mpsc::UnboundedSender<ContractRequest>) -> Self {
ContractViewSystemApi { runtime }
}
}
pub struct ServiceViewSystemApi {
runtime: mpsc::UnboundedSender<ServiceRequest>,
}
impl ServiceViewSystemApi {
pub fn new(runtime: mpsc::UnboundedSender<ServiceRequest>) -> Self {
ServiceViewSystemApi { runtime }
}
}
impl_view_system_api_for_contract!(ContractViewSystemApi, wasmer::RuntimeError);
impl_view_system_api_for_service!(ServiceViewSystemApi, wasmer::RuntimeError);
pub struct WasmerContractExtra {
runtime: mpsc::UnboundedSender<ContractRequest>,
instance: Instance,
}
pub struct RuntimeGuard<'runtime, S> {
runtime: Arc<Mutex<Option<S>>>,
_lifetime: PhantomData<&'runtime ()>,
}
impl<S> Drop for RuntimeGuard<'_, S> {
fn drop(&mut self) {
self.runtime
.try_lock()
.expect("Guard dropped while runtime is still in use")
.take();
}
}
impl From<ExecutionError> for wasmer::RuntimeError {
fn from(error: ExecutionError) -> Self {
wasmer::RuntimeError::user(Box::new(error))
}
}
impl From<wasmer::RuntimeError> for ExecutionError {
fn from(error: wasmer::RuntimeError) -> Self {
error
.downcast::<ExecutionError>()
.unwrap_or_else(|unknown_error| {
ExecutionError::WasmError(WasmExecutionError::ExecuteModuleInWasmer(unknown_error))
})
}
}
pub struct CachedContractModule {
compiled_bytecode: Bytes,
}
impl CachedContractModule {
pub fn new(contract_bytecode: Bytecode) -> Result<Self, anyhow::Error> {
let module = Module::new(&Self::create_compilation_engine(), contract_bytecode)?;
let compiled_bytecode = module.serialize()?;
Ok(CachedContractModule { compiled_bytecode })
}
fn create_compilation_engine() -> Engine {
let metering = Arc::new(Metering::new(0, WasmContract::operation_cost));
let mut compiler_config = Singlepass::default();
compiler_config.push_middleware(metering);
compiler_config.canonicalize_nans(true);
EngineBuilder::new(compiler_config).into()
}
pub fn create_execution_instance(&self) -> Result<(Engine, Module), anyhow::Error> {
let engine = Engine::headless();
let store = Store::new(&engine);
let module = unsafe { Module::deserialize(&store, &*self.compiled_bytecode) }?;
Ok((engine, module))
}
}