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 use web_thread_select as web_thread;
245
246 impl web_thread::AsJs for WasmServiceModule {
247 fn to_js(&self) -> Result<JsValue, JsValue> {
248 match self {
249 #[cfg(with_wasmer)]
250 Self::Wasmer { module } => Ok(::wasmer::Module::clone(module).into()),
251 }
252 }
253
254 fn from_js(value: JsValue) -> Result<Self, JsValue> {
255 cfg_if::cfg_if! {
258 if #[cfg(with_wasmer)] {
259 Ok(Self::Wasmer {
260 module: value.try_into()?,
261 })
262 } else {
263 Err(value)
264 }
265 }
266 }
267 }
268
269 impl web_thread::Post for WasmServiceModule {}
270
271 impl web_thread::AsJs for WasmContractModule {
272 fn to_js(&self) -> Result<JsValue, JsValue> {
273 match self {
274 #[cfg(with_wasmer)]
275 Self::Wasmer { module, engine: _ } => Ok(::wasmer::Module::clone(module).into()),
276 }
277 }
278
279 fn from_js(value: JsValue) -> Result<Self, JsValue> {
280 cfg_if::cfg_if! {
283 if #[cfg(with_wasmer)] {
284 Ok(Self::Wasmer {
285 module: value.try_into()?,
286 engine: Default::default(),
287 })
288 } else {
289 Err(value)
290 }
291 }
292 }
293 }
294
295 impl web_thread::Post for WasmContractModule {}
296};
297
298#[derive(Debug, Error)]
300pub enum WasmExecutionError {
301 #[error("Failed to load contract Wasm module: {_0}")]
302 LoadContractModule(#[source] anyhow::Error),
303 #[error("Failed to load service Wasm module: {_0}")]
304 LoadServiceModule(#[source] anyhow::Error),
305 #[error("Failed to instrument Wasm module to add fuel metering")]
306 InstrumentModule,
307 #[error("Invalid Wasm module: {0}")]
308 InvalidBytecode(#[from] wasm_instrument::parity_wasm::SerializationError),
309 #[cfg(with_wasmer)]
310 #[error("Failed to instantiate Wasm module: {_0}")]
311 InstantiateModuleWithWasmer(#[from] Box<::wasmer::InstantiationError>),
312 #[cfg(with_wasmtime)]
313 #[error("Failed to create and configure Wasmtime runtime: {_0}")]
314 CreateWasmtimeEngine(#[source] anyhow::Error),
315 #[cfg(with_wasmer)]
316 #[error(
317 "Failed to execute Wasm module in Wasmer. This may be caused by panics or insufficient fuel. {0}"
318 )]
319 ExecuteModuleInWasmer(#[from] ::wasmer::RuntimeError),
320 #[cfg(with_wasmtime)]
321 #[error("Failed to execute Wasm module in Wasmtime: {0}")]
322 ExecuteModuleInWasmtime(#[from] ::wasmtime::Trap),
323 #[error("Failed to execute Wasm module: {0}")]
324 ExecuteModule(#[from] linera_witty::RuntimeError),
325 #[error("Attempt to wait for an unknown promise")]
326 UnknownPromise,
327 #[error("Attempt to call incorrect `wait` function for a promise")]
328 IncorrectPromise,
329}
330
331#[cfg(with_wasmer)]
332impl From<::wasmer::InstantiationError> for WasmExecutionError {
333 fn from(instantiation_error: ::wasmer::InstantiationError) -> Self {
334 WasmExecutionError::InstantiateModuleWithWasmer(Box::new(instantiation_error))
335 }
336}
337
338#[cfg(with_testing)]
340pub mod test {
341 use std::{path::Path, sync::LazyLock};
342
343 #[cfg(with_fs)]
344 use super::{WasmContractModule, WasmRuntime, WasmServiceModule};
345
346 fn build_applications_in_directory(dir: &str) -> Result<(), std::io::Error> {
347 let output = std::process::Command::new("cargo")
348 .current_dir(dir)
349 .args(["build", "--release", "--target", "wasm32-unknown-unknown"])
350 .output()?;
351 if !output.status.success() {
352 panic!(
353 "Failed to build applications in directory {dir}.\n\n\
354 stdout:\n-------\n{}\n\n\
355 stderr:\n-------\n{}",
356 String::from_utf8_lossy(&output.stdout),
357 String::from_utf8_lossy(&output.stderr),
358 );
359 }
360 Ok(())
361 }
362
363 fn build_applications() -> Result<(), std::io::Error> {
364 for dir in ["../examples", "../linera-sdk/tests/fixtures"] {
365 build_applications_in_directory(dir)?;
366 }
367 Ok(())
368 }
369
370 pub fn get_example_bytecode_paths(name: &str) -> Result<(String, String), std::io::Error> {
371 let name = name.replace('-', "_");
372 static INSTANCE: LazyLock<()> = LazyLock::new(|| build_applications().unwrap());
373 LazyLock::force(&INSTANCE);
374 for dir in ["../examples", "../linera-sdk/tests/fixtures"] {
375 let prefix = format!("{dir}/target/wasm32-unknown-unknown/release");
376 let file_contract = format!("{prefix}/{name}_contract.wasm");
377 let file_service = format!("{prefix}/{name}_service.wasm");
378 if Path::new(&file_contract).exists() && Path::new(&file_service).exists() {
379 return Ok((file_contract, file_service));
380 }
381 }
382 Err(std::io::Error::last_os_error())
383 }
384
385 #[cfg(with_fs)]
386 pub async fn build_example_application(
387 name: &str,
388 wasm_runtime: impl Into<Option<WasmRuntime>>,
389 ) -> Result<(WasmContractModule, WasmServiceModule), anyhow::Error> {
390 let (contract_path, service_path) = get_example_bytecode_paths(name)?;
391 let wasm_runtime = wasm_runtime.into().unwrap_or_default();
392 let contract = WasmContractModule::from_file(&contract_path, wasm_runtime).await?;
393 let service = WasmServiceModule::from_file(&service_path, wasm_runtime).await?;
394 Ok((contract, service))
395 }
396}