From 08b58d5b12ad30d88120f10f62f2000f70b59692 Mon Sep 17 00:00:00 2001 From: crypto523 Date: Wed, 22 Nov 2023 06:46:27 -0800 Subject: [PATCH] Sanity-check `gtf` opcode (#1503) https://github.com/FuelLabs/fuel-core/issues/1499 vm benches: ``` gtf/gtf time: [208.43 ns 208.99 ns 209.60 ns] ``` sanity benches: ``` block target estimation/other/gtf time: [65.877 ms 66.094 ms 66.323 ms] ``` --------- Co-authored-by: xgreenx --- CHANGELOG.md | 1 + benches/benches/block_target_gas.rs | 238 +++++++++--------- benches/benches/block_target_gas_set/other.rs | 27 +- benches/benches/vm_set/alu.rs | 8 +- benches/src/lib.rs | 36 +++ 5 files changed, 175 insertions(+), 135 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8f5f681..1df760c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Description of the upcoming release here. ### Added +- [#1503](https://github.com/FuelLabs/fuel-core/pull/1503): Add `gtf` opcode sanity check - [#1502](https://github.com/FuelLabs/fuel-core/pull/1502): Added price benchmark for `vm_initialization`. - [#1492](https://github.com/FuelLabs/fuel-core/pull/1492): Support backward iteration in the RocksDB. It allows backward queries that were not allowed before. - [#1490](https://github.com/FuelLabs/fuel-core/pull/1490): Add push and pop benchmarks. diff --git a/benches/benches/block_target_gas.rs b/benches/benches/block_target_gas.rs index d14daee0..e4493caf 100644 --- a/benches/benches/block_target_gas.rs +++ b/benches/benches/block_target_gas.rs @@ -88,35 +88,58 @@ const BASE: u64 = 100_000; pub struct SanityBenchmarkRunnerBuilder; -pub struct SharedSanityBenchmarkFactory { +pub struct SharedSanityBenchmarkRunnerBuilder { service: FuelService, rt: tokio::runtime::Runtime, contract_id: ContractId, rng: rand::rngs::StdRng, } +pub struct MultiContractBenchmarkRunnerBuilder { + service: FuelService, + rt: tokio::runtime::Runtime, + contract_ids: Vec, + rng: rand::rngs::StdRng, +} + impl SanityBenchmarkRunnerBuilder { /// Creates a factory for benchmarks that share a service with a contract, `contract_id`, pre- /// deployed. - pub fn new_shared(contract_id: ContractId) -> SharedSanityBenchmarkFactory { + /// The size of the database can be overridden with the `STATE_SIZE` environment variable. + pub fn new_shared(contract_id: ContractId) -> SharedSanityBenchmarkRunnerBuilder { let state_size = crate::utils::get_state_size(); - let (service, rt) = service_with_contract_id(state_size, contract_id); + let (service, rt) = service_with_many_contracts(state_size, vec![contract_id]); let rng = rand::rngs::StdRng::seed_from_u64(2322u64); - SharedSanityBenchmarkFactory { + SharedSanityBenchmarkRunnerBuilder { service, rt, contract_id, rng, } } + + pub fn new_with_many_contracts( + contract_ids: Vec, + ) -> MultiContractBenchmarkRunnerBuilder { + let state_size = 1000; // Arbitrary small state size + let (service, rt) = service_with_many_contracts(state_size, contract_ids.clone()); + let rng = rand::rngs::StdRng::seed_from_u64(2322u64); + MultiContractBenchmarkRunnerBuilder { + service, + rt, + contract_ids, + rng, + } + } } -impl SharedSanityBenchmarkFactory { - fn build(&mut self) -> SanityBenchmark { - SanityBenchmark { +impl SharedSanityBenchmarkRunnerBuilder { + fn build(&mut self) -> SanityBenchmarkRunner { + SanityBenchmarkRunner { service: &mut self.service, rt: &self.rt, rng: &mut self.rng, + contract_ids: vec![self.contract_id], extra_inputs: vec![], extra_outputs: vec![], } @@ -125,7 +148,7 @@ impl SharedSanityBenchmarkFactory { pub fn build_with_new_contract( &mut self, contract_instructions: Vec, - ) -> SanityBenchmark { + ) -> SanityBenchmarkRunner { replace_contract_in_service( &mut self.service, &self.contract_id, @@ -135,15 +158,29 @@ impl SharedSanityBenchmarkFactory { } } -pub struct SanityBenchmark<'a> { +impl MultiContractBenchmarkRunnerBuilder { + pub fn build(&mut self) -> SanityBenchmarkRunner { + SanityBenchmarkRunner { + service: &mut self.service, + rt: &self.rt, + rng: &mut self.rng, + contract_ids: self.contract_ids.clone(), + extra_inputs: vec![], + extra_outputs: vec![], + } + } +} + +pub struct SanityBenchmarkRunner<'a> { service: &'a mut FuelService, rt: &'a tokio::runtime::Runtime, rng: &'a mut rand::rngs::StdRng, + contract_ids: Vec, extra_inputs: Vec, extra_outputs: Vec, } -impl<'a> SanityBenchmark<'a> { +impl<'a> SanityBenchmarkRunner<'a> { pub fn with_extra_inputs(mut self, extra_inputs: Vec) -> Self { self.extra_inputs = extra_inputs; self @@ -167,7 +204,7 @@ impl<'a> SanityBenchmark<'a> { script, script_data, self.service, - VmBench::CONTRACT, + self.contract_ids, self.rt, self.rng, self.extra_inputs, @@ -182,88 +219,29 @@ fn run( script: Vec, script_data: Vec, ) { - group.bench_function(id, |b| { - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap(); - let _drop = rt.enter(); - - let database = Database::rocksdb(); - let mut config = Config::local_node(); - config.chain_conf.consensus_parameters.tx_params.max_gas_per_tx = TARGET_BLOCK_GAS_LIMIT; - config - .chain_conf - .consensus_parameters - .predicate_params - .max_gas_per_predicate = TARGET_BLOCK_GAS_LIMIT; - config.chain_conf.block_gas_limit = TARGET_BLOCK_GAS_LIMIT; - config.chain_conf.consensus_parameters.gas_costs = GasCosts::new(default_gas_costs()); - config.chain_conf.consensus_parameters.fee_params.gas_per_byte = 0; - config.utxo_validation = false; - config.block_production = Trigger::Instant; - - let service = fuel_core::service::FuelService::new(database, config.clone()) - .expect("Unable to start a FuelService"); - service.start().expect("Unable to start the service"); - let mut rng = rand::rngs::StdRng::seed_from_u64(2322u64); - - b.to_async(&rt).iter(|| { - let shared = service.shared.clone(); - - let tx = fuel_core_types::fuel_tx::TransactionBuilder::script( - script.clone().into_iter().collect(), - script_data.clone(), - ) - .script_gas_limit(TARGET_BLOCK_GAS_LIMIT - BASE) - .gas_price(1) - .add_unsigned_coin_input( - SecretKey::random(&mut rng), - rng.gen(), - u64::MAX / 2, - AssetId::BASE, - Default::default(), - Default::default(), - ) - .finalize_as_transaction(); - async move { - let tx_id = tx.id(&config.chain_conf.consensus_parameters.chain_id); - - let mut sub = shared.block_importer.block_importer.subscribe(); - shared - .txpool - .insert(vec![std::sync::Arc::new(tx)]) - .await - .into_iter() - .next() - .expect("Should be at least 1 element") - .expect("Should include transaction successfully"); - let res = sub.recv().await.expect("Should produce a block"); - assert_eq!(res.tx_status.len(), 2); - assert_eq!(res.sealed_block.entity.transactions().len(), 2); - assert_eq!(res.tx_status[0].id, tx_id); - - let fuel_core_types::services::executor::TransactionExecutionResult::Failed { - reason, - .. - } = &res.tx_status[0].result - else { - panic!("The execution should fails with out of gas") - }; - if !reason.contains("OutOfGas") { - panic!("The test failed because of {}", reason); - } - } - }) - }); + let mut rng = rand::rngs::StdRng::seed_from_u64(2322u64); + let contract_ids = vec![]; + let (service, rt) = service_with_many_contracts(0, contract_ids.clone()); // Doesn't need any contracts + run_with_service_with_extra_inputs( + id, + group, + script, + script_data, + &service, + contract_ids, + &rt, + &mut rng, + vec![], + vec![], + ); } -/// Sets up a service with a full database. Returns the service with the associated Runtime. -/// The size of the database can be overridden with the `STATE_SIZE` environment variable. -fn service_with_contract_id( +/// Sets up a service with a contract for each contract id. The contract state will be set to +/// `state_size` for each contract. +fn service_with_many_contracts( state_size: u64, - contract_id: ContractId, -) -> (fuel_core::service::FuelService, tokio::runtime::Runtime) { + contract_ids: Vec, +) -> (FuelService, tokio::runtime::Runtime) { use fuel_core::database::vm_database::IncreaseStorageKey; let rt = tokio::runtime::Builder::new_current_thread() .enable_all() @@ -277,9 +255,11 @@ fn service_with_contract_id( .consensus_parameters .tx_params .max_gas_per_tx = TARGET_BLOCK_GAS_LIMIT; - config.chain_conf.initial_state.as_mut().unwrap().contracts = - Some(vec![ContractConfig { - contract_id, + + let contract_configs = contract_ids + .iter() + .map(|contract_id| ContractConfig { + contract_id: *contract_id, code: vec![], salt: Default::default(), state: None, @@ -288,7 +268,9 @@ fn service_with_contract_id( output_index: None, tx_pointer_block_height: None, tx_pointer_tx_idx: None, - }]); + }) + .collect::>(); + config.chain_conf.initial_state.as_mut().unwrap().contracts = Some(contract_configs); config .chain_conf @@ -308,36 +290,38 @@ fn service_with_contract_id( let mut storage_key = primitive_types::U256::zero(); let mut key_bytes = Bytes32::zeroed(); - database - .init_contract_state( - &contract_id, - (0..state_size).map(|_| { - storage_key.to_big_endian(key_bytes.as_mut()); - storage_key.increase().unwrap(); - (key_bytes, key_bytes) - }), - ) - .unwrap(); - - let mut storage_key = primitive_types::U256::zero(); - let mut sub_id = Bytes32::zeroed(); - database - .init_contract_balances( - &contract_id, - (0..state_size).map(|k| { - storage_key.to_big_endian(sub_id.as_mut()); - - let asset = if k % 2 == 0 { - VmBench::CONTRACT.asset_id(&sub_id) - } else { - let asset_id = AssetId::new(*sub_id); + for contract_id in contract_ids.iter() { + database + .init_contract_state( + contract_id, + (0..state_size).map(|_| { + storage_key.to_big_endian(key_bytes.as_mut()); storage_key.increase().unwrap(); - asset_id - }; - (asset, k / 2 + 1_000) - }), - ) - .unwrap(); + (key_bytes, key_bytes) + }), + ) + .unwrap(); + + let mut storage_key = primitive_types::U256::zero(); + let mut sub_id = Bytes32::zeroed(); + database + .init_contract_balances( + contract_id, + (0..state_size).map(|k| { + storage_key.to_big_endian(sub_id.as_mut()); + + let asset = if k % 2 == 0 { + VmBench::CONTRACT.asset_id(&sub_id) + } else { + let asset_id = AssetId::new(*sub_id); + storage_key.increase().unwrap(); + asset_id + }; + (asset, k / 2 + 1_000) + }), + ) + .unwrap(); + } let service = fuel_core::service::FuelService::new(database, config.clone()) .expect("Unable to start a FuelService"); @@ -354,8 +338,8 @@ fn run_with_service_with_extra_inputs( group: &mut BenchmarkGroup, script: Vec, script_data: Vec, - service: &fuel_core::service::FuelService, - contract_id: ContractId, + service: &FuelService, + contract_ids: Vec, rt: &tokio::runtime::Runtime, rng: &mut rand::rngs::StdRng, extra_inputs: Vec, @@ -382,6 +366,7 @@ fn run_with_service_with_extra_inputs( Default::default(), Default::default(), ); + for contract_id in &contract_ids { let input_count = tx_builder.inputs().len(); let contract_input = Input::contract( @@ -389,13 +374,14 @@ fn run_with_service_with_extra_inputs( Bytes32::zeroed(), Bytes32::zeroed(), TxPointer::default(), - contract_id, + *contract_id, ); let contract_output = Output::contract(input_count as u8, Bytes32::zeroed(), Bytes32::zeroed()); tx_builder .add_input(contract_input) .add_output(contract_output); + } for input in &extra_inputs { tx_builder.add_input(input.clone()); diff --git a/benches/benches/block_target_gas_set/other.rs b/benches/benches/block_target_gas_set/other.rs index 69e3dc77..44904216 100644 --- a/benches/benches/block_target_gas_set/other.rs +++ b/benches/benches/block_target_gas_set/other.rs @@ -10,11 +10,8 @@ pub fn run_other(group: &mut BenchmarkGroup) { let asset_id = AssetId::zeroed(); let script_data = script_data(&contract_id, &asset_id); let mut shared_runner_builder = SanityBenchmarkRunnerBuilder::new_shared(contract_id); - // run_group_ref( - // &mut c.benchmark_group("flag"), - // "flag", - // VmBench::new(op::flag(0x10)), - // ); + + // flag run( "other/flag", group, @@ -37,5 +34,23 @@ pub fn run_other(group: &mut BenchmarkGroup) { .run(id, group, instructions, script_data.clone()); } - // gtf: TODO: As part of parent issue (https://github.com/FuelLabs/fuel-core/issues/1386) + // gtf + { + let count = 254; + let correct_index = count; // Have the last index be the correct one. The builder includes an extra input, so it's the 255th index (254). + + let contract_ids = (0..count) + .map(|x| ContractId::from([x as u8; 32])) + .collect::>(); + + let instructions = vec![ + op::movi(0x11, correct_index as u32), + op::gtf_args(0x10, 0x11, GTFArgs::InputContractOutputIndex), + op::jmpb(RegId::ZERO, 0), + ]; + + SanityBenchmarkRunnerBuilder::new_with_many_contracts(contract_ids) + .build() + .run("other/gtf", group, instructions, vec![]); + } } diff --git a/benches/benches/vm_set/alu.rs b/benches/benches/vm_set/alu.rs index 8e93f7db..18230cb5 100644 --- a/benches/benches/vm_set/alu.rs +++ b/benches/benches/vm_set/alu.rs @@ -98,12 +98,14 @@ pub fn run(c: &mut Criterion) { ); { - let data = vec![0u8; 32]; + let count = 254; + let correct_index = count - 1; // Have the last index be the correct one. The builder includes an extra input, so it's the 255th index (254). run_group_ref( &mut c.benchmark_group("gtf"), "gtf", - VmBench::new(op::gtf_args(0x10, RegId::ZERO, GTFArgs::ScriptData)) - .with_data(data), + VmBench::new(op::gtf_args(0x10, 0x11, GTFArgs::InputContractOutputIndex)) + .with_empty_contracts_count(count) + .with_prepare_script(vec![op::movi(0x11, correct_index as u32)]), ); } diff --git a/benches/src/lib.rs b/benches/src/lib.rs index d59ea2e3..c97e72b4 100644 --- a/benches/src/lib.rs +++ b/benches/src/lib.rs @@ -93,6 +93,7 @@ pub struct VmBench { pub prepare_call: Option, pub dummy_contract: Option, pub contract_code: Option, + pub empty_contracts: Vec, pub receipts_ctx: Option, } @@ -132,6 +133,7 @@ impl VmBench { prepare_call: None, dummy_contract: None, contract_code: None, + empty_contracts: vec![], receipts_ctx: None, } } @@ -294,6 +296,15 @@ impl VmBench { self } + pub fn with_empty_contracts_count(mut self, count: usize) -> Self { + let mut contract_ids = Vec::with_capacity(count); + for n in 0..count { + contract_ids.push(ContractId::from([n as u8; 32])); + } + self.empty_contracts = contract_ids; + self + } + pub fn prepare(self) -> anyhow::Result { self.try_into() } @@ -320,6 +331,7 @@ impl TryFrom for VmBenchPrepared { prepare_call, dummy_contract, contract_code, + empty_contracts, receipts_ctx, } = case; @@ -391,6 +403,30 @@ impl TryFrom for VmBenchPrepared { db.deploy_contract_with_id(&salt, &slots, &contract, &root, &id)?; } + for contract_id in empty_contracts { + let input_count = tx.inputs().len(); + let output = + Output::contract(input_count as u8, Bytes32::zeroed(), Bytes32::zeroed()); + let input = Input::contract( + UtxoId::default(), + Bytes32::zeroed(), + Bytes32::zeroed(), + TxPointer::default(), + contract_id, + ); + + tx.add_input(input); + tx.add_output(output); + + db.deploy_contract_with_id( + &VmBench::SALT, + &[], + &Contract::default(), + &Bytes32::zeroed(), + &contract_id, + )?; + } + inputs.into_iter().for_each(|i| { tx.add_input(i); });