Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rebalance gas cost #2152

Merged
merged 15 commits into from
May 30, 2024
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ and this project adheres to
- cosmwasm-std: Deprecate "compact" serialization of `Binary`, `HexBinary`,
`Checksum` ([#2125])
- cosmwasm-vm: Update wasmer to 4.3.1 ([#2147], [#2153])
- cosmwasm-vm: Rebalance gas costs for cryptographic functions and wasm
instructions. ([#2152])

[#2044]: https://github.com/CosmWasm/cosmwasm/pull/2044
[#2051]: https://github.com/CosmWasm/cosmwasm/pull/2051
Expand All @@ -85,6 +87,7 @@ and this project adheres to
[#2108]: https://github.com/CosmWasm/cosmwasm/pull/2108
[#2125]: https://github.com/CosmWasm/cosmwasm/pull/2125
[#2147]: https://github.com/CosmWasm/cosmwasm/pull/2147
[#2152]: https://github.com/CosmWasm/cosmwasm/pull/2152
[#2153]: https://github.com/CosmWasm/cosmwasm/pull/2153

## [2.0.1] - 2024-04-03
Expand Down
5 changes: 3 additions & 2 deletions contracts/crypto-verify/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@

use cosmwasm_std::{Binary, Response, Uint128};
use cosmwasm_vm::testing::{
instantiate, mock_env, mock_info, mock_instance, query, MockApi, MockQuerier, MockStorage,
instantiate, mock_env, mock_info, mock_instance_with_gas_limit, query, MockApi, MockQuerier,
MockStorage,
};
use cosmwasm_vm::{from_slice, Instance};
use hex_literal::hex;
Expand Down Expand Up @@ -96,7 +97,7 @@ fn build_drand_message(round: u64, previous_signature: &[u8]) -> Vec<u8> {
const DESERIALIZATION_LIMIT: usize = 20_000;

fn setup() -> Instance<MockApi, MockStorage, MockQuerier> {
let mut deps = mock_instance(WASM, &[]);
let mut deps = mock_instance_with_gas_limit(WASM, 10_000_000_000);
let msg = InstantiateMsg {};
let info = mock_info(CREATOR, &[]);
let res: Response = instantiate(&mut deps, mock_env(), info, msg).unwrap();
Expand Down
12 changes: 12 additions & 0 deletions docs/GAS.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@ gas and took 15ms on our CI system. The ideal cost per operation for this system
is `10**12 / (96837752 / (15 / 1000))`: 154. This is rounded to 150 for
simplicity.

CosmWasm 2.1 update: All gas values were re-evaluated and adjusted to meet the 1
Teragas/second target mentioned above. A rerun of the Argon2 test contract
consumed 5270718300 gas with the previous cost of 150, so the operation count
was `5270718300 / 150 = 35138122`. This took 6ms on our benchmark server, so the
new cost per operation is `10**12 / (35138122 / (6 / 1000))`: 171. This is
rounded to 170 for simplicity.

Benchmarking system:

- CPU: Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz (4 cores, 8 threads)
- RAM: 32GB DDR4 2133 MHz

Each machine is different, we know that. But the above target helps us in
multiple ways:

Expand Down
2 changes: 1 addition & 1 deletion packages/crypto/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ and [cosmwasm-std](`https://crates.io/crates/cosmwasm-std`) crates.

```
cd packages/crypto
cargo bench
cargo bench --features std
```

## License
Expand Down
13 changes: 7 additions & 6 deletions packages/crypto/benches/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,12 +157,13 @@ where
.map(|(secret_key, message)| *message * secret_key)
.collect();

for i in 1..=two_pow_max {
let num_points = 2_usize.pow(i);
let messages = &messages[..num_points];
let keys = &public_keys[..num_points];
for i in 0..=two_pow_max {
let n = 2_usize.pow(i); // the number of pairings on the left hand side
let k = n + 1; // the number of pairings in total
let messages = &messages[..n];
let keys = &public_keys[..n];
let aggregated_signature: G2Affine =
signatures[..num_points].iter().sum::<G2Projective>().into();
signatures[..n].iter().sum::<G2Projective>().into();

let serialized_pubkeys: Vec<u8> = keys
.iter()
Expand All @@ -187,7 +188,7 @@ where
.serialize_compressed(&mut serialized_signature[..])
.unwrap();

group.bench_function(format!("bls12_381_pairing_equality_{num_points}"), |b| {
group.bench_function(format!("bls12_381_pairing_equality_k={k}"), |b| {
b.iter(|| {
let is_valid = black_box(bls12_381_pairing_equality(
&serialized_pubkeys,
Expand Down
91 changes: 60 additions & 31 deletions packages/vm/src/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,55 +42,84 @@ pub struct GasConfig {
/// ed25519 signature verification cost
pub ed25519_verify_cost: u64,
/// ed25519 batch signature verification cost
pub ed25519_batch_verify_cost: u64,
pub ed25519_batch_verify_cost: LinearGasCost,
/// ed25519 batch signature verification cost (single public key)
pub ed25519_batch_verify_one_pubkey_cost: u64,
/// bls12-381 aggregate cost per point (g1)
pub bls12_381_aggregate_g1_per_point: u64,
/// bls12-381 aggregate cost per point (g2)
pub bls12_381_aggregate_g2_per_point: u64,
pub ed25519_batch_verify_one_pubkey_cost: LinearGasCost,
/// bls12-381 aggregate cost (g1)
pub bls12_381_aggregate_g1_cost: LinearGasCost,
/// bls12-381 aggregate cost (g2)
pub bls12_381_aggregate_g2_cost: LinearGasCost,
/// bls12-381 hash to g1 cost
pub bls12_381_hash_to_g1_cost: u64,
/// bls12-381 hash to g2 cost
pub bls12_381_hash_to_g2_cost: u64,
/// bls12-381 pairing equality check cost
pub bls12_381_pairing_equality_cost: u64,
/// bls12-381 aggregated pairing equality check cost per point
/// (added on top of the base pairing equality check cost)
pub bls12_381_aggregated_pairing_equality_cost_per_pair: u64,
pub bls12_381_pairing_equality_cost: LinearGasCost,
}

impl Default for GasConfig {
fn default() -> Self {
// Target is 10^12 per second (see GAS.md), i.e. 10^6 gas per µ second.
const GAS_PER_US: u64 = 1_000_000;
Self {
// ~119 us in crypto benchmarks
secp256k1_verify_cost: 119 * GAS_PER_US,
// ~233 us in crypto benchmarks
secp256k1_recover_pubkey_cost: 233 * GAS_PER_US,
// ~374 us in crypto benchmarks
secp256r1_verify_cost: 374 * GAS_PER_US,
// ~834 us in crypto benchmarks
secp256r1_recover_pubkey_cost: 834 * GAS_PER_US,
// ~63 us in crypto benchmarks
ed25519_verify_cost: 63 * GAS_PER_US,
// Gas cost factors, relative to ed25519_verify cost
// From https://docs.rs/ed25519-zebra/2.2.0/ed25519_zebra/batch/index.html
ed25519_batch_verify_cost: 63 * GAS_PER_US / 2,
ed25519_batch_verify_one_pubkey_cost: 63 * GAS_PER_US / 4,
// ~96 us in crypto benchmarks
secp256k1_verify_cost: 96 * GAS_PER_US,
// ~194 us in crypto benchmarks
secp256k1_recover_pubkey_cost: 194 * GAS_PER_US,
// ~279 us in crypto benchmarks
secp256r1_verify_cost: 279 * GAS_PER_US,
// ~592 us in crypto benchmarks
secp256r1_recover_pubkey_cost: 592 * GAS_PER_US,
// ~35 us in crypto benchmarks
ed25519_verify_cost: 35 * GAS_PER_US,
// Calculated based on the benchmark results for `ed25519_batch_verify_{x}`.
ed25519_batch_verify_cost: LinearGasCost {
base: 24 * GAS_PER_US,
per_item: 21 * GAS_PER_US,
},
// Calculated based on the benchmark results for `ed25519_batch_verify_one_pubkey_{x}`.
ed25519_batch_verify_one_pubkey_cost: LinearGasCost {
base: 36 * GAS_PER_US,
per_item: 10 * GAS_PER_US,
},
// just assume the production machines have more than 4 cores, so we can half that
bls12_381_aggregate_g1_per_point: 16 * GAS_PER_US / 2,
bls12_381_aggregate_g2_per_point: 33 * GAS_PER_US / 2,
bls12_381_hash_to_g1_cost: 324 * GAS_PER_US,
bls12_381_hash_to_g2_cost: 528 * GAS_PER_US,
// god i wish i was lying
bls12_381_pairing_equality_cost: 1038 * GAS_PER_US,
bls12_381_aggregated_pairing_equality_cost_per_pair: 108 * GAS_PER_US,
bls12_381_aggregate_g1_cost: LinearGasCost {
base: 136 * GAS_PER_US / 2,
per_item: 24 * GAS_PER_US / 2,
},
bls12_381_aggregate_g2_cost: LinearGasCost {
base: 207 * GAS_PER_US / 2,
per_item: 49 * GAS_PER_US / 2,
},
bls12_381_hash_to_g1_cost: 563 * GAS_PER_US,
bls12_381_hash_to_g2_cost: 871 * GAS_PER_US,
bls12_381_pairing_equality_cost: LinearGasCost {
base: 2112 * GAS_PER_US,
per_item: 163 * GAS_PER_US,
},
}
}
}

/// Linear gas cost model where the cost is linear in the number of items.
///
/// To calculate it, you sample the cost for a few different amounts of items and fit a line to it.
/// Let `b` be that line of best fit. Then `base = b(0)` is the y-intercept and
/// `per_item = b(1) - b(0)` the slope.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct LinearGasCost {
/// This is a flat part of the cost, charged once per batch.
base: u64,
/// This is the cost per item in the batch.
per_item: u64,
}

impl LinearGasCost {
pub fn total_cost(&self, items: u64) -> u64 {
self.base + self.per_item * items
}
}

/** context data **/

#[derive(Clone, PartialEq, Eq, Debug, Default)]
Expand Down
38 changes: 22 additions & 16 deletions packages/vm/src/imports.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
//! Import implementations

use std::cmp::max;
use std::marker::PhantomData;

use cosmwasm_crypto::{
Expand Down Expand Up @@ -253,7 +252,7 @@ const BLS12_381_AGGREGATE_SUCCESS: u32 = 0;
/// Return code (error code) for success when hashing to the curve
const BLS12_381_HASH_TO_CURVE_SUCCESS: u32 = 0;

/// Maximum size of continous points passed to aggregate functions
/// Maximum size of continuous points passed to aggregate functions
const BLS12_381_MAX_AGGREGATE_SIZE: usize = 2 * MI;

/// Maximum size of the message passed to the hash-to-curve functions
Expand All @@ -278,7 +277,9 @@ pub fn do_bls12_381_aggregate_g1<

let estimated_point_count = (g1s.len() / BLS12_381_G1_POINT_LEN) as u64;
let gas_info = GasInfo::with_cost(
data.gas_config.bls12_381_aggregate_g1_per_point * estimated_point_count,
data.gas_config
.bls12_381_aggregate_g1_cost
.total_cost(estimated_point_count),
);
process_gas_info(data, &mut store, gas_info)?;

Expand Down Expand Up @@ -322,7 +323,9 @@ pub fn do_bls12_381_aggregate_g2<

let estimated_point_count = (g2s.len() / BLS12_381_G2_POINT_LEN) as u64;
let gas_info = GasInfo::with_cost(
data.gas_config.bls12_381_aggregate_g2_per_point * estimated_point_count,
data.gas_config
.bls12_381_aggregate_g2_cost
.total_cost(estimated_point_count),
);
process_gas_info(data, &mut store, gas_info)?;

Expand Down Expand Up @@ -369,15 +372,18 @@ pub fn do_bls12_381_pairing_equality<
let r = read_region(&memory, r_ptr, BLS12_381_G1_POINT_LEN)?;
let s = read_region(&memory, s_ptr, BLS12_381_G2_POINT_LEN)?;

let estimated_point_count = (ps.len() / BLS12_381_G1_POINT_LEN) as u64;
let additional_cost = data
.gas_config
.bls12_381_aggregated_pairing_equality_cost_per_pair
// Add one since we do not include any pairs in the base benchmark, and we always need to add one for the `r` and `s` pair.
* (estimated_point_count + 1);
chipshort marked this conversation as resolved.
Show resolved Hide resolved
// The values here are only correct if ps and qs can be divided by the point size.
// They are good enough for gas since we error in `bls12_381_pairing_equality` if the inputs are
// not properly formatted.
let estimated_n = (ps.len() / BLS12_381_G1_POINT_LEN) as u64;
// The number of parings to compute (`n` on the left hand side and `k = n + 1` in total)
let estimated_k = estimated_n + 1;

let gas_info =
GasInfo::with_cost(data.gas_config.bls12_381_pairing_equality_cost + additional_cost);
let gas_info = GasInfo::with_cost(
data.gas_config
.bls12_381_pairing_equality_cost
.total_cost(estimated_k),
);
process_gas_info(data, &mut store, gas_info)?;

let code = match bls12_381_pairing_equality(&ps, &qs, &r, &s) {
Expand Down Expand Up @@ -729,11 +735,11 @@ pub fn do_ed25519_batch_verify<
let public_keys = decode_sections(&public_keys);

let gas_cost = if public_keys.len() == 1 {
data.gas_config.ed25519_batch_verify_one_pubkey_cost
&data.gas_config.ed25519_batch_verify_one_pubkey_cost
} else {
data.gas_config.ed25519_batch_verify_cost
} * signatures.len() as u64;
let gas_info = GasInfo::with_cost(max(gas_cost, data.gas_config.ed25519_verify_cost));
&data.gas_config.ed25519_batch_verify_cost
};
let gas_info = GasInfo::with_cost(gas_cost.total_cost(signatures.len() as u64));
process_gas_info(data, &mut store, gas_info)?;
let result = ed25519_batch_verify(&mut OsRng, &messages, &signatures, &public_keys);
let code = match result {
Expand Down
8 changes: 4 additions & 4 deletions packages/vm/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -914,7 +914,7 @@ mod tests {

let report2 = instance.create_gas_report();
assert_eq!(report2.used_externally, 251);
assert_eq!(report2.used_internally, 8461548);
assert_eq!(report2.used_internally, 12109530);
assert_eq!(report2.limit, LIMIT);
assert_eq!(
report2.remaining,
Expand Down Expand Up @@ -1105,7 +1105,7 @@ mod tests {
.unwrap();

let init_used = orig_gas - instance.get_gas_left();
assert_eq!(init_used, 8461799);
assert_eq!(init_used, 12109781);
}

#[test]
Expand All @@ -1130,7 +1130,7 @@ mod tests {
.unwrap();

let execute_used = gas_before_execute - instance.get_gas_left();
assert_eq!(execute_used, 11181706);
assert_eq!(execute_used, 12658786);
}

#[test]
Expand Down Expand Up @@ -1173,6 +1173,6 @@ mod tests {
);

let query_used = gas_before_query - instance.get_gas_left();
assert_eq!(query_used, 7142556);
assert_eq!(query_used, 8094896);
}
}
9 changes: 7 additions & 2 deletions packages/vm/src/testing/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@ use crate::backend::unwrap_or_return_with_gas;
use crate::{Backend, BackendApi, BackendError, BackendResult, GasInfo};

pub const MOCK_CONTRACT_ADDR: &str = "cosmwasmcontract"; // TODO: use correct address
const GAS_COST_HUMANIZE: u64 = 44; // TODO: these seem very low
const GAS_COST_CANONICALIZE: u64 = 55;
/// Default gas multiplier in wasmd.
/// See https://github.com/CosmWasm/wasmd/blob/v0.51.0/x/wasm/types/gas_register.go#L34
const WASMD_GAS_MULTIPLIER: u64 = 140_000;
/// See https://github.com/CosmWasm/wasmd/blob/v0.51.0/x/wasm/keeper/api.go#L27
const GAS_COST_HUMANIZE: u64 = 4 * WASMD_GAS_MULTIPLIER;
/// See https://github.com/CosmWasm/wasmd/blob/v0.51.0/x/wasm/keeper/api.go#L28
const GAS_COST_CANONICALIZE: u64 = 5 * WASMD_GAS_MULTIPLIER;

/// Default prefix used when creating Bech32 encoded address.
const BECH32_PREFIX: &str = "cosmwasm";
Expand Down
2 changes: 1 addition & 1 deletion packages/vm/src/wasm_backend/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ fn cost(_operator: &Operator) -> u64 {
// In https://github.com/CosmWasm/cosmwasm/pull/1042 a profiler is developed to
// identify runtime differences between different Wasm operation, but this is not yet
// precise enough to derive insights from it.
150
170
}

/// Use Cranelift as the compiler backend if the feature is enabled
Expand Down
Loading