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 .await
106 .map_err(anyhow::Error::from)
107 .map_err(WasmExecutionError::LoadContractModule)?,
108 runtime,
109 )
110 .await
111 }
112}
113
114impl UserContractModule for WasmContractModule {
115 fn instantiate(
116 &self,
117 runtime: ContractSyncRuntimeHandle,
118 ) -> Result<UserContractInstance, ExecutionError> {
119 #[cfg(with_metrics)]
120 let _instantiation_latency = metrics::CONTRACT_INSTANTIATION_LATENCY.measure_latency();
121
122 let instance: UserContractInstance = match self {
123 #[cfg(with_wasmtime)]
124 WasmContractModule::Wasmtime { module } => {
125 Box::new(WasmtimeContractInstance::prepare(module, runtime)?)
126 }
127 #[cfg(with_wasmer)]
128 WasmContractModule::Wasmer { engine, module } => Box::new(
129 WasmerContractInstance::prepare(engine.clone(), module, runtime)?,
130 ),
131 };
132
133 Ok(instance)
134 }
135}
136
137#[derive(Clone)]
139pub enum WasmServiceModule {
140 #[cfg(with_wasmer)]
141 Wasmer { module: ::wasmer::Module },
142 #[cfg(with_wasmtime)]
143 Wasmtime { module: ::wasmtime::Module },
144}
145
146impl WasmServiceModule {
147 pub async fn new(
149 service_bytecode: Bytecode,
150 runtime: WasmRuntime,
151 ) -> Result<Self, WasmExecutionError> {
152 match runtime {
153 #[cfg(with_wasmer)]
154 WasmRuntime::Wasmer => Self::from_wasmer(service_bytecode).await,
155 #[cfg(with_wasmtime)]
156 WasmRuntime::Wasmtime => Self::from_wasmtime(service_bytecode).await,
157 }
158 }
159
160 #[cfg(with_fs)]
162 pub async fn from_file(
163 service_bytecode_file: impl AsRef<Path>,
164 runtime: WasmRuntime,
165 ) -> Result<Self, WasmExecutionError> {
166 Self::new(
167 Bytecode::load_from_file(service_bytecode_file)
168 .await
169 .map_err(anyhow::Error::from)
170 .map_err(WasmExecutionError::LoadServiceModule)?,
171 runtime,
172 )
173 .await
174 }
175}
176
177impl UserServiceModule for WasmServiceModule {
178 fn instantiate(
179 &self,
180 runtime: ServiceSyncRuntimeHandle,
181 ) -> Result<UserServiceInstance, ExecutionError> {
182 #[cfg(with_metrics)]
183 let _instantiation_latency = metrics::SERVICE_INSTANTIATION_LATENCY.measure_latency();
184
185 let instance: UserServiceInstance = match self {
186 #[cfg(with_wasmtime)]
187 WasmServiceModule::Wasmtime { module } => {
188 Box::new(WasmtimeServiceInstance::prepare(module, runtime)?)
189 }
190 #[cfg(with_wasmer)]
191 WasmServiceModule::Wasmer { module } => {
192 Box::new(WasmerServiceInstance::prepare(module, runtime)?)
193 }
194 };
195
196 Ok(instance)
197 }
198}
199
200pub fn add_metering(bytecode: Bytecode) -> Result<Bytecode, WasmExecutionError> {
202 struct WasmtimeRules;
203
204 impl gas_metering::Rules for WasmtimeRules {
205 fn instruction_cost(
210 &self,
211 instruction: &parity_wasm::elements::Instruction,
212 ) -> Option<u32> {
213 use parity_wasm::elements::Instruction::*;
214
215 Some(match instruction {
216 Nop | Drop | Block(_) | Loop(_) | Unreachable | Else | End => 0,
217 _ => 1,
218 })
219 }
220
221 fn memory_grow_cost(&self) -> gas_metering::MemoryGrowCost {
222 gas_metering::MemoryGrowCost::Free
223 }
224
225 fn call_per_local_cost(&self) -> u32 {
226 0
227 }
228 }
229
230 let instrumented_module = gas_metering::inject(
231 parity_wasm::deserialize_buffer(&bytecode.bytes)?,
232 gas_metering::host_function::Injector::new(
233 "linera:app/contract-runtime-api",
234 "consume-fuel",
235 ),
236 &WasmtimeRules,
237 )
238 .map_err(|_| WasmExecutionError::InstrumentModule)?;
239
240 Ok(Bytecode::new(instrumented_module.into_bytes()?))
241}
242
243#[cfg(web)]
244const _: () = {
245 use js_sys::wasm_bindgen::JsValue;
246 use web_thread_select as web_thread;
247
248 impl web_thread::AsJs for WasmServiceModule {
249 fn to_js(&self) -> Result<JsValue, JsValue> {
250 match self {
251 #[cfg(with_wasmer)]
252 Self::Wasmer { module } => Ok(::wasmer::Module::clone(module).into()),
253 }
254 }
255
256 fn from_js(value: JsValue) -> Result<Self, JsValue> {
257 cfg_if::cfg_if! {
260 if #[cfg(with_wasmer)] {
261 Ok(Self::Wasmer {
262 module: value.try_into()?,
263 })
264 } else {
265 Err(value)
266 }
267 }
268 }
269 }
270
271 impl web_thread::Post for WasmServiceModule {}
272
273 impl web_thread::AsJs for WasmContractModule {
274 fn to_js(&self) -> Result<JsValue, JsValue> {
275 match self {
276 #[cfg(with_wasmer)]
277 Self::Wasmer { module, engine: _ } => Ok(::wasmer::Module::clone(module).into()),
278 }
279 }
280
281 fn from_js(value: JsValue) -> Result<Self, JsValue> {
282 cfg_if::cfg_if! {
285 if #[cfg(with_wasmer)] {
286 Ok(Self::Wasmer {
287 module: value.try_into()?,
288 engine: Default::default(),
289 })
290 } else {
291 Err(value)
292 }
293 }
294 }
295 }
296
297 impl web_thread::Post for WasmContractModule {}
298};
299
300#[derive(Debug, Error)]
302pub enum WasmExecutionError {
303 #[error("Failed to load contract Wasm module: {_0}")]
304 LoadContractModule(#[source] anyhow::Error),
305 #[error("Failed to load service Wasm module: {_0}")]
306 LoadServiceModule(#[source] anyhow::Error),
307 #[error("Failed to instrument Wasm module to add fuel metering")]
308 InstrumentModule,
309 #[error("Invalid Wasm module: {0}")]
310 InvalidBytecode(#[from] wasm_instrument::parity_wasm::SerializationError),
311 #[cfg(with_wasmer)]
312 #[error("Failed to instantiate Wasm module: {_0}")]
313 InstantiateModuleWithWasmer(#[from] Box<::wasmer::InstantiationError>),
314 #[cfg(with_wasmtime)]
315 #[error("Failed to create and configure Wasmtime runtime: {_0}")]
316 CreateWasmtimeEngine(#[source] anyhow::Error),
317 #[cfg(with_wasmer)]
318 #[error(
319 "Failed to execute Wasm module in Wasmer. This may be caused by panics or insufficient fuel. {0}"
320 )]
321 ExecuteModuleInWasmer(#[from] ::wasmer::RuntimeError),
322 #[cfg(with_wasmtime)]
323 #[error("Failed to execute Wasm module in Wasmtime: {0}")]
324 ExecuteModuleInWasmtime(#[from] ::wasmtime::Trap),
325 #[error("Failed to execute Wasm module: {0}")]
326 ExecuteModule(#[from] linera_witty::RuntimeError),
327 #[error("Attempt to wait for an unknown promise")]
328 UnknownPromise,
329 #[error("Attempt to call incorrect `wait` function for a promise")]
330 IncorrectPromise,
331}
332
333#[cfg(with_wasmer)]
334impl From<::wasmer::InstantiationError> for WasmExecutionError {
335 fn from(instantiation_error: ::wasmer::InstantiationError) -> Self {
336 WasmExecutionError::InstantiateModuleWithWasmer(Box::new(instantiation_error))
337 }
338}
339
340#[cfg(with_testing)]
342pub mod test {
343 use std::{path::Path, sync::LazyLock};
344
345 #[cfg(with_fs)]
346 use super::{WasmContractModule, WasmRuntime, WasmServiceModule};
347
348 fn build_applications_in_directory(dir: &str) -> Result<(), std::io::Error> {
349 let output = std::process::Command::new("cargo")
350 .current_dir(dir)
351 .args(["build", "--release", "--target", "wasm32-unknown-unknown"])
352 .output()?;
353 if !output.status.success() {
354 panic!(
355 "Failed to build applications in directory {dir}.\n\n\
356 stdout:\n-------\n{}\n\n\
357 stderr:\n-------\n{}",
358 String::from_utf8_lossy(&output.stdout),
359 String::from_utf8_lossy(&output.stderr),
360 );
361 }
362 Ok(())
363 }
364
365 fn build_applications() -> Result<(), std::io::Error> {
366 for dir in ["../examples", "../linera-sdk/tests/fixtures"] {
367 build_applications_in_directory(dir)?;
368 }
369 Ok(())
370 }
371
372 pub fn get_example_bytecode_paths(name: &str) -> Result<(String, String), std::io::Error> {
373 let name = name.replace('-', "_");
374 static INSTANCE: LazyLock<()> = LazyLock::new(|| build_applications().unwrap());
375 LazyLock::force(&INSTANCE);
376 for dir in ["../examples", "../linera-sdk/tests/fixtures"] {
377 let prefix = format!("{dir}/target/wasm32-unknown-unknown/release");
378 let file_contract = format!("{prefix}/{name}_contract.wasm");
379 let file_service = format!("{prefix}/{name}_service.wasm");
380 if Path::new(&file_contract).exists() && Path::new(&file_service).exists() {
381 return Ok((file_contract, file_service));
382 }
383 }
384 Err(std::io::Error::last_os_error())
385 }
386
387 #[cfg(with_fs)]
388 pub async fn build_example_application(
389 name: &str,
390 wasm_runtime: impl Into<Option<WasmRuntime>>,
391 ) -> Result<(WasmContractModule, WasmServiceModule), anyhow::Error> {
392 let (contract_path, service_path) = get_example_bytecode_paths(name)?;
393 let wasm_runtime = wasm_runtime.into().unwrap_or_default();
394 let contract = WasmContractModule::from_file(&contract_path, wasm_runtime).await?;
395 let service = WasmServiceModule::from_file(&service_path, wasm_runtime).await?;
396 Ok((contract, service))
397 }
398}