Skip to content

Commit

Permalink
Merge pull request #3554 from anoma/grarco/rc-gas-update
Browse files Browse the repository at this point in the history
Update gas costs
  • Loading branch information
mergify[bot] authored Aug 9, 2024
2 parents 8573ded + a1be239 commit d264b78
Show file tree
Hide file tree
Showing 13 changed files with 332 additions and 215 deletions.
2 changes: 2 additions & 0 deletions .changelog/unreleased/improvements/3554-rc-gas-update.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Updated the gas costs based on benchmarks ran on v41.
([\#3554](https://github.com/anoma/namada/pull/3554))
15 changes: 7 additions & 8 deletions crates/benches/native_vps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -751,7 +751,7 @@ fn masp_check_convert(c: &mut Criterion) {
}

fn masp_check_output(c: &mut Criterion) {
c.bench_function("masp_vp_check_output", |b| {
c.bench_function("vp_masp_check_output", |b| {
b.iter_batched(
|| {
let (_, _verifiers_from_tx, signed_tx) =
Expand Down Expand Up @@ -977,7 +977,9 @@ fn customize_masp_tx_data(
)
}

// benchmark the cost of validating two signatures in a batch.
// Benchmark the cost of validating two signatures in a batch (two leverage
// multiscalar multiplication speedups). The gas cost per single signature
// verification should be the result of this bench divided by two.
fn masp_batch_signature_verification(c: &mut Criterion) {
let (_, _, tx) = setup_storage_for_masp_verification("unshielding");
let transaction = tx
Expand Down Expand Up @@ -1023,8 +1025,7 @@ fn masp_batch_signature_verification(c: &mut Criterion) {

// Benchmark both one and two proofs and take the difference as the variable
// cost for every proofs. Charge the full cost for the first note and then
// charge the variable cost multiplied by the number of remaining notes and
// divided by the number of cores
// charge the variable cost multiplied by the number of remaining notes
fn masp_batch_spend_proofs_validate(c: &mut Criterion) {
let mut group = c.benchmark_group("masp_batch_spend_proofs_validate");
let PVKs { spend_vk, .. } = preload_verifying_keys();
Expand Down Expand Up @@ -1069,8 +1070,7 @@ fn masp_batch_spend_proofs_validate(c: &mut Criterion) {

// Benchmark both one and two proofs and take the difference as the variable
// cost for every proofs. Charge the full cost for the first note and then
// charge the variable cost multiplied by the number of remaining notes and
// divided by the number of cores
// charge the variable cost multiplied by the number of remaining notes
fn masp_batch_convert_proofs_validate(c: &mut Criterion) {
let mut group = c.benchmark_group("masp_batch_convert_proofs_validate");
let PVKs { convert_vk, .. } = preload_verifying_keys();
Expand Down Expand Up @@ -1115,8 +1115,7 @@ fn masp_batch_convert_proofs_validate(c: &mut Criterion) {

// Benchmark both one and two proofs and take the difference as the variable
// cost for every proofs. Charge the full cost for the first note and then
// charge the variable cost multiplied by the number of remaining notes and
// divided by the number of cores
// charge the variable cost multiplied by the number of remaining notes
fn masp_batch_output_proofs_validate(c: &mut Criterion) {
let mut group = c.benchmark_group("masp_batch_output_proofs_validate");
let PVKs { output_vk, .. } = preload_verifying_keys();
Expand Down
9 changes: 8 additions & 1 deletion crates/benches/process_wrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ fn process_tx(c: &mut Criterion) {
)| {
assert_eq!(
// Assert that the wrapper transaction was valid
// NOTE: this function invovles a loop on the inner txs to
// check that they are allowlisted. The cost of that should
// technically depend on the number of inner txs and should
// be computed at runtime. From some tests though, we can
// see that the cost of that operation is minimale (200
// ns), so we can just approximate it to a constant cost
// included in this benchmark
shell
.check_proposal_tx(
&wrapper,
Expand All @@ -104,5 +111,5 @@ fn process_tx(c: &mut Criterion) {
});
}

criterion_group!(process_wrapper, process_tx);
criterion_group!(process_wrapper, process_tx,);
criterion_main!(process_wrapper);
59 changes: 32 additions & 27 deletions crates/benches/wasm_opcodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,27 @@ use wasm_instrument::parity_wasm::elements::Instruction::*;
use wasm_instrument::parity_wasm::elements::{
BlockType, BrTableData, SignExtInstruction,
};
use wasmer::{imports, Instance, Module, Store, Value};
use wasmer::{imports, Instance, Module, Store};

// Don't reduce this value too much or it will be impossible to see the
// differences in execution times between the diffent instructions
const ITERATIONS: u64 = 10_000;
const ENTRY_POINT: &str = "op";

lazy_static! {
static ref EMPTY_MODULE: String = format!(
r#"
(module
(func $f0 nop)
(func $f1 (result i32) i32.const 1 return)
(table 1 funcref)
(elem (i32.const 0) $f0)
(global $iter (mut i32) (i32.const 0))
(memory 1)
(func (export "{ENTRY_POINT}") (param $local_var i32)"#
);
}

lazy_static! {
static ref WASM_OPTS: Vec<wasm_instrument::parity_wasm::elements::Instruction> = vec![
// Unreachable unconditionally traps, so no need to divide its cost by ITERATIONS because we only execute it once
Expand Down Expand Up @@ -386,18 +400,7 @@ struct WatBuilder {

impl Display for WatBuilder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(
f,
r#"
(module
(func $f0 nop)
(func $f1 (result i32) i32.const 1 return)
(table 1 funcref)
(elem (i32.const 0) $f0)
(global $iter (mut i32) (i32.const 0))
(memory 1)
(func (export "{ENTRY_POINT}") (param $local_var i32)"#
)?;
writeln!(f, r#"{}"#, *EMPTY_MODULE)?;

for _ in 0..ITERATIONS {
writeln!(f, r#"{}"#, self.wat)?;
Expand All @@ -420,20 +423,19 @@ fn get_wasm_store() -> Store {
// An empty wasm module to serve as the base reference for all the other
// instructions since the bigger part of the cost is the function call itself
fn empty_module(c: &mut Criterion) {
let module_wat = format!(
r#"
(module
(func (export "{ENTRY_POINT}") (param $local_var i32))
)
"#,
);
let module_wat = format!(r#"{}))"#, *EMPTY_MODULE);
let mut store = get_wasm_store();
let module = Module::new(&store, module_wat).unwrap();
let instance = Instance::new(&mut store, &module, &imports! {}).unwrap();
let function = instance.exports.get_function(ENTRY_POINT).unwrap();
let function = instance
.exports
.get_function(ENTRY_POINT)
.unwrap()
.typed::<i32, ()>(&store)
.unwrap();

c.bench_function("empty_module", |b| {
b.iter(|| function.call(&mut store, &[Value::I32(0)]).unwrap());
b.iter(|| function.call(&mut store, 0).unwrap());
});
}

Expand All @@ -445,15 +447,18 @@ fn ops(c: &mut Criterion) {
let module = Module::new(&store, builder.to_string()).unwrap();
let instance =
Instance::new(&mut store, &module, &imports! {}).unwrap();
let function = instance.exports.get_function(ENTRY_POINT).unwrap();
let function = instance
.exports
.get_function(ENTRY_POINT)
.unwrap()
.typed::<i32, ()>(&store)
.unwrap();

group.bench_function(format!("{}", builder.instruction), |b| {
if let Unreachable = builder.instruction {
b.iter(|| {
function.call(&mut store, &[Value::I32(0)]).unwrap_err()
});
b.iter(|| function.call(&mut store, 0).unwrap_err());
} else {
b.iter(|| function.call(&mut store, &[Value::I32(0)]).unwrap());
b.iter(|| function.call(&mut store, 0).unwrap());
}
});
}
Expand Down
151 changes: 122 additions & 29 deletions crates/gas/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,74 +50,167 @@ pub enum GasParseError {
Overflow,
}

const COMPILE_GAS_PER_BYTE: u64 = 1_955;
const PARALLEL_GAS_DIVIDER: u64 = 10;
const WASM_CODE_VALIDATION_GAS_PER_BYTE: u64 = 67;
const WRAPPER_TX_VALIDATION_GAS: u64 = 3_245_500;
// RAW GAS COSTS
// ================================================================================
// The raw gas costs exctracted from the benchmarks.
//
const COMPILE_GAS_PER_BYTE_RAW: u64 = 1_664;
const WASM_CODE_VALIDATION_GAS_PER_BYTE_RAW: u64 = 59;
const WRAPPER_TX_VALIDATION_GAS_RAW: u64 = 1_526_700;
// There's no benchmark to calculate the cost of storage occupation, so we
// define it as the cost of storage latency (which is needed for any storage
// operation and it's based on actual execution time), plus the same cost
// multiplied by an arbitrary factor that represents the higher cost of storage
// space as a resource. This way, the storage occupation cost is not completely
// free-floating but it's tied to the other costs
const STORAGE_OCCUPATION_GAS_PER_BYTE: u64 =
PHYSICAL_STORAGE_LATENCY_PER_BYTE * (1 + 10);
const STORAGE_OCCUPATION_GAS_PER_BYTE_RAW: u64 =
PHYSICAL_STORAGE_LATENCY_PER_BYTE_RAW * (1 + 10);
// NOTE: this accounts for the latency of a physical drive access. For read
// accesses we have no way to tell if data was in cache or in storage. Moreover,
// the latency shouldn't really be accounted per single byte but rather per
// storage blob but this would make it more tedious to compute gas in the
// codebase. For these two reasons we just set an arbitrary value (based on
// actual SSDs latency) per byte here
const PHYSICAL_STORAGE_LATENCY_PER_BYTE: u64 = 1_000_000;
const PHYSICAL_STORAGE_LATENCY_PER_BYTE_RAW: u64 = 20;
// This is based on the global average bandwidth
const NETWORK_TRANSMISSION_GAS_PER_BYTE: u64 = 848;

const NETWORK_TRANSMISSION_GAS_PER_BYTE_RAW: u64 = 848;

// The cost of accessing data from memory (both read and write mode), per byte
const MEMORY_ACCESS_GAS_PER_BYTE_RAW: u64 = 39;
// The cost of accessing data from storage, per byte
const STORAGE_ACCESS_GAS_PER_BYTE_RAW: u64 =
93 + PHYSICAL_STORAGE_LATENCY_PER_BYTE_RAW;
// The cost of writing data to storage, per byte
const STORAGE_WRITE_GAS_PER_BYTE_RAW: u64 = MEMORY_ACCESS_GAS_PER_BYTE_RAW
+ 17_583
+ STORAGE_OCCUPATION_GAS_PER_BYTE_RAW;
// The cost of removing data from storage, per byte
const STORAGE_DELETE_GAS_PER_BYTE_RAW: u64 = MEMORY_ACCESS_GAS_PER_BYTE_RAW
+ 17_583
+ PHYSICAL_STORAGE_LATENCY_PER_BYTE_RAW;
// The cost of verifying a single signature of a transaction
const VERIFY_TX_SIG_GAS_RAW: u64 = 435_190;
// The cost for requesting one more page in wasm (64KiB)
const WASM_MEMORY_PAGE_GAS_RAW: u64 =
MEMORY_ACCESS_GAS_PER_BYTE_RAW * 64 * 1_024;
// The cost to validate an Ibc action
const IBC_ACTION_VALIDATE_GAS_RAW: u64 = 290_935;
// The cost to execute an Ibc action
const IBC_ACTION_EXECUTE_GAS_RAW: u64 = 1_685_733;
// The cost of masp sig verification
const MASP_VERIFY_SIG_GAS_RAW: u64 = 1_908_750;
// The fixed cost of spend note verification
const MASP_FIXED_SPEND_GAS_RAW: u64 = 59_521_000;
// The variable cost of spend note verification
const MASP_VARIABLE_SPEND_GAS_RAW: u64 = 9_849_000;
// The fixed cost of convert note verification
const MASP_FIXED_CONVERT_GAS_RAW: u64 = 46_197_000;
// The variable cost of convert note verification
const MASP_VARIABLE_CONVERT_GAS_RAW: u64 = 10_245_000;
// The fixed cost of output note verification
const MASP_FIXED_OUTPUT_GAS_RAW: u64 = 53_439_000;
// The variable cost of output note verification
const MASP_VARIABLE_OUTPUT_GAS_RAW: u64 = 9_710_000;
// The cost to process a masp spend note in the bundle
const MASP_SPEND_CHECK_GAS_RAW: u64 = 405_070;
// The cost to process a masp convert note in the bundle
const MASP_CONVERT_CHECK_GAS_RAW: u64 = 188_590;
// The cost to process a masp output note in the bundle
const MASP_OUTPUT_CHECK_GAS_RAW: u64 = 204_430;
// The cost to run the final masp check in the bundle
const MASP_FINAL_CHECK_GAS_RAW: u64 = 43;
// ================================================================================

// A correction factor for non-WASM-opcodes costs. We can see that the
// gas cost we get for wasm codes (txs and vps) is much greater than what we
// would expect from the benchmarks. This is likely due to some imperfections in
// the injection tool but, most importantly, to the fact that the code we end up
// executing is an optimized version of the one we instrument. Therefore we
// provide this factor to correct the costs of non-WASM gas based on the avarage
// speedup we can observe. NOTE: we should really reduce the gas costs of WASM
// opcodes instead of increasing the gas costs of non-WASM gas, but the former
// would involve some complicated adjustments for host function calls so we
// prefer to go with the latter.
const GAS_COST_CORRECTION: u64 = 5;

// ADJUSTED GAS COSTS
// ================================================================================
// The gas costs adjusted for the correction factor.
//
const PARALLEL_GAS_DIVIDER: u64 = 1;
// The compilation cost is reduced by a factor to compensate for the (most
// likely) presence of the cache
const COMPILE_GAS_PER_BYTE: u64 =
COMPILE_GAS_PER_BYTE_RAW * GAS_COST_CORRECTION / 100;
const WASM_CODE_VALIDATION_GAS_PER_BYTE: u64 =
WASM_CODE_VALIDATION_GAS_PER_BYTE_RAW * GAS_COST_CORRECTION;
const WRAPPER_TX_VALIDATION_GAS: u64 =
WRAPPER_TX_VALIDATION_GAS_RAW * GAS_COST_CORRECTION;
const STORAGE_OCCUPATION_GAS_PER_BYTE: u64 =
STORAGE_OCCUPATION_GAS_PER_BYTE_RAW * GAS_COST_CORRECTION;
const NETWORK_TRANSMISSION_GAS_PER_BYTE: u64 =
NETWORK_TRANSMISSION_GAS_PER_BYTE_RAW * GAS_COST_CORRECTION;
/// The cost of accessing data from memory (both read and write mode), per byte
pub const MEMORY_ACCESS_GAS_PER_BYTE: u64 = 104;
pub const MEMORY_ACCESS_GAS_PER_BYTE: u64 =
MEMORY_ACCESS_GAS_PER_BYTE_RAW * GAS_COST_CORRECTION;
/// The cost of accessing data from storage, per byte
pub const STORAGE_ACCESS_GAS_PER_BYTE: u64 =
163 + PHYSICAL_STORAGE_LATENCY_PER_BYTE;
STORAGE_ACCESS_GAS_PER_BYTE_RAW * GAS_COST_CORRECTION;
/// The cost of writing data to storage, per byte
pub const STORAGE_WRITE_GAS_PER_BYTE: u64 =
MEMORY_ACCESS_GAS_PER_BYTE + 69_634 + STORAGE_OCCUPATION_GAS_PER_BYTE;
STORAGE_WRITE_GAS_PER_BYTE_RAW * GAS_COST_CORRECTION;
/// The cost of removing data from storage, per byte
pub const STORAGE_DELETE_GAS_PER_BYTE: u64 =
MEMORY_ACCESS_GAS_PER_BYTE + 69_634 + PHYSICAL_STORAGE_LATENCY_PER_BYTE;
STORAGE_DELETE_GAS_PER_BYTE_RAW * GAS_COST_CORRECTION;
/// The cost of verifying a single signature of a transaction
pub const VERIFY_TX_SIG_GAS: u64 = 594_290;
pub const VERIFY_TX_SIG_GAS: u64 = VERIFY_TX_SIG_GAS_RAW * GAS_COST_CORRECTION;
/// The cost for requesting one more page in wasm (64KiB)
#[allow(clippy::cast_possible_truncation)] // const in u32 range
pub const WASM_MEMORY_PAGE_GAS: u32 =
MEMORY_ACCESS_GAS_PER_BYTE as u32 * 64 * 1_024;
(WASM_MEMORY_PAGE_GAS_RAW * GAS_COST_CORRECTION) as u32;
/// The cost to validate an Ibc action
pub const IBC_ACTION_VALIDATE_GAS: u64 = 1_472_023;
pub const IBC_ACTION_VALIDATE_GAS: u64 =
IBC_ACTION_VALIDATE_GAS_RAW * GAS_COST_CORRECTION;
/// The cost to execute an Ibc action
pub const IBC_ACTION_EXECUTE_GAS: u64 = 3_678_745;
pub const IBC_ACTION_EXECUTE_GAS: u64 =
IBC_ACTION_EXECUTE_GAS_RAW * GAS_COST_CORRECTION;
/// The cost of masp sig verification
pub const MASP_VERIFY_SIG_GAS: u64 = 5_443_000;
pub const MASP_VERIFY_SIG_GAS: u64 =
MASP_VERIFY_SIG_GAS_RAW * GAS_COST_CORRECTION;
/// The fixed cost of spend note verification
pub const MASP_FIXED_SPEND_GAS: u64 = 87_866_000;
pub const MASP_FIXED_SPEND_GAS: u64 =
MASP_FIXED_SPEND_GAS_RAW * GAS_COST_CORRECTION;
/// The variable cost of spend note verification
pub const MASP_VARIABLE_SPEND_GAS: u64 = 14_384_000;
pub const MASP_VARIABLE_SPEND_GAS: u64 =
MASP_VARIABLE_SPEND_GAS_RAW * GAS_COST_CORRECTION;
/// The fixed cost of convert note verification
pub const MASP_FIXED_CONVERT_GAS: u64 = 70_308_000;
pub const MASP_FIXED_CONVERT_GAS: u64 =
MASP_FIXED_CONVERT_GAS_RAW * GAS_COST_CORRECTION;
/// The variable cost of convert note verification
pub const MASP_VARIABLE_CONVERT_GAS: u64 = 12_664_000;
pub const MASP_VARIABLE_CONVERT_GAS: u64 =
MASP_VARIABLE_CONVERT_GAS_RAW * GAS_COST_CORRECTION;
/// The fixed cost of output note verification
pub const MASP_FIXED_OUTPUT_GAS: u64 = 78_203_000;
pub const MASP_FIXED_OUTPUT_GAS: u64 =
MASP_FIXED_OUTPUT_GAS_RAW * GAS_COST_CORRECTION;
/// The variable cost of output note verification
pub const MASP_VARIABLE_OUTPUT_GAS: u64 = 14_586_000;
pub const MASP_VARIABLE_OUTPUT_GAS: u64 =
MASP_VARIABLE_OUTPUT_GAS_RAW * GAS_COST_CORRECTION;
/// The cost to process a masp spend note in the bundle
pub const MASP_SPEND_CHECK_GAS: u64 = 479_730;
pub const MASP_SPEND_CHECK_GAS: u64 =
MASP_SPEND_CHECK_GAS_RAW * GAS_COST_CORRECTION;
/// The cost to process a masp convert note in the bundle
pub const MASP_CONVERT_CHECK_GAS: u64 = 173_570;
pub const MASP_CONVERT_CHECK_GAS: u64 =
MASP_CONVERT_CHECK_GAS_RAW * GAS_COST_CORRECTION;
/// The cost to process a masp output note in the bundle
pub const MASP_OUTPUT_CHECK_GAS: u64 = 310_260;
pub const MASP_OUTPUT_CHECK_GAS: u64 =
MASP_OUTPUT_CHECK_GAS_RAW * GAS_COST_CORRECTION;
/// The cost to run the final masp check in the bundle
pub const MASP_FINAL_CHECK_GAS: u64 = 44;
pub const MASP_FINAL_CHECK_GAS: u64 =
MASP_FINAL_CHECK_GAS_RAW * GAS_COST_CORRECTION;
/// Gas divider specific for the masp vp. Only allocates half of the cores to
/// the masp vp since we can expect the other half to be busy with other vps
pub const MASP_PARALLEL_GAS_DIVIDER: u64 = PARALLEL_GAS_DIVIDER / 2;
pub const MASP_PARALLEL_GAS_DIVIDER: u64 = 1;
// ================================================================================

/// Gas module result for functions that may fail
pub type Result<T> = std::result::Result<T, Error>;
Expand Down
2 changes: 1 addition & 1 deletion crates/sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ use tx::{
use wallet::{Wallet, WalletIo, WalletStorage};

/// Default gas-limit
pub const DEFAULT_GAS_LIMIT: u64 = 100_000;
pub const DEFAULT_GAS_LIMIT: u64 = 150_000;

#[allow(missing_docs)]
#[cfg(not(feature = "async-send"))]
Expand Down
Loading

0 comments on commit d264b78

Please sign in to comment.