Skip to content

Commit 11dcd5b

Browse files
committed
add gas calibration harness for events.
1 parent af0b324 commit 11dcd5b

File tree

8 files changed

+166
-17
lines changed

8 files changed

+166
-17
lines changed

fvm/src/kernel/default.rs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -985,18 +985,20 @@ where
985985
.call_manager
986986
.charge_gas(self.call_manager.price_list().on_actor_event_validate(len))?;
987987

988-
let actor_evt = match panic::catch_unwind(|| {
989-
fvm_ipld_encoding::from_slice(raw_evt).or_error(ErrorNumber::IllegalArgument)
990-
}) {
991-
Ok(v) => v,
992-
Err(e) => {
993-
log::error!("panic when decoding cbor from actor: {:?}", e);
994-
Err(syscall_error!(IllegalArgument; "panic when decoding cbor from actor").into())
995-
}
988+
let actor_evt = {
989+
let res = match panic::catch_unwind(|| {
990+
fvm_ipld_encoding::from_slice(raw_evt).or_error(ErrorNumber::IllegalArgument)
991+
}) {
992+
Ok(v) => v,
993+
Err(e) => {
994+
log::error!("panic when decoding event cbor from actor: {:?}", e);
995+
Err(syscall_error!(IllegalArgument; "panic when decoding event cbor from actor").into())
996+
}
997+
};
998+
t.stop();
999+
res
9961000
}?;
9971001

998-
t.stop();
999-
10001002
let t = self.call_manager.charge_gas(
10011003
self.call_manager
10021004
.price_list()

fvm/src/syscalls/event.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ pub fn emit_event(
1818
event_off: u32, // ActorEvent
1919
event_len: u32,
2020
) -> Result<()> {
21+
// Disable the limits for gas calibration.
22+
#[cfg(not(feature = "gas_calibration"))]
23+
{
24+
const MAX_CBOR_EVENT_LEN: u32 = 1024;
25+
if event_len > MAX_CBOR_EVENT_LEN {
26+
return Err(crate::syscall_error!(IllegalArgument; "exceeded maximum event length ({} bytes); got {}", MAX_CBOR_EVENT_LEN, event_len).into());
27+
}
28+
}
29+
2130
let raw = context.memory.try_slice(event_off, event_len)?;
2231
context.kernel.emit_event(raw)
2332
}

testing/calibration/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ authors = ["Protocol Labs", "Filecoin Core Devs"]
88
repository = "https://github.com/filecoin-project/ref-fvm"
99

1010
[dependencies]
11-
fvm = { version = "3.0.0-alpha.22", path = "../../fvm", default-features = false, features = ["testing"] }
11+
fvm = { version = "3.0.0-alpha.22", path = "../../fvm", default-features = false, features = ["testing", "gas_calibration"] }
1212
fvm_shared = { version = "3.0.0-alpha.18", path = "../../shared", features = ["testing"] }
1313
fvm_ipld_car = { version = "0.6.0", path = "../../ipld/car" }
1414
fvm_ipld_blockstore = { version = "0.1.1", path = "../../ipld/blockstore" }

testing/calibration/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ The calibration uses the machinery from the integration tests, but it's kept sep
1919

2020
Note that the `--release` flag has a huge impact on runtimes and therefore the model paramters, in the order of 100x.
2121

22-
Alternatively all the scenarios and exports can be executed the followign way:
22+
Alternatively all the scenarios and exports can be executed the following way:
2323

2424
```shell
2525
make run

testing/calibration/contract/fil-gas-calibration-actor/src/lib.rs

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1+
use std::num::Wrapping;
2+
13
// Copyright 2021-2023 Protocol Labs
24
// SPDX-License-Identifier: Apache-2.0, MIT
35
use anyhow::{anyhow, Result};
46
use cid::multihash::Code;
5-
use fvm_ipld_encoding::DAG_CBOR;
7+
use fvm_ipld_encoding::{RawBytes, DAG_CBOR};
68
use fvm_sdk::message::params_raw;
79
use fvm_sdk::vm::abort;
810
use fvm_shared::address::{Address, Protocol};
911
use fvm_shared::crypto::hash::SupportedHashes;
1012
use fvm_shared::crypto::signature::{Signature, SignatureType, SECP_SIG_LEN};
1113
use fvm_shared::econ::TokenAmount;
1214
use fvm_shared::error::ExitCode;
15+
use fvm_shared::event::{ActorEvent, Entry, Flags};
1316
use fvm_shared::sys::SendFlags;
1417
use num_derive::FromPrimitive;
1518
use num_traits::FromPrimitive;
@@ -35,6 +38,8 @@ pub enum Method {
3538
OnRecoverSecpPublicKey,
3639
/// Measure sends
3740
OnSend,
41+
/// Emit events variying the number of entries, and with variable lengths for keys and values.
42+
OnEvent,
3843
}
3944

4045
#[derive(Serialize, Deserialize)]
@@ -76,6 +81,18 @@ pub struct OnRecoverSecpPublicKeyParams {
7681
pub seed: u64,
7782
}
7883

84+
#[derive(Serialize, Deserialize)]
85+
pub struct OnEventParams {
86+
pub iterations: usize,
87+
/// Number of entries in the event.
88+
pub entries: usize,
89+
/// Target size of the CBOR object.
90+
pub target_size: usize,
91+
/// Flags to apply to all entries.
92+
pub flags: Flags,
93+
pub seed: u64,
94+
}
95+
7996
#[derive(Serialize, Deserialize)]
8097
pub struct OnSendParams {
8198
pub iterations: usize,
@@ -125,6 +142,7 @@ fn dispatch(method: Method, params_ptr: u32) -> Result<()> {
125142
Method::OnVerifySignature => dispatch_to(on_verify_signature, params_ptr),
126143
Method::OnRecoverSecpPublicKey => dispatch_to(on_recover_secp_public_key, params_ptr),
127144
Method::OnSend => dispatch_to(on_send, params_ptr),
145+
Method::OnEvent => dispatch_to(on_event, params_ptr),
128146
}
129147
}
130148

@@ -193,6 +211,38 @@ fn on_verify_signature(p: OnVerifySignatureParams) -> Result<()> {
193211
Ok(())
194212
}
195213

214+
fn on_event(p: OnEventParams) -> Result<()> {
215+
// Deduct the approximate overhead of each entry (3 bytes) + flag (1 byte). This
216+
// is fuzzy because the size of the encoded CBOR depends on the length of fields, but it's good enough.
217+
let size_per_entry =
218+
((p.target_size.checked_sub(p.entries * 4).unwrap_or(1)) / p.entries).max(1);
219+
let mut rand = lcg64(p.seed);
220+
for _ in 0..p.iterations {
221+
let mut entries = Vec::with_capacity(p.entries);
222+
for _ in 0..p.entries {
223+
let (r1, r2, r3) = (
224+
rand.next().unwrap(),
225+
rand.next().unwrap(),
226+
rand.next().unwrap(),
227+
);
228+
// Generate a random key of an arbitrary length that fits within the size per entry.
229+
// This will never be equal to size_per_entry, and it might be zero, which is fine
230+
// for gas calculation purposes.
231+
let key = random_ascii_string((r1 % size_per_entry as u64) as usize, r2);
232+
// Generate a value to fill up the remaining bytes.
233+
let value = random_bytes(size_per_entry - key.len(), r3);
234+
entries.push(Entry {
235+
flags: p.flags,
236+
key,
237+
value: RawBytes::from(value),
238+
})
239+
}
240+
fvm_sdk::event::emit_event(&ActorEvent::from(entries))?;
241+
}
242+
243+
Ok(())
244+
}
245+
196246
fn on_recover_secp_public_key(p: OnRecoverSecpPublicKeyParams) -> Result<()> {
197247
let mut data = random_bytes(p.size, p.seed);
198248
let sig: [u8; SECP_SIG_LEN] = p
@@ -244,14 +294,22 @@ fn random_mutations(data: &mut Vec<u8>, seed: u64, n: usize) {
244294
}
245295
}
246296

297+
/// Generates a random string in the 0x20 - 0x7e ASCII character range
298+
/// (alphanumeric + symbols, excluding the delete symbol).
299+
fn random_ascii_string(n: usize, seed: u64) -> String {
300+
let bytes = lcg64(seed).map(|x| ((x % 95) + 32) as u8).take(n).collect();
301+
String::from_utf8(bytes).unwrap()
302+
}
303+
247304
/// Knuth's quick and dirty random number generator.
248305
/// https://en.wikipedia.org/wiki/Linear_congruential_generator
249-
fn lcg64(mut seed: u64) -> impl Iterator<Item = u64> {
306+
fn lcg64(initial_seed: u64) -> impl Iterator<Item = u64> {
250307
let a = 6364136223846793005;
251308
let c = 1442695040888963407;
309+
let mut seed = Wrapping(initial_seed);
252310
std::iter::repeat_with(move || {
253-
seed = a * seed + c;
254-
seed
311+
seed = Wrapping(a) * seed + Wrapping(c);
312+
seed.0
255313
})
256314
}
257315

testing/calibration/src/bin/on_block.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ fn main() {
4747
if charge.name.starts_with("OnBlock") {
4848
if let Some(t) = charge.elapsed.get() {
4949
let ob = Obs {
50+
charge: charge.name.to_string(),
5051
label: "n/a".into(),
5152
elapsed_nanos: t.as_nanos(),
5253
variables: vec![*size],
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright 2021-2023 Protocol Labs
2+
// SPDX-License-Identifier: Apache-2.0, MIT
3+
#![feature(slice_group_by)]
4+
5+
use std::usize;
6+
7+
use fil_gas_calibration_actor::{Method, OnEventParams};
8+
use fvm_gas_calibration::*;
9+
use fvm_shared::event::Flags;
10+
use rand::{thread_rng, Rng};
11+
12+
const CHARGE_VALIDATE: &str = "OnActorEventValidate";
13+
const CHARGE_ACCEPT: &str = "OnActorEventAccept";
14+
const METHOD: Method = Method::OnEvent;
15+
16+
fn main() {
17+
let mut config: Vec<(usize, usize)> = vec![];
18+
// 1 entry, ranging 8..1024 bytes
19+
config.extend((3u32..=10).map(|n| (1usize, u64::pow(2, n) as usize)));
20+
// 2 entry, ranging 16..1024 bytes
21+
config.extend((4u32..=10).map(|n| (2usize, u64::pow(2, n) as usize)));
22+
// 4 entries, ranging 32..1024 bytes
23+
config.extend((5u32..=10).map(|n| (4usize, u64::pow(2, n) as usize)));
24+
// 8 entries, ranging 64..1024 bytes
25+
config.extend((6u32..=10).map(|n| (8usize, u64::pow(2, n) as usize)));
26+
// 16 entries, ranging 128..1024 bytes
27+
config.extend((7u32..=10).map(|n| (16usize, u64::pow(2, n) as usize)));
28+
// 32 entries, ranging 256..1024 bytes
29+
config.extend((8u32..=10).map(|n| (32usize, u64::pow(2, n) as usize)));
30+
// 64 entries, ranging 512..1024 bytes
31+
config.extend((9u32..=10).map(|n| (64usize, u64::pow(2, n) as usize)));
32+
33+
let iterations = 500;
34+
35+
let (mut validate_obs, mut accept_obs) = (Vec::new(), Vec::new());
36+
37+
let mut te = instantiate_tester();
38+
39+
let mut rng = thread_rng();
40+
41+
for (entries, target_size) in config.iter() {
42+
let label = format!("{entries:?}entries");
43+
let params = OnEventParams {
44+
iterations,
45+
// number of entries to emit
46+
entries: *entries,
47+
// target size of the encoded CBOR; this is approximate.
48+
target_size: *target_size,
49+
flags: Flags::FLAG_INDEXED_ALL,
50+
seed: rng.gen(),
51+
};
52+
53+
let ret = te.execute_or_die(METHOD as u64, &params);
54+
55+
{
56+
let mut series =
57+
collect_obs(ret.clone(), CHARGE_VALIDATE, &label, *target_size as usize);
58+
series = eliminate_outliers(series, 0.02, Eliminate::Top);
59+
validate_obs.extend(series);
60+
};
61+
62+
{
63+
let mut series = collect_obs(ret.clone(), CHARGE_ACCEPT, &label, *target_size as usize);
64+
series = eliminate_outliers(series, 0.02, Eliminate::Top);
65+
accept_obs.extend(series);
66+
};
67+
}
68+
69+
for (obs, name) in vec![(validate_obs, CHARGE_VALIDATE), (accept_obs, CHARGE_ACCEPT)].iter() {
70+
let regression = obs
71+
.group_by(|a, b| a.label == b.label)
72+
.map(|g| least_squares(g[0].label.to_owned(), g, 0))
73+
.collect::<Vec<_>>();
74+
75+
export(name, &obs, &regression).unwrap();
76+
}
77+
}

testing/calibration/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use num_traits::Zero;
2424
use serde::Serialize;
2525

2626
pub const WASM_COMPILED_PATH: &str =
27-
"../../target/debug/wbuild/fil_gas_calibration_actor/fil_gas_calibration_actor.compact.wasm";
27+
"../../target/release/wbuild/fil_gas_calibration_actor/fil_gas_calibration_actor.compact.wasm";
2828

2929
pub const ENOUGH_GAS: Gas = Gas::new(1_000_000_000);
3030

@@ -88,6 +88,7 @@ lazy_static! {
8888
/// to model time in terms of some variables.
8989
#[derive(Serialize)]
9090
pub struct Obs {
91+
pub charge: String,
9192
pub label: String,
9293
pub elapsed_nanos: u128,
9394
pub variables: Vec<usize>,
@@ -257,6 +258,7 @@ pub fn collect_obs(ret: &ApplyRet, name: &str, label: &str, size: usize) -> Vec<
257258
.iter()
258259
.filter_map(|t| match t {
259260
ExecutionEvent::GasCharge(charge) if charge.name == name => Some(Obs {
261+
charge: charge.name.to_string(),
260262
label: label.to_owned(),
261263
elapsed_nanos: charge.elapsed.get().unwrap().as_nanos(),
262264
variables: vec![size],

0 commit comments

Comments
 (0)