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