Skip to content

Commit af4b239

Browse files
authored
Merge pull request #2152 from CosmWasm/chipshort/gas-benchmarking
Rebalance gas cost
2 parents 9cd3350 + ebbe201 commit af4b239

File tree

10 files changed

+120
-63
lines changed

10 files changed

+120
-63
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ and this project adheres to
7878
- cosmwasm-std: Deprecate "compact" serialization of `Binary`, `HexBinary`,
7979
`Checksum` ([#2125])
8080
- cosmwasm-vm: Update wasmer to 4.3.1 ([#2147], [#2153])
81+
- cosmwasm-vm: Rebalance gas costs for cryptographic functions and wasm
82+
instructions. ([#2152])
8183

8284
[#2044]: https://github.com/CosmWasm/cosmwasm/pull/2044
8385
[#2051]: https://github.com/CosmWasm/cosmwasm/pull/2051
@@ -88,6 +90,7 @@ and this project adheres to
8890
[#2108]: https://github.com/CosmWasm/cosmwasm/pull/2108
8991
[#2125]: https://github.com/CosmWasm/cosmwasm/pull/2125
9092
[#2147]: https://github.com/CosmWasm/cosmwasm/pull/2147
93+
[#2152]: https://github.com/CosmWasm/cosmwasm/pull/2152
9194
[#2153]: https://github.com/CosmWasm/cosmwasm/pull/2153
9295

9396
## [2.0.1] - 2024-04-03

contracts/crypto-verify/tests/integration.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
2222
use cosmwasm_std::{Binary, Response, Uint128};
2323
use cosmwasm_vm::testing::{
24-
instantiate, mock_env, mock_info, mock_instance, query, MockApi, MockQuerier, MockStorage,
24+
instantiate, mock_env, mock_info, mock_instance_with_gas_limit, query, MockApi, MockQuerier,
25+
MockStorage,
2526
};
2627
use cosmwasm_vm::{from_slice, Instance};
2728
use hex_literal::hex;
@@ -96,7 +97,7 @@ fn build_drand_message(round: u64, previous_signature: &[u8]) -> Vec<u8> {
9697
const DESERIALIZATION_LIMIT: usize = 20_000;
9798

9899
fn setup() -> Instance<MockApi, MockStorage, MockQuerier> {
99-
let mut deps = mock_instance(WASM, &[]);
100+
let mut deps = mock_instance_with_gas_limit(WASM, 10_000_000_000);
100101
let msg = InstantiateMsg {};
101102
let info = mock_info(CREATOR, &[]);
102103
let res: Response = instantiate(&mut deps, mock_env(), info, msg).unwrap();

docs/GAS.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,18 @@ gas and took 15ms on our CI system. The ideal cost per operation for this system
2929
is `10**12 / (96837752 / (15 / 1000))`: 154. This is rounded to 150 for
3030
simplicity.
3131

32+
CosmWasm 2.1 update: All gas values were re-evaluated and adjusted to meet the 1
33+
Teragas/second target mentioned above. A rerun of the Argon2 test contract
34+
consumed 5270718300 gas with the previous cost of 150, so the operation count
35+
was `5270718300 / 150 = 35138122`. This took 6ms on our benchmark server, so the
36+
new cost per operation is `10**12 / (35138122 / (6 / 1000))`: 171. This is
37+
rounded to 170 for simplicity.
38+
39+
Benchmarking system:
40+
41+
- CPU: Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz (4 cores, 8 threads)
42+
- RAM: 32GB DDR4 2133 MHz
43+
3244
Each machine is different, we know that. But the above target helps us in
3345
multiple ways:
3446

packages/crypto/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ and [cosmwasm-std](`https://crates.io/crates/cosmwasm-std`) crates.
2121

2222
```
2323
cd packages/crypto
24-
cargo bench
24+
cargo bench --features std
2525
```
2626

2727
## License

packages/crypto/benches/main.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -157,12 +157,13 @@ where
157157
.map(|(secret_key, message)| *message * secret_key)
158158
.collect();
159159

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

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

190-
group.bench_function(format!("bls12_381_pairing_equality_{num_points}"), |b| {
191+
group.bench_function(format!("bls12_381_pairing_equality_k={k}"), |b| {
191192
b.iter(|| {
192193
let is_valid = black_box(bls12_381_pairing_equality(
193194
&serialized_pubkeys,

packages/vm/src/environment.rs

Lines changed: 60 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -42,55 +42,84 @@ pub struct GasConfig {
4242
/// ed25519 signature verification cost
4343
pub ed25519_verify_cost: u64,
4444
/// ed25519 batch signature verification cost
45-
pub ed25519_batch_verify_cost: u64,
45+
pub ed25519_batch_verify_cost: LinearGasCost,
4646
/// ed25519 batch signature verification cost (single public key)
47-
pub ed25519_batch_verify_one_pubkey_cost: u64,
48-
/// bls12-381 aggregate cost per point (g1)
49-
pub bls12_381_aggregate_g1_per_point: u64,
50-
/// bls12-381 aggregate cost per point (g2)
51-
pub bls12_381_aggregate_g2_per_point: u64,
47+
pub ed25519_batch_verify_one_pubkey_cost: LinearGasCost,
48+
/// bls12-381 aggregate cost (g1)
49+
pub bls12_381_aggregate_g1_cost: LinearGasCost,
50+
/// bls12-381 aggregate cost (g2)
51+
pub bls12_381_aggregate_g2_cost: LinearGasCost,
5252
/// bls12-381 hash to g1 cost
5353
pub bls12_381_hash_to_g1_cost: u64,
5454
/// bls12-381 hash to g2 cost
5555
pub bls12_381_hash_to_g2_cost: u64,
5656
/// bls12-381 pairing equality check cost
57-
pub bls12_381_pairing_equality_cost: u64,
58-
/// bls12-381 aggregated pairing equality check cost per point
59-
/// (added on top of the base pairing equality check cost)
60-
pub bls12_381_aggregated_pairing_equality_cost_per_pair: u64,
57+
pub bls12_381_pairing_equality_cost: LinearGasCost,
6158
}
6259

6360
impl Default for GasConfig {
6461
fn default() -> Self {
6562
// Target is 10^12 per second (see GAS.md), i.e. 10^6 gas per µ second.
6663
const GAS_PER_US: u64 = 1_000_000;
6764
Self {
68-
// ~119 us in crypto benchmarks
69-
secp256k1_verify_cost: 119 * GAS_PER_US,
70-
// ~233 us in crypto benchmarks
71-
secp256k1_recover_pubkey_cost: 233 * GAS_PER_US,
72-
// ~374 us in crypto benchmarks
73-
secp256r1_verify_cost: 374 * GAS_PER_US,
74-
// ~834 us in crypto benchmarks
75-
secp256r1_recover_pubkey_cost: 834 * GAS_PER_US,
76-
// ~63 us in crypto benchmarks
77-
ed25519_verify_cost: 63 * GAS_PER_US,
78-
// Gas cost factors, relative to ed25519_verify cost
79-
// From https://docs.rs/ed25519-zebra/2.2.0/ed25519_zebra/batch/index.html
80-
ed25519_batch_verify_cost: 63 * GAS_PER_US / 2,
81-
ed25519_batch_verify_one_pubkey_cost: 63 * GAS_PER_US / 4,
65+
// ~96 us in crypto benchmarks
66+
secp256k1_verify_cost: 96 * GAS_PER_US,
67+
// ~194 us in crypto benchmarks
68+
secp256k1_recover_pubkey_cost: 194 * GAS_PER_US,
69+
// ~279 us in crypto benchmarks
70+
secp256r1_verify_cost: 279 * GAS_PER_US,
71+
// ~592 us in crypto benchmarks
72+
secp256r1_recover_pubkey_cost: 592 * GAS_PER_US,
73+
// ~35 us in crypto benchmarks
74+
ed25519_verify_cost: 35 * GAS_PER_US,
75+
// Calculated based on the benchmark results for `ed25519_batch_verify_{x}`.
76+
ed25519_batch_verify_cost: LinearGasCost {
77+
base: 24 * GAS_PER_US,
78+
per_item: 21 * GAS_PER_US,
79+
},
80+
// Calculated based on the benchmark results for `ed25519_batch_verify_one_pubkey_{x}`.
81+
ed25519_batch_verify_one_pubkey_cost: LinearGasCost {
82+
base: 36 * GAS_PER_US,
83+
per_item: 10 * GAS_PER_US,
84+
},
8285
// just assume the production machines have more than 4 cores, so we can half that
83-
bls12_381_aggregate_g1_per_point: 16 * GAS_PER_US / 2,
84-
bls12_381_aggregate_g2_per_point: 33 * GAS_PER_US / 2,
85-
bls12_381_hash_to_g1_cost: 324 * GAS_PER_US,
86-
bls12_381_hash_to_g2_cost: 528 * GAS_PER_US,
87-
// god i wish i was lying
88-
bls12_381_pairing_equality_cost: 1038 * GAS_PER_US,
89-
bls12_381_aggregated_pairing_equality_cost_per_pair: 108 * GAS_PER_US,
86+
bls12_381_aggregate_g1_cost: LinearGasCost {
87+
base: 136 * GAS_PER_US / 2,
88+
per_item: 24 * GAS_PER_US / 2,
89+
},
90+
bls12_381_aggregate_g2_cost: LinearGasCost {
91+
base: 207 * GAS_PER_US / 2,
92+
per_item: 49 * GAS_PER_US / 2,
93+
},
94+
bls12_381_hash_to_g1_cost: 563 * GAS_PER_US,
95+
bls12_381_hash_to_g2_cost: 871 * GAS_PER_US,
96+
bls12_381_pairing_equality_cost: LinearGasCost {
97+
base: 2112 * GAS_PER_US,
98+
per_item: 163 * GAS_PER_US,
99+
},
90100
}
91101
}
92102
}
93103

104+
/// Linear gas cost model where the cost is linear in the number of items.
105+
///
106+
/// To calculate it, you sample the cost for a few different amounts of items and fit a line to it.
107+
/// Let `b` be that line of best fit. Then `base = b(0)` is the y-intercept and
108+
/// `per_item = b(1) - b(0)` the slope.
109+
#[derive(Clone, PartialEq, Eq, Debug)]
110+
pub struct LinearGasCost {
111+
/// This is a flat part of the cost, charged once per batch.
112+
base: u64,
113+
/// This is the cost per item in the batch.
114+
per_item: u64,
115+
}
116+
117+
impl LinearGasCost {
118+
pub fn total_cost(&self, items: u64) -> u64 {
119+
self.base + self.per_item * items
120+
}
121+
}
122+
94123
/** context data **/
95124

96125
#[derive(Clone, PartialEq, Eq, Debug, Default)]

packages/vm/src/imports.rs

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
//! Import implementations
22
3-
use std::cmp::max;
43
use std::marker::PhantomData;
54

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

256-
/// Maximum size of continous points passed to aggregate functions
255+
/// Maximum size of continuous points passed to aggregate functions
257256
const BLS12_381_MAX_AGGREGATE_SIZE: usize = 2 * MI;
258257

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

279278
let estimated_point_count = (g1s.len() / BLS12_381_G1_POINT_LEN) as u64;
280279
let gas_info = GasInfo::with_cost(
281-
data.gas_config.bls12_381_aggregate_g1_per_point * estimated_point_count,
280+
data.gas_config
281+
.bls12_381_aggregate_g1_cost
282+
.total_cost(estimated_point_count),
282283
);
283284
process_gas_info(data, &mut store, gas_info)?;
284285

@@ -322,7 +323,9 @@ pub fn do_bls12_381_aggregate_g2<
322323

323324
let estimated_point_count = (g2s.len() / BLS12_381_G2_POINT_LEN) as u64;
324325
let gas_info = GasInfo::with_cost(
325-
data.gas_config.bls12_381_aggregate_g2_per_point * estimated_point_count,
326+
data.gas_config
327+
.bls12_381_aggregate_g2_cost
328+
.total_cost(estimated_point_count),
326329
);
327330
process_gas_info(data, &mut store, gas_info)?;
328331

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

372-
let estimated_point_count = (ps.len() / BLS12_381_G1_POINT_LEN) as u64;
373-
let additional_cost = data
374-
.gas_config
375-
.bls12_381_aggregated_pairing_equality_cost_per_pair
376-
// 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.
377-
* (estimated_point_count + 1);
375+
// The values here are only correct if ps and qs can be divided by the point size.
376+
// They are good enough for gas since we error in `bls12_381_pairing_equality` if the inputs are
377+
// not properly formatted.
378+
let estimated_n = (ps.len() / BLS12_381_G1_POINT_LEN) as u64;
379+
// The number of parings to compute (`n` on the left hand side and `k = n + 1` in total)
380+
let estimated_k = estimated_n + 1;
378381

379-
let gas_info =
380-
GasInfo::with_cost(data.gas_config.bls12_381_pairing_equality_cost + additional_cost);
382+
let gas_info = GasInfo::with_cost(
383+
data.gas_config
384+
.bls12_381_pairing_equality_cost
385+
.total_cost(estimated_k),
386+
);
381387
process_gas_info(data, &mut store, gas_info)?;
382388

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

731737
let gas_cost = if public_keys.len() == 1 {
732-
data.gas_config.ed25519_batch_verify_one_pubkey_cost
738+
&data.gas_config.ed25519_batch_verify_one_pubkey_cost
733739
} else {
734-
data.gas_config.ed25519_batch_verify_cost
735-
} * signatures.len() as u64;
736-
let gas_info = GasInfo::with_cost(max(gas_cost, data.gas_config.ed25519_verify_cost));
740+
&data.gas_config.ed25519_batch_verify_cost
741+
};
742+
let gas_info = GasInfo::with_cost(gas_cost.total_cost(signatures.len() as u64));
737743
process_gas_info(data, &mut store, gas_info)?;
738744
let result = ed25519_batch_verify(&mut OsRng, &messages, &signatures, &public_keys);
739745
let code = match result {

packages/vm/src/instance.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -914,7 +914,7 @@ mod tests {
914914

915915
let report2 = instance.create_gas_report();
916916
assert_eq!(report2.used_externally, 251);
917-
assert_eq!(report2.used_internally, 8461548);
917+
assert_eq!(report2.used_internally, 12109530);
918918
assert_eq!(report2.limit, LIMIT);
919919
assert_eq!(
920920
report2.remaining,
@@ -1105,7 +1105,7 @@ mod tests {
11051105
.unwrap();
11061106

11071107
let init_used = orig_gas - instance.get_gas_left();
1108-
assert_eq!(init_used, 8461799);
1108+
assert_eq!(init_used, 12109781);
11091109
}
11101110

11111111
#[test]
@@ -1130,7 +1130,7 @@ mod tests {
11301130
.unwrap();
11311131

11321132
let execute_used = gas_before_execute - instance.get_gas_left();
1133-
assert_eq!(execute_used, 11181706);
1133+
assert_eq!(execute_used, 12658786);
11341134
}
11351135

11361136
#[test]
@@ -1173,6 +1173,6 @@ mod tests {
11731173
);
11741174

11751175
let query_used = gas_before_query - instance.get_gas_left();
1176-
assert_eq!(query_used, 7142556);
1176+
assert_eq!(query_used, 8094896);
11771177
}
11781178
}

packages/vm/src/testing/mock.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,13 @@ use crate::backend::unwrap_or_return_with_gas;
1111
use crate::{Backend, BackendApi, BackendError, BackendResult, GasInfo};
1212

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

1722
/// Default prefix used when creating Bech32 encoded address.
1823
const BECH32_PREFIX: &str = "cosmwasm";

packages/vm/src/wasm_backend/engine.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ fn cost(_operator: &Operator) -> u64 {
2323
// In https://github.com/CosmWasm/cosmwasm/pull/1042 a profiler is developed to
2424
// identify runtime differences between different Wasm operation, but this is not yet
2525
// precise enough to derive insights from it.
26-
150
26+
170
2727
}
2828

2929
/// Use Cranelift as the compiler backend if the feature is enabled

0 commit comments

Comments
 (0)