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