diff --git a/CHANGELOG.md b/CHANGELOG.md index bc946061db..a5fd2f8b48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 @@ -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 diff --git a/contracts/crypto-verify/tests/integration.rs b/contracts/crypto-verify/tests/integration.rs index 0c9c380b72..9107228305 100644 --- a/contracts/crypto-verify/tests/integration.rs +++ b/contracts/crypto-verify/tests/integration.rs @@ -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; @@ -96,7 +97,7 @@ fn build_drand_message(round: u64, previous_signature: &[u8]) -> Vec { const DESERIALIZATION_LIMIT: usize = 20_000; fn setup() -> Instance { - 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(); diff --git a/docs/GAS.md b/docs/GAS.md index 2a7b25caa2..873304e960 100644 --- a/docs/GAS.md +++ b/docs/GAS.md @@ -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: diff --git a/packages/crypto/README.md b/packages/crypto/README.md index 180fcb07ab..3443bddac9 100644 --- a/packages/crypto/README.md +++ b/packages/crypto/README.md @@ -21,7 +21,7 @@ and [cosmwasm-std](`https://crates.io/crates/cosmwasm-std`) crates. ``` cd packages/crypto -cargo bench +cargo bench --features std ``` ## License diff --git a/packages/crypto/benches/main.rs b/packages/crypto/benches/main.rs index a3af20d839..afc64cbf13 100644 --- a/packages/crypto/benches/main.rs +++ b/packages/crypto/benches/main.rs @@ -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::().into(); + signatures[..n].iter().sum::().into(); let serialized_pubkeys: Vec = keys .iter() @@ -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, diff --git a/packages/vm/src/environment.rs b/packages/vm/src/environment.rs index e30a0877e2..d3e0a5b538 100644 --- a/packages/vm/src/environment.rs +++ b/packages/vm/src/environment.rs @@ -42,22 +42,19 @@ 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 { @@ -65,32 +62,64 @@ impl Default for GasConfig { // 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)] diff --git a/packages/vm/src/imports.rs b/packages/vm/src/imports.rs index ef86c276db..115cd5f33c 100644 --- a/packages/vm/src/imports.rs +++ b/packages/vm/src/imports.rs @@ -1,6 +1,5 @@ //! Import implementations -use std::cmp::max; use std::marker::PhantomData; use cosmwasm_crypto::{ @@ -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 @@ -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)?; @@ -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)?; @@ -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); + // 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) { @@ -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 { diff --git a/packages/vm/src/instance.rs b/packages/vm/src/instance.rs index 08082d7ee8..4dfe1f00e5 100644 --- a/packages/vm/src/instance.rs +++ b/packages/vm/src/instance.rs @@ -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, @@ -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] @@ -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] @@ -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); } } diff --git a/packages/vm/src/testing/mock.rs b/packages/vm/src/testing/mock.rs index fd5bdd1eb6..9260b69941 100644 --- a/packages/vm/src/testing/mock.rs +++ b/packages/vm/src/testing/mock.rs @@ -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"; diff --git a/packages/vm/src/wasm_backend/engine.rs b/packages/vm/src/wasm_backend/engine.rs index 4cf58e6f88..7b2f6190a9 100644 --- a/packages/vm/src/wasm_backend/engine.rs +++ b/packages/vm/src/wasm_backend/engine.rs @@ -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