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