Skip to content

runtime: real gas-budget enforcement + Wasmtime fuel metering (closes #51)#62

Merged
proofmancer merged 1 commit into
mainfrom
runtime/gas-budget
May 25, 2026
Merged

runtime: real gas-budget enforcement + Wasmtime fuel metering (closes #51)#62
proofmancer merged 1 commit into
mainfrom
runtime/gas-budget

Conversation

@proofmancer
Copy link
Copy Markdown
Member

Closes #51.

The v0.3 runtime tracked gas_used per dimension but never aborted on overflow. That caveat is gone. Two layers of metering now compose so a malicious or buggy module cannot starve a node.

What landed

1. Per-dimension gas budgets

The hostcall ABI documented in spec/abi/wasm.md already specified env.gas_consume(dim, amount). The runtime now actually enforces it.

  • New gas_budgets: HashMap<u32, u64> on HostState
  • New Instance::set_gas_budget(dim, units) API
  • env.gas_consume hostcall: returns Err(anyhow!("out of gas: ...")) when gas_used + amount > budget. Wasmtime converts the Err to a trap; the trap propagates as an Instance::call error.
  • Dimensions without a budget set stay unmetered (existing callers unchanged).

2. Wasmtime fuel

Enabled in Config::consume_fuel(true) so every WASM instruction tics the fuel counter. Defense against the failure mode where a malicious module spins in a pure-WASM loop and never calls back into a hostcall.

  • Instance::set_fuel(units) to set the budget
  • Instance::fuel_remaining() to read what's left
  • New DEFAULT_FUEL = 10_000_000_000 applied at load time; existing callers see no behavior change

3. Tests

7 new unit tests, all passing:

  • gas_consume_without_budget_runs_freely: unmetered call is uncapped
  • gas_consume_under_budget_runs_and_records_usage: budget OK -> call succeeds, usage tracked
  • gas_consume_over_budget_traps_with_clear_error: budget < amount -> trap with "out of gas" in message, gas_used not incremented
  • gas_budget_only_applies_to_set_dimension: budget on dim 1 ignored when call charges dim 0
  • fuel_default_is_sufficient_for_counter_module: 100 increment calls fit in DEFAULT_FUEL
  • fuel_is_actually_consumed: one call decreases fuel_remaining
  • fuel_exhaustion_traps_the_call: zero fuel -> trap

4. Hand-crafted GAS_TEST_WASM constant

The existing counter module doesn't call gas_consume (v0.3 codegen doesn't emit it). Testing budget enforcement requires a module that does. 73 bytes of hand-crafted WASM in lib.rs tests: imports env.gas_consume, exports do_work which calls gas_consume(0, 100) and returns 1.

End-to-end demo

let rt = Runtime::new();
let mut instance = rt.load(GAS_TEST_WASM).unwrap();

instance.set_gas_budget(0, 50);                     // budget 50, charge 100
let err = instance.call("do_work", &[]).unwrap_err();
// "out of gas: dimension 0 budget 50 exceeded (100 > 50)"

instance.set_gas_budget(0, 200);                     // raise the budget
let result = instance.call("do_work", &[]).unwrap();
assert_eq!(result, 1);
assert_eq!(instance.gas_used(0), 100);

What this PR does NOT yet do

Each is its own future issue:

What unblocks next

@proofmancer proofmancer merged commit d7b26ed into main May 25, 2026
2 checks passed
@proofmancer proofmancer deleted the runtime/gas-budget branch May 25, 2026 14:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Runtime: real gas budget enforcement on the WASM engine

1 participant