1#![cfg(with_wasm_runtime)]
12
13mod entrypoints;
14mod module_cache;
15#[macro_use]
16mod runtime_api;
17#[cfg(with_wasmer)]
18mod wasmer;
19#[cfg(with_wasmtime)]
20mod wasmtime;
21
22use linera_base::data_types::Bytecode;
23#[cfg(with_metrics)]
24use linera_base::prometheus_util::MeasureLatency as _;
25use thiserror::Error;
26use wasm_instrument::{gas_metering, parity_wasm};
27#[cfg(with_wasmer)]
28use wasmer::{WasmerContractInstance, WasmerServiceInstance};
29#[cfg(with_wasmtime)]
30use wasmtime::{WasmtimeContractInstance, WasmtimeServiceInstance};
31
32pub use self::{
33 entrypoints::{ContractEntrypoints, ServiceEntrypoints},
34 runtime_api::{BaseRuntimeApi, ContractRuntimeApi, RuntimeApiData, ServiceRuntimeApi},
35};
36use crate::{
37 ContractSyncRuntimeHandle, ExecutionError, ServiceSyncRuntimeHandle, UserContractInstance,
38 UserContractModule, UserServiceInstance, UserServiceModule, WasmRuntime,
39};
40
41#[cfg(with_metrics)]
42mod metrics {
43 use std::sync::LazyLock;
44
45 use linera_base::prometheus_util::{exponential_bucket_latencies, register_histogram_vec};
46 use prometheus::HistogramVec;
47
48 pub static CONTRACT_INSTANTIATION_LATENCY: LazyLock<HistogramVec> = LazyLock::new(|| {
49 register_histogram_vec(
50 "wasm_contract_instantiation_latency",
51 "Wasm contract instantiation latency",
52 &[],
53 exponential_bucket_latencies(1.0),
54 )
55 });
56
57 pub static SERVICE_INSTANTIATION_LATENCY: LazyLock<HistogramVec> = LazyLock::new(|| {
58 register_histogram_vec(
59 "wasm_service_instantiation_latency",
60 "Wasm service instantiation latency",
61 &[],
62 exponential_bucket_latencies(1.0),
63 )
64 });
65}
66
67#[derive(Clone)]
69pub enum WasmContractModule {
70 #[cfg(with_wasmer)]
71 Wasmer {
72 engine: ::wasmer::Engine,
73 module: ::wasmer::Module,
74 },
75 #[cfg(with_wasmtime)]
76 Wasmtime { module: ::wasmtime::Module },
77}
78
79impl WasmContractModule {
80 pub async fn new(
82 contract_bytecode: Bytecode,
83 runtime: WasmRuntime,
84 ) -> Result<Self, WasmExecutionError> {
85 let contract_bytecode = add_metering(contract_bytecode)?;
86 match runtime {
87 #[cfg(with_wasmer)]
88 WasmRuntime::Wasmer => Self::from_wasmer(contract_bytecode).await,
89 #[cfg(with_wasmtime)]
90 WasmRuntime::Wasmtime => Self::from_wasmtime(contract_bytecode).await,
91 }
92 }
93
94 #[cfg(with_fs)]
96 pub async fn from_file(
97 contract_bytecode_file: impl AsRef<std::path::Path>,
98 runtime: WasmRuntime,
99 ) -> Result<Self, WasmExecutionError> {
100 Self::new(
101 Bytecode::load_from_file(contract_bytecode_file)
102 .await
103 .map_err(anyhow::Error::from)
104 .map_err(WasmExecutionError::LoadContractModule)?,
105 runtime,
106 )
107 .await
108 }
109}
110
111impl UserContractModule for WasmContractModule {
112 fn instantiate(
113 &self,
114 runtime: ContractSyncRuntimeHandle,
115 ) -> Result<UserContractInstance, ExecutionError> {
116 #[cfg(with_metrics)]
117 let _instantiation_latency = metrics::CONTRACT_INSTANTIATION_LATENCY.measure_latency();
118
119 let instance: UserContractInstance = match self {
120 #[cfg(with_wasmtime)]
121 WasmContractModule::Wasmtime { module } => {
122 Box::new(WasmtimeContractInstance::prepare(module, runtime)?)
123 }
124 #[cfg(with_wasmer)]
125 WasmContractModule::Wasmer { engine, module } => Box::new(
126 WasmerContractInstance::prepare(engine.clone(), module, runtime)?,
127 ),
128 };
129
130 Ok(instance)
131 }
132}
133
134#[derive(Clone)]
136pub enum WasmServiceModule {
137 #[cfg(with_wasmer)]
138 Wasmer { module: ::wasmer::Module },
139 #[cfg(with_wasmtime)]
140 Wasmtime { module: ::wasmtime::Module },
141}
142
143impl WasmServiceModule {
144 pub async fn new(
146 service_bytecode: Bytecode,
147 runtime: WasmRuntime,
148 ) -> Result<Self, WasmExecutionError> {
149 match runtime {
150 #[cfg(with_wasmer)]
151 WasmRuntime::Wasmer => Self::from_wasmer(service_bytecode).await,
152 #[cfg(with_wasmtime)]
153 WasmRuntime::Wasmtime => Self::from_wasmtime(service_bytecode).await,
154 }
155 }
156
157 #[cfg(with_fs)]
159 pub async fn from_file(
160 service_bytecode_file: impl AsRef<std::path::Path>,
161 runtime: WasmRuntime,
162 ) -> Result<Self, WasmExecutionError> {
163 Self::new(
164 Bytecode::load_from_file(service_bytecode_file)
165 .await
166 .map_err(anyhow::Error::from)
167 .map_err(WasmExecutionError::LoadServiceModule)?,
168 runtime,
169 )
170 .await
171 }
172}
173
174impl UserServiceModule for WasmServiceModule {
175 fn instantiate(
176 &self,
177 runtime: ServiceSyncRuntimeHandle,
178 ) -> Result<UserServiceInstance, ExecutionError> {
179 #[cfg(with_metrics)]
180 let _instantiation_latency = metrics::SERVICE_INSTANTIATION_LATENCY.measure_latency();
181
182 let instance: UserServiceInstance = match self {
183 #[cfg(with_wasmtime)]
184 WasmServiceModule::Wasmtime { module } => {
185 Box::new(WasmtimeServiceInstance::prepare(module, runtime)?)
186 }
187 #[cfg(with_wasmer)]
188 WasmServiceModule::Wasmer { module } => {
189 Box::new(WasmerServiceInstance::prepare(module, runtime)?)
190 }
191 };
192
193 Ok(instance)
194 }
195}
196
197pub fn add_metering(bytecode: Bytecode) -> Result<Bytecode, WasmExecutionError> {
199 struct WasmtimeRules;
200
201 impl gas_metering::Rules for WasmtimeRules {
202 fn instruction_cost(
207 &self,
208 instruction: &parity_wasm::elements::Instruction,
209 ) -> Option<u32> {
210 use parity_wasm::elements::Instruction::*;
211
212 Some(match instruction {
213 Nop | Drop | Block(_) | Loop(_) | Unreachable | Else | End => 0,
214 _ => 1,
215 })
216 }
217
218 fn memory_grow_cost(&self) -> gas_metering::MemoryGrowCost {
219 gas_metering::MemoryGrowCost::Free
220 }
221
222 fn call_per_local_cost(&self) -> u32 {
223 0
224 }
225 }
226
227 let instrumented_module = gas_metering::inject(
228 parity_wasm::deserialize_buffer(&bytecode.bytes)?,
229 gas_metering::host_function::Injector::new(
230 "linera:app/contract-runtime-api",
231 "consume-fuel",
232 ),
233 &WasmtimeRules,
234 )
235 .map_err(|_| WasmExecutionError::InstrumentModule)?;
236
237 Ok(Bytecode::new(instrumented_module.into_bytes()?))
238}
239
240#[cfg(web)]
241const _: () = {
242 use js_sys::wasm_bindgen::JsValue;
243
244 impl TryFrom<JsValue> for WasmServiceModule {
245 type Error = JsValue;
246
247 fn try_from(value: JsValue) -> Result<Self, JsValue> {
248 cfg_if::cfg_if! {
251 if #[cfg(with_wasmer)] {
252 Ok(Self::Wasmer {
253 module: value.try_into()?,
254 })
255 } else {
256 Err(value)
257 }
258 }
259 }
260 }
261
262 impl From<WasmServiceModule> for JsValue {
263 fn from(module: WasmServiceModule) -> JsValue {
264 match module {
265 #[cfg(with_wasmer)]
266 WasmServiceModule::Wasmer { module } => ::wasmer::Module::clone(&module).into(),
267 }
268 }
269 }
270
271 impl TryFrom<JsValue> for WasmContractModule {
272 type Error = JsValue;
273
274 fn try_from(value: JsValue) -> Result<Self, JsValue> {
275 cfg_if::cfg_if! {
277 if #[cfg(with_wasmer)] {
278 Ok(Self::Wasmer {
279 module: value.try_into()?,
280 engine: Default::default(),
281 })
282 } else {
283 Err(value)
284 }
285 }
286 }
287 }
288
289 impl From<WasmContractModule> for JsValue {
290 fn from(module: WasmContractModule) -> JsValue {
291 match module {
292 #[cfg(with_wasmer)]
293 WasmContractModule::Wasmer { module, engine: _ } => {
294 ::wasmer::Module::clone(&module).into()
295 }
296 }
297 }
298 }
299};
300
301#[derive(Debug, Error)]
303pub enum WasmExecutionError {
304 #[error("Failed to load contract Wasm module: {_0}")]
305 LoadContractModule(#[source] anyhow::Error),
306 #[error("Failed to load service Wasm module: {_0}")]
307 LoadServiceModule(#[source] anyhow::Error),
308 #[error("Failed to instrument Wasm module to add fuel metering")]
309 InstrumentModule,
310 #[error("Invalid Wasm module: {0}")]
311 InvalidBytecode(#[from] wasm_instrument::parity_wasm::SerializationError),
312 #[cfg(with_wasmer)]
313 #[error("Failed to instantiate Wasm module: {_0}")]
314 InstantiateModuleWithWasmer(#[from] Box<::wasmer::InstantiationError>),
315 #[cfg(with_wasmtime)]
316 #[error("Failed to create and configure Wasmtime runtime: {_0}")]
317 CreateWasmtimeEngine(#[source] anyhow::Error),
318 #[cfg(with_wasmer)]
319 #[error(
320 "Failed to execute Wasm module in Wasmer. This may be caused by panics or insufficient fuel. {0}"
321 )]
322 ExecuteModuleInWasmer(#[from] ::wasmer::RuntimeError),
323 #[cfg(with_wasmtime)]
324 #[error("Failed to execute Wasm module in Wasmtime: {0}")]
325 ExecuteModuleInWasmtime(#[from] ::wasmtime::Trap),
326 #[error("Failed to execute Wasm module: {0}")]
327 ExecuteModule(#[from] linera_witty::RuntimeError),
328 #[error("Attempt to wait for an unknown promise")]
329 UnknownPromise,
330 #[error("Attempt to call incorrect `wait` function for a promise")]
331 IncorrectPromise,
332}
333
334#[cfg(with_wasmer)]
335impl From<::wasmer::InstantiationError> for WasmExecutionError {
336 fn from(instantiation_error: ::wasmer::InstantiationError) -> Self {
337 WasmExecutionError::InstantiateModuleWithWasmer(Box::new(instantiation_error))
338 }
339}
340
341#[cfg(with_testing)]
343pub mod test {
344 use std::sync::LazyLock;
345
346 #[cfg(with_fs)]
347 use super::{WasmContractModule, WasmRuntime, WasmServiceModule};
348
349 fn build_applications() -> Result<(), std::io::Error> {
350 tracing::info!("Building example applications with cargo");
351 let output = std::process::Command::new("cargo")
352 .current_dir("../examples")
353 .args(["build", "--release", "--target", "wasm32-unknown-unknown"])
354 .output()?;
355 if !output.status.success() {
356 panic!(
357 "Failed to build example applications.\n\n\
358 stdout:\n-------\n{}\n\n\
359 stderr:\n-------\n{}",
360 String::from_utf8_lossy(&output.stdout),
361 String::from_utf8_lossy(&output.stderr),
362 );
363 }
364 Ok(())
365 }
366
367 pub fn get_example_bytecode_paths(name: &str) -> Result<(String, String), std::io::Error> {
368 let name = name.replace('-', "_");
369 static INSTANCE: LazyLock<()> = LazyLock::new(|| build_applications().unwrap());
370 LazyLock::force(&INSTANCE);
371 Ok((
372 format!("../examples/target/wasm32-unknown-unknown/release/{name}_contract.wasm"),
373 format!("../examples/target/wasm32-unknown-unknown/release/{name}_service.wasm"),
374 ))
375 }
376
377 #[cfg(with_fs)]
378 pub async fn build_example_application(
379 name: &str,
380 wasm_runtime: impl Into<Option<WasmRuntime>>,
381 ) -> Result<(WasmContractModule, WasmServiceModule), anyhow::Error> {
382 let (contract_path, service_path) = get_example_bytecode_paths(name)?;
383 let wasm_runtime = wasm_runtime.into().unwrap_or_default();
384 let contract = WasmContractModule::from_file(&contract_path, wasm_runtime).await?;
385 let service = WasmServiceModule::from_file(&service_path, wasm_runtime).await?;
386 Ok((contract, service))
387 }
388}