From 0229f865b6cdec028d586e7d8273558b3d1dbe51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Sun, 11 Sep 2022 14:36:06 +0200 Subject: [PATCH] Use `u64` for gas counter (#30) * Use `u64` for gas counter * Update doc --- CHANGELOG.md | 10 ++++ Cargo.toml | 3 +- src/gas_metering/mod.rs | 87 +++++++++++++++++-------------- src/gas_metering/validation.rs | 14 ++--- tests/expectations/gas/branch.wat | 6 +-- tests/expectations/gas/call.wat | 6 +-- tests/expectations/gas/ifs.wat | 8 +-- tests/expectations/gas/simple.wat | 10 ++-- tests/expectations/gas/start.wat | 4 +- 9 files changed, 85 insertions(+), 63 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f102637..ba3236c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,17 @@ The interface provided to smart contracts will adhere to semver with one excepti major version bumps will be backwards compatible with regard to already deployed contracts. In other words: Upgrading this pallet will not break pre-existing contracts. +## [v0.3.0] + +### Changed + +- Use 64bit arithmetic for per-block gas counter +[#30](https://github.com/paritytech/wasm-instrument/pull/30) + ## [v0.2.0] 2022-06-06 + +### Changed + - Adjust debug information (if already parsed) when injecting gas metering [#16](https://github.com/paritytech/wasm-instrument/pull/16) diff --git a/Cargo.toml b/Cargo.toml index aad3ae1..391804e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-instrument" -version = "0.2.0" +version = "0.3.0" edition = "2021" rust-version = "1.56.1" authors = ["Parity Technologies "] @@ -27,6 +27,7 @@ parity-wasm = { version = "0.45", default-features = false } binaryen = "0.12" criterion = "0.3" diff = "0.1" +pretty_assertions = "1" rand = "0.8" wat = "1" wasmparser = "0.90" diff --git a/src/gas_metering/mod.rs b/src/gas_metering/mod.rs index 2425552..97a7c35 100644 --- a/src/gas_metering/mod.rs +++ b/src/gas_metering/mod.rs @@ -105,7 +105,7 @@ impl Rules for ConstantCostRules { /// imported gas metering function. /// /// The output module imports a function "gas" from the specified module with type signature -/// [i32] -> []. The argument is the amount of gas required to continue execution. The external +/// [i64] -> []. The argument is the amount of gas required to continue execution. The external /// function is meant to keep track of the total amount of gas used and trap or otherwise halt /// execution of the runtime if the gas usage exceeds some allowed limit. /// @@ -144,7 +144,7 @@ pub fn inject( // Injecting gas counting external let mut mbuilder = builder::from_module(module); let import_sig = - mbuilder.push_signature(builder::signature().with_param(ValueType::I32).build_sig()); + mbuilder.push_signature(builder::signature().with_param(ValueType::I64).build_sig()); mbuilder.push_import( builder::import() @@ -284,7 +284,7 @@ struct MeteredBlock { /// Index of the first instruction (aka `Opcode`) in the block. start_pos: usize, /// Sum of costs of all instructions until end of the block. - cost: u32, + cost: u64, } /// Counter is used to manage state during the gas metering algorithm implemented by @@ -375,7 +375,8 @@ impl Counter { .expect("last_index is greater than 0; last_index is stack size - 1; qed"); let prev_metered_block = &mut prev_control_block.active_metered_block; if closing_metered_block.start_pos == prev_metered_block.start_pos { - prev_metered_block.cost += closing_metered_block.cost; + prev_metered_block.cost = + prev_metered_block.cost.checked_add(closing_metered_block.cost).ok_or(())?; return Ok(()) } } @@ -425,7 +426,7 @@ impl Counter { /// Increment the cost of the current block by the specified value. fn increment(&mut self, val: u32) -> Result<(), ()> { let top_block = self.active_metered_block()?; - top_block.cost = top_block.cost.checked_add(val).ok_or(())?; + top_block.cost = top_block.cost.checked_add(val.into()).ok_or(())?; Ok(()) } } @@ -465,8 +466,9 @@ fn add_grow_counter( .with_instructions(elements::Instructions::new(vec![ GetLocal(0), GetLocal(0), - I32Const(cost as i32), - I32Mul, + I64ExtendUI32, + I64Const(i64::from(cost)), + I64Mul, // todo: there should be strong guarantee that it does not return anything on // stack? Call(gas_func), @@ -583,7 +585,7 @@ fn insert_metering_calls( // If there the next block starts at this position, inject metering instructions. let used_block = if let Some(block) = block_iter.peek() { if block.start_pos == original_pos { - new_instrs.push(I32Const(block.cost as i32)); + new_instrs.push(I64Const(block.cost as i64)); new_instrs.push(Call(gas_func)); true } else { @@ -612,6 +614,7 @@ fn insert_metering_calls( mod tests { use super::*; use parity_wasm::{builder, elements, elements::Instruction::*, serialize}; + use pretty_assertions::assert_eq; fn get_function_body( module: &elements::Module, @@ -639,12 +642,20 @@ mod tests { assert_eq!( get_function_body(&injected_module, 0).unwrap(), - &vec![I32Const(2), Call(0), GetGlobal(0), Call(2), End][..] + &vec![I64Const(2), Call(0), GetGlobal(0), Call(2), End][..] ); assert_eq!( get_function_body(&injected_module, 1).unwrap(), - &vec![GetLocal(0), GetLocal(0), I32Const(10000), I32Mul, Call(0), GrowMemory(0), End,] - [..] + &vec![ + GetLocal(0), + GetLocal(0), + I64ExtendUI32, + I64Const(10000), + I64Mul, + Call(0), + GrowMemory(0), + End, + ][..] ); let binary = serialize(injected_module).expect("serialization failed"); @@ -667,7 +678,7 @@ mod tests { assert_eq!( get_function_body(&injected_module, 0).unwrap(), - &vec![I32Const(2), Call(0), GetGlobal(0), GrowMemory(0), End][..] + &vec![I64Const(2), Call(0), GetGlobal(0), GrowMemory(0), End][..] ); assert_eq!(injected_module.functions_space(), 2); @@ -719,17 +730,17 @@ mod tests { assert_eq!( get_function_body(&injected_module, 1).unwrap(), &vec![ - I32Const(3), + I64Const(3), Call(0), Call(1), If(elements::BlockType::NoResult), - I32Const(3), + I64Const(3), Call(0), Call(1), Call(1), Call(1), Else, - I32Const(2), + I64Const(2), Call(0), Call(1), Call(1), @@ -775,7 +786,7 @@ mod tests { expected = r#" (module (func (result i32) - (call 0 (i32.const 1)) + (call 0 (i64.const 1)) (get_global 0))) "# } @@ -795,7 +806,7 @@ mod tests { expected = r#" (module (func (result i32) - (call 0 (i32.const 6)) + (call 0 (i64.const 6)) (get_global 0) (block (get_global 0) @@ -824,16 +835,16 @@ mod tests { expected = r#" (module (func (result i32) - (call 0 (i32.const 3)) + (call 0 (i64.const 3)) (get_global 0) (if (then - (call 0 (i32.const 3)) + (call 0 (i64.const 3)) (get_global 0) (get_global 0) (get_global 0)) (else - (call 0 (i32.const 2)) + (call 0 (i64.const 2)) (get_global 0) (get_global 0))) (get_global 0))) @@ -857,13 +868,13 @@ mod tests { expected = r#" (module (func (result i32) - (call 0 (i32.const 6)) + (call 0 (i64.const 6)) (get_global 0) (block (get_global 0) (drop) (br 0) - (call 0 (i32.const 2)) + (call 0 (i64.const 2)) (get_global 0) (drop)) (get_global 0))) @@ -891,18 +902,18 @@ mod tests { expected = r#" (module (func (result i32) - (call 0 (i32.const 5)) + (call 0 (i64.const 5)) (get_global 0) (block (get_global 0) (if (then - (call 0 (i32.const 4)) + (call 0 (i64.const 4)) (get_global 0) (get_global 0) (drop) (br_if 1))) - (call 0 (i32.const 2)) + (call 0 (i64.const 2)) (get_global 0) (drop)) (get_global 0))) @@ -933,18 +944,18 @@ mod tests { expected = r#" (module (func (result i32) - (call 0 (i32.const 3)) + (call 0 (i64.const 3)) (get_global 0) (loop - (call 0 (i32.const 4)) + (call 0 (i64.const 4)) (get_global 0) (if (then - (call 0 (i32.const 2)) + (call 0 (i64.const 2)) (get_global 0) (br_if 0)) (else - (call 0 (i32.const 4)) + (call 0 (i64.const 4)) (get_global 0) (get_global 0) (drop) @@ -969,13 +980,13 @@ mod tests { expected = r#" (module (func (result i32) - (call 0 (i32.const 2)) + (call 0 (i64.const 2)) (get_global 0) (if (then - (call 0 (i32.const 1)) + (call 0 (i64.const 1)) (return))) - (call 0 (i32.const 1)) + (call 0 (i64.const 1)) (get_global 0))) "# } @@ -998,18 +1009,18 @@ mod tests { expected = r#" (module (func (result i32) - (call 0 (i32.const 5)) + (call 0 (i64.const 5)) (get_global 0) (block (get_global 0) (if (then - (call 0 (i32.const 1)) + (call 0 (i64.const 1)) (br 1)) (else - (call 0 (i32.const 1)) + (call 0 (i64.const 1)) (br 0))) - (call 0 (i32.const 2)) + (call 0 (i64.const 2)) (get_global 0) (drop)) (get_global 0))) @@ -1031,9 +1042,9 @@ mod tests { expected = r#" (module (func - (call 0 (i32.const 2)) + (call 0 (i64.const 2)) (loop - (call 0 (i32.const 1)) + (call 0 (i64.const 1)) (br 0) ) unreachable diff --git a/src/gas_metering/validation.rs b/src/gas_metering/validation.rs index 8d0733b..4e8c200 100644 --- a/src/gas_metering/validation.rs +++ b/src/gas_metering/validation.rs @@ -23,10 +23,10 @@ struct ControlFlowNode { first_instr_pos: Option, /// The actual gas cost of executing all instructions in the basic block. - actual_cost: u32, + actual_cost: u64, /// The amount of gas charged by the injected metering instructions within this basic block. - charged_cost: u32, + charged_cost: u64, /// Whether there are any other nodes in the graph that loop back to this one. Every cycle in /// the control flow graph contains at least one node with this flag set. @@ -68,10 +68,10 @@ impl ControlFlowGraph { } fn increment_actual_cost(&mut self, node_id: NodeId, cost: u32) { - self.get_node_mut(node_id).actual_cost += cost; + self.get_node_mut(node_id).actual_cost += u64::from(cost); } - fn increment_charged_cost(&mut self, node_id: NodeId, cost: u32) { + fn increment_charged_cost(&mut self, node_id: NodeId, cost: u64) { self.get_node_mut(node_id).charged_cost += cost; } @@ -267,9 +267,9 @@ fn validate_graph_gas_costs(graph: &ControlFlowGraph) -> bool { fn visit( graph: &ControlFlowGraph, node_id: NodeId, - mut total_actual: u32, - mut total_charged: u32, - loop_costs: &mut Map, + mut total_actual: u64, + mut total_charged: u64, + loop_costs: &mut Map, ) -> bool { let node = graph.get_node(node_id); diff --git a/tests/expectations/gas/branch.wat b/tests/expectations/gas/branch.wat index ab92e95..a02c7ad 100644 --- a/tests/expectations/gas/branch.wat +++ b/tests/expectations/gas/branch.wat @@ -1,10 +1,10 @@ (module (type (;0;) (func (result i32))) - (type (;1;) (func (param i32))) + (type (;1;) (func (param i64))) (import "env" "gas" (func (;0;) (type 1))) (func $fibonacci_with_break (;1;) (type 0) (result i32) (local i32 i32) - i32.const 13 + i64.const 13 call 0 block ;; label = @1 i32.const 0 @@ -18,7 +18,7 @@ local.set 1 i32.const 1 br_if 0 (;@1;) - i32.const 5 + i64.const 5 call 0 local.get 0 local.get 1 diff --git a/tests/expectations/gas/call.wat b/tests/expectations/gas/call.wat index 7cf6eca..e414e93 100644 --- a/tests/expectations/gas/call.wat +++ b/tests/expectations/gas/call.wat @@ -1,10 +1,10 @@ (module (type (;0;) (func (param i32 i32) (result i32))) - (type (;1;) (func (param i32))) + (type (;1;) (func (param i64))) (import "env" "gas" (func (;0;) (type 1))) (func $add_locals (;1;) (type 0) (param $x i32) (param $y i32) (result i32) (local i32) - i32.const 5 + i64.const 5 call 0 local.get $x local.get $y @@ -13,7 +13,7 @@ local.get 2 ) (func $add (;2;) (type 0) (param i32 i32) (result i32) - i32.const 3 + i64.const 3 call 0 local.get 0 local.get 1 diff --git a/tests/expectations/gas/ifs.wat b/tests/expectations/gas/ifs.wat index 5f23c27..642ea43 100644 --- a/tests/expectations/gas/ifs.wat +++ b/tests/expectations/gas/ifs.wat @@ -1,19 +1,19 @@ (module (type (;0;) (func (param i32) (result i32))) - (type (;1;) (func (param i32))) + (type (;1;) (func (param i64))) (import "env" "gas" (func (;0;) (type 1))) (func (;1;) (type 0) (param i32) (result i32) - i32.const 2 + i64.const 2 call 0 i32.const 1 if (result i32) ;; label = @1 - i32.const 3 + i64.const 3 call 0 local.get 0 i32.const 1 i32.add else - i32.const 2 + i64.const 2 call 0 local.get 0 i32.popcnt diff --git a/tests/expectations/gas/simple.wat b/tests/expectations/gas/simple.wat index d742951..42f0fd6 100644 --- a/tests/expectations/gas/simple.wat +++ b/tests/expectations/gas/simple.wat @@ -1,16 +1,16 @@ (module (type (;0;) (func)) - (type (;1;) (func (param i32))) + (type (;1;) (func (param i64))) (import "env" "gas" (func (;0;) (type 1))) (func (;1;) (type 0) - i32.const 2 + i64.const 2 call 0 i32.const 1 if ;; label = @1 - i32.const 1 + i64.const 1 call 0 loop ;; label = @2 - i32.const 2 + i64.const 2 call 0 i32.const 123 drop @@ -18,7 +18,7 @@ end ) (func (;2;) (type 0) - i32.const 1 + i64.const 1 call 0 block ;; label = @1 end diff --git a/tests/expectations/gas/start.wat b/tests/expectations/gas/start.wat index c05a47c..6fe0e12 100644 --- a/tests/expectations/gas/start.wat +++ b/tests/expectations/gas/start.wat @@ -1,12 +1,12 @@ (module (type (;0;) (func (param i32 i32))) (type (;1;) (func)) - (type (;2;) (func (param i32))) + (type (;2;) (func (param i64))) (import "env" "ext_return" (func $ext_return (;0;) (type 0))) (import "env" "memory" (memory (;0;) 1 1)) (import "env" "gas" (func (;1;) (type 2))) (func $start (;2;) (type 1) - i32.const 4 + i64.const 4 call 1 i32.const 8 i32.const 4