Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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