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;
29#[cfg(with_wasmer)]
30use wasmer::{WasmerContractInstance, WasmerServiceInstance};
31#[cfg(with_wasmtime)]
32use wasmtime::{WasmtimeContractInstance, WasmtimeServiceInstance};
33
34pub use self::{
35 entrypoints::{ContractEntrypoints, ServiceEntrypoints},
36 runtime_api::{BaseRuntimeApi, ContractRuntimeApi, RuntimeApiData, ServiceRuntimeApi},
37};
38use crate::{
39 ContractSyncRuntimeHandle, ExecutionError, ServiceSyncRuntimeHandle, UserContractInstance,
40 UserContractModule, UserServiceInstance, UserServiceModule, WasmRuntime,
41};
42
43#[cfg(with_metrics)]
44mod metrics {
45 use std::sync::LazyLock;
46
47 use linera_base::prometheus_util::{
48 exponential_bucket_interval, exponential_bucket_latencies, register_histogram_vec,
49 };
50 use prometheus::HistogramVec;
51
52 pub static CONTRACT_INSTANTIATION_LATENCY: LazyLock<HistogramVec> = LazyLock::new(|| {
53 register_histogram_vec(
54 "wasm_contract_instantiation_latency",
55 "Wasm contract instantiation latency",
56 &[],
57 exponential_bucket_latencies(100.0),
58 )
59 });
60
61 pub static SERVICE_INSTANTIATION_LATENCY: LazyLock<HistogramVec> = LazyLock::new(|| {
62 register_histogram_vec(
63 "wasm_service_instantiation_latency",
64 "Wasm service instantiation latency",
65 &[],
66 exponential_bucket_latencies(100.0),
67 )
68 });
69
70 pub static WASM_BYTECODE_SIZE_BYTES: LazyLock<HistogramVec> = LazyLock::new(|| {
71 register_histogram_vec(
72 "wasm_bytecode_size_bytes",
73 "Size in bytes of WASM bytecodes being loaded",
74 &["type"],
75 exponential_bucket_interval(10_000.0, 100_000_000.0),
76 )
77 });
78}
79
80#[derive(Clone)]
82#[allow(missing_docs)]
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)]
149#[allow(missing_docs)]
150pub enum WasmServiceModule {
151 #[cfg(with_wasmer)]
152 Wasmer { module: ::wasmer::Module },
153 #[cfg(with_wasmtime)]
154 Wasmtime { module: ::wasmtime::Module },
155}
156
157impl WasmServiceModule {
158 pub async fn new(
160 service_bytecode: Bytecode,
161 runtime: WasmRuntime,
162 ) -> Result<Self, WasmExecutionError> {
163 match runtime {
164 #[cfg(with_wasmer)]
165 WasmRuntime::Wasmer => Self::from_wasmer(service_bytecode).await,
166 #[cfg(with_wasmtime)]
167 WasmRuntime::Wasmtime => Self::from_wasmtime(service_bytecode).await,
168 }
169 }
170
171 #[cfg(with_fs)]
173 pub async fn from_file(
174 service_bytecode_file: impl AsRef<Path>,
175 runtime: WasmRuntime,
176 ) -> Result<Self, WasmExecutionError> {
177 Self::new(
178 Bytecode::load_from_file(service_bytecode_file)
179 .await
180 .map_err(anyhow::Error::from)
181 .map_err(WasmExecutionError::LoadServiceModule)?,
182 runtime,
183 )
184 .await
185 }
186}
187
188impl UserServiceModule for WasmServiceModule {
189 fn instantiate(
190 &self,
191 runtime: ServiceSyncRuntimeHandle,
192 ) -> Result<UserServiceInstance, ExecutionError> {
193 #[cfg(with_metrics)]
194 let _instantiation_latency = metrics::SERVICE_INSTANTIATION_LATENCY.measure_latency();
195
196 let instance: UserServiceInstance = match self {
197 #[cfg(with_wasmtime)]
198 WasmServiceModule::Wasmtime { module } => {
199 Box::new(WasmtimeServiceInstance::prepare(module, runtime)?)
200 }
201 #[cfg(with_wasmer)]
202 WasmServiceModule::Wasmer { module } => {
203 Box::new(WasmerServiceInstance::prepare(module, runtime)?)
204 }
205 };
206
207 Ok(instance)
208 }
209}
210
211pub fn add_metering(bytecode: &Bytecode) -> Result<Bytecode, WasmExecutionError> {
213 pub struct Costs;
214 impl walrus_meter::Costs for Costs {
215 fn instruction(&self, instruction: &walrus::ir::Instr) -> i32 {
216 use walrus::ir::Instr::*;
217 match instruction {
218 Drop(_) | Block(_) | Loop(_) | Unreachable(_) => 0,
219 _ => 1,
220 }
221 }
222 }
223
224 let instrumented_module = walrus_meter::instrument(
225 &bytecode.bytes,
226 Costs,
227 ("linera:app/contract-runtime-api", "consume-fuel"),
228 )
229 .map_err(|_| WasmExecutionError::InstrumentModule)?;
230
231 Ok(Bytecode::new(instrumented_module))
232}
233
234#[cfg(web)]
235const _: () = {
236 use js_sys::wasm_bindgen::JsValue;
237 use web_thread_select as web_thread;
238
239 impl web_thread::AsJs for WasmServiceModule {
240 fn to_js(&self) -> Result<JsValue, JsValue> {
241 match self {
242 #[cfg(with_wasmer)]
243 Self::Wasmer { module } => Ok(::wasmer::Module::clone(module).into()),
244 }
245 }
246
247 fn from_js(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 web_thread::Post for WasmServiceModule {}
263
264 impl web_thread::AsJs for WasmContractModule {
265 fn to_js(&self) -> Result<JsValue, JsValue> {
266 match self {
267 #[cfg(with_wasmer)]
268 Self::Wasmer { module, engine: _ } => Ok(::wasmer::Module::clone(module).into()),
269 }
270 }
271
272 fn from_js(value: JsValue) -> Result<Self, JsValue> {
273 cfg_if::cfg_if! {
276 if #[cfg(with_wasmer)] {
277 Ok(Self::Wasmer {
278 module: value.try_into()?,
279 engine: Default::default(),
280 })
281 } else {
282 Err(value)
283 }
284 }
285 }
286 }
287
288 impl web_thread::Post for WasmContractModule {}
289};
290
291#[derive(Debug, Error)]
293#[allow(missing_docs)]
294pub enum WasmExecutionError {
295 #[error("Failed to load contract Wasm module: {_0}")]
296 LoadContractModule(#[source] anyhow::Error),
297 #[error("Failed to load service Wasm module: {_0}")]
298 LoadServiceModule(#[source] anyhow::Error),
299 #[error("Failed to instrument Wasm module to add fuel metering")]
300 InstrumentModule,
301 #[cfg(with_wasmer)]
302 #[error("Failed to instantiate Wasm module: {_0}")]
303 InstantiateModuleWithWasmer(#[from] Box<::wasmer::InstantiationError>),
304 #[cfg(with_wasmtime)]
305 #[error("Failed to create and configure Wasmtime runtime: {_0}")]
306 CreateWasmtimeEngine(#[source] anyhow::Error),
307 #[cfg(with_wasmer)]
308 #[error(
309 "Failed to execute Wasm module in Wasmer. This may be caused by panics or insufficient fuel. {0}"
310 )]
311 ExecuteModuleInWasmer(#[from] ::wasmer::RuntimeError),
312 #[cfg(with_wasmtime)]
313 #[error("Failed to execute Wasm module in Wasmtime: {0}")]
314 ExecuteModuleInWasmtime(#[from] ::wasmtime::Trap),
315 #[error("Failed to execute Wasm module: {0}")]
316 ExecuteModule(#[from] linera_witty::RuntimeError),
317 #[error("Attempt to wait for an unknown promise")]
318 UnknownPromise,
319 #[error("Attempt to call incorrect `wait` function for a promise")]
320 IncorrectPromise,
321}
322
323#[cfg(with_wasmer)]
324impl From<::wasmer::InstantiationError> for WasmExecutionError {
325 fn from(instantiation_error: ::wasmer::InstantiationError) -> Self {
326 WasmExecutionError::InstantiateModuleWithWasmer(Box::new(instantiation_error))
327 }
328}
329
330#[cfg(with_testing)]
332pub mod test {
333 use std::{path::Path, sync::LazyLock};
334
335 fn build_applications_in_directory(dir: &str) -> Result<(), std::io::Error> {
336 let output = std::process::Command::new("cargo")
337 .current_dir(dir)
338 .args(["build", "--release", "--target", "wasm32-unknown-unknown"])
339 .output()?;
340 if !output.status.success() {
341 panic!(
342 "Failed to build applications in directory {dir}.\n\n\
343 stdout:\n-------\n{}\n\n\
344 stderr:\n-------\n{}",
345 String::from_utf8_lossy(&output.stdout),
346 String::from_utf8_lossy(&output.stderr),
347 );
348 }
349 Ok(())
350 }
351
352 fn build_applications() -> Result<(), std::io::Error> {
353 for dir in ["../examples", "../linera-sdk/tests/fixtures"] {
354 build_applications_in_directory(dir)?;
355 }
356 Ok(())
357 }
358
359 pub fn get_example_bytecode_paths(name: &str) -> Result<(String, String), std::io::Error> {
361 let name = name.replace('-', "_");
362 static INSTANCE: LazyLock<()> = LazyLock::new(|| build_applications().unwrap());
363 LazyLock::force(&INSTANCE);
364 for dir in ["../examples", "../linera-sdk/tests/fixtures"] {
365 let prefix = format!("{dir}/target/wasm32-unknown-unknown/release");
366 let file_contract = format!("{prefix}/{name}_contract.wasm");
367 let file_service = format!("{prefix}/{name}_service.wasm");
368 if Path::new(&file_contract).exists() && Path::new(&file_service).exists() {
369 return Ok((file_contract, file_service));
370 }
371 }
372 Err(std::io::Error::last_os_error())
373 }
374}