Skip to content

Commit 6c0b4b5

Browse files
authored
Standardize Wasm fuel metering (#3647)
## Motivation Wasmer and Wasmtime had different fuel metering implementations. When using Wasmer, the code used bytecode instrumentation, altering the Wasm bytecode to call back to the host to consume fuel. When using Wasmtime, the code used Wasmtime's own native fuel metering implementation. This led to inconsistencies and required a sanitizer to try to ensure both runtimes measured fuel consumption the same way. ## Proposal Use the same bytecode instrumentation for all runtimes. This allows removing the sanitizer. ## Test Plan CI should catch any regressions. ## Release Plan - These changes follow the usual release cycle, because this only contains an internal refactor that should not affect any visible behavior. ## Links - Closes #1967 - [reviewer checklist](https://github.com/linera-io/linera-protocol/blob/main/CONTRIBUTING.md#reviewer-checklist)
1 parent ee11220 commit 6c0b4b5

File tree

9 files changed

+76
-788
lines changed

9 files changed

+76
-788
lines changed

Cargo.lock

Lines changed: 0 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/Cargo.lock

Lines changed: 2 additions & 23 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

linera-execution/Cargo.toml

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,8 @@ revm = [
2323
]
2424
fs = ["tokio/fs"]
2525
metrics = ["prometheus", "linera-views/metrics"]
26-
wasmer = [
27-
"dep:wasmer",
28-
"wasmer/enable-serde",
29-
"linera-witty/wasmer",
30-
"wasm-encoder",
31-
"wasm-instrument",
32-
"wasmparser",
33-
]
34-
wasmtime = [
35-
"dep:wasmtime",
36-
"linera-witty/wasmtime",
37-
"wasm-encoder",
38-
"wasmparser",
39-
]
26+
wasmer = ["dep:wasmer", "wasmer/enable-serde", "linera-witty/wasmer"]
27+
wasmtime = ["dep:wasmtime", "linera-witty/wasmtime"]
4028
web = ["linera-base/web", "linera-views/web", "js-sys"]
4129

4230
[dependencies]
@@ -75,9 +63,7 @@ tempfile = { workspace = true, optional = true }
7563
thiserror.workspace = true
7664
tracing = { workspace = true, features = ["log"] }
7765
url.workspace = true
78-
wasm-encoder = { workspace = true, optional = true }
79-
wasm-instrument = { workspace = true, optional = true, features = ["sign_ext"] }
80-
wasmparser = { workspace = true, optional = true }
66+
wasm-instrument = { workspace = true, features = ["sign_ext"] }
8167
wasmtime = { workspace = true, optional = true }
8268

8369
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]

linera-execution/src/lib.rs

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1298,10 +1298,6 @@ pub enum WasmRuntime {
12981298
#[cfg_attr(not(with_wasmer), default)]
12991299
#[display("wasmtime")]
13001300
Wasmtime,
1301-
#[cfg(with_wasmer)]
1302-
WasmerWithSanitizer,
1303-
#[cfg(with_wasmtime)]
1304-
WasmtimeWithSanitizer,
13051301
}
13061302

13071303
#[derive(Clone, Copy, Display)]
@@ -1318,19 +1314,6 @@ pub trait WithWasmDefault {
13181314
fn with_wasm_default(self) -> Self;
13191315
}
13201316

1321-
impl WasmRuntime {
1322-
pub fn needs_sanitizer(self) -> bool {
1323-
match self {
1324-
#[cfg(with_wasmer)]
1325-
WasmRuntime::WasmerWithSanitizer => true,
1326-
#[cfg(with_wasmtime)]
1327-
WasmRuntime::WasmtimeWithSanitizer => true,
1328-
#[cfg(with_wasm_runtime)]
1329-
_ => false,
1330-
}
1331-
}
1332-
}
1333-
13341317
impl WithWasmDefault for Option<WasmRuntime> {
13351318
fn with_wasm_default(self) -> Self {
13361319
#[cfg(with_wasm_runtime)]

linera-execution/src/wasm/mod.rs

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
mod entrypoints;
1414
mod module_cache;
15-
mod sanitizer;
1615
#[macro_use]
1716
mod runtime_api;
1817
#[cfg(with_wasmer)]
@@ -22,6 +21,7 @@ mod wasmtime;
2221

2322
use linera_base::data_types::Bytecode;
2423
use thiserror::Error;
24+
use wasm_instrument::{gas_metering, parity_wasm};
2525
#[cfg(with_wasmer)]
2626
use wasmer::{WasmerContractInstance, WasmerServiceInstance};
2727
#[cfg(with_wasmtime)]
@@ -35,7 +35,6 @@ use {
3535
std::sync::LazyLock,
3636
};
3737

38-
use self::sanitizer::sanitize;
3938
pub use self::{
4039
entrypoints::{ContractEntrypoints, ServiceEntrypoints},
4140
runtime_api::{BaseRuntimeApi, ContractRuntimeApi, RuntimeApiData, ServiceRuntimeApi},
@@ -83,22 +82,12 @@ impl WasmContractModule {
8382
contract_bytecode: Bytecode,
8483
runtime: WasmRuntime,
8584
) -> Result<Self, WasmExecutionError> {
86-
let contract_bytecode = if runtime.needs_sanitizer() {
87-
// Ensure bytecode normalization whenever wasmer and wasmtime are possibly
88-
// compared.
89-
sanitize(contract_bytecode).map_err(WasmExecutionError::LoadContractModule)?
90-
} else {
91-
contract_bytecode
92-
};
85+
let contract_bytecode = add_metering(contract_bytecode)?;
9386
match runtime {
9487
#[cfg(with_wasmer)]
95-
WasmRuntime::Wasmer | WasmRuntime::WasmerWithSanitizer => {
96-
Self::from_wasmer(contract_bytecode).await
97-
}
88+
WasmRuntime::Wasmer => Self::from_wasmer(contract_bytecode).await,
9889
#[cfg(with_wasmtime)]
99-
WasmRuntime::Wasmtime | WasmRuntime::WasmtimeWithSanitizer => {
100-
Self::from_wasmtime(contract_bytecode).await
101-
}
90+
WasmRuntime::Wasmtime => Self::from_wasmtime(contract_bytecode).await,
10291
}
10392
}
10493

@@ -159,13 +148,9 @@ impl WasmServiceModule {
159148
) -> Result<Self, WasmExecutionError> {
160149
match runtime {
161150
#[cfg(with_wasmer)]
162-
WasmRuntime::Wasmer | WasmRuntime::WasmerWithSanitizer => {
163-
Self::from_wasmer(service_bytecode).await
164-
}
151+
WasmRuntime::Wasmer => Self::from_wasmer(service_bytecode).await,
165152
#[cfg(with_wasmtime)]
166-
WasmRuntime::Wasmtime | WasmRuntime::WasmtimeWithSanitizer => {
167-
Self::from_wasmtime(service_bytecode).await
168-
}
153+
WasmRuntime::Wasmtime => Self::from_wasmtime(service_bytecode).await,
169154
}
170155
}
171156

@@ -209,6 +194,49 @@ impl UserServiceModule for WasmServiceModule {
209194
}
210195
}
211196

197+
/// Instrument the [`Bytecode`] to add fuel metering.
198+
pub fn add_metering(bytecode: Bytecode) -> Result<Bytecode, WasmExecutionError> {
199+
struct WasmtimeRules;
200+
201+
impl gas_metering::Rules for WasmtimeRules {
202+
/// Calculates the fuel cost of a WebAssembly [`Operator`].
203+
///
204+
/// The rules try to follow the hardcoded [rules in the Wasmtime runtime
205+
/// engine](https://docs.rs/wasmtime/5.0.0/wasmtime/struct.Store.html#method.add_fuel).
206+
fn instruction_cost(
207+
&self,
208+
instruction: &parity_wasm::elements::Instruction,
209+
) -> Option<u32> {
210+
use parity_wasm::elements::Instruction::*;
211+
212+
Some(match instruction {
213+
Nop | Drop | Block(_) | Loop(_) | Unreachable | Else | End => 0,
214+
_ => 1,
215+
})
216+
}
217+
218+
fn memory_grow_cost(&self) -> gas_metering::MemoryGrowCost {
219+
gas_metering::MemoryGrowCost::Free
220+
}
221+
222+
fn call_per_local_cost(&self) -> u32 {
223+
0
224+
}
225+
}
226+
227+
let instrumented_module = gas_metering::inject(
228+
parity_wasm::deserialize_buffer(&bytecode.bytes)?,
229+
gas_metering::host_function::Injector::new(
230+
"linera:app/contract-runtime-api",
231+
"consume-fuel",
232+
),
233+
&WasmtimeRules,
234+
)
235+
.map_err(|_| WasmExecutionError::InstrumentModule)?;
236+
237+
Ok(Bytecode::new(instrumented_module.into_bytes()?))
238+
}
239+
212240
#[cfg(web)]
213241
const _: () = {
214242
use js_sys::wasm_bindgen::JsValue;
@@ -277,6 +305,10 @@ pub enum WasmExecutionError {
277305
LoadContractModule(#[source] anyhow::Error),
278306
#[error("Failed to load service Wasm module: {_0}")]
279307
LoadServiceModule(#[source] anyhow::Error),
308+
#[error("Failed to instrument Wasm module to add fuel metering")]
309+
InstrumentModule,
310+
#[error("Invalid Wasm module")]
311+
InvalidBytecode(#[from] wasm_instrument::parity_wasm::SerializationError),
280312
#[cfg(with_wasmer)]
281313
#[error("Failed to instantiate Wasm module: {_0}")]
282314
InstantiateModuleWithWasmer(#[from] Box<::wasmer::InstantiationError>),

0 commit comments

Comments
 (0)