diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c526bf4e37..5b4e6af8361 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Description of the upcoming release here. - [#1488](https://github.com/FuelLabs/fuel-core/pull/1488): Add docker login for cross binaries that use docker - [#1485](https://github.com/FuelLabs/fuel-core/pull/1485): Prepare rc release of fuel core v0.21 +- [#1453](https://github.com/FuelLabs/fuel-core/pull/1453): Add the majority of the "sanity" benchmarks for contract opcodes. - [#1473](https://github.com/FuelLabs/fuel-core/pull/1473): Expose fuel-core version as a constant - [#1469](https://github.com/FuelLabs/fuel-core/pull/1469): Added support of bloom filter for RocksDB tables and increased the block cache. - [#1642](https://github.com/FuelLabs/fuel-core/pull/1462): Added benchmark to measure the performance of contract state and contract ID calculation; use for gas costing. diff --git a/Cargo.lock b/Cargo.lock index 6bfc3c8ff73..f349dc2f247 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2799,6 +2799,7 @@ dependencies = [ "ed25519-dalek 2.0.0", "ethnum", "fuel-core", + "fuel-core-chain-config", "fuel-core-services", "fuel-core-storage", "fuel-core-sync", diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 0a8aa55e8a8..78d83239c68 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -14,6 +14,7 @@ ctrlc = "3.2.3" ed25519-dalek = { version = "2.0", features = ["rand_core"] } ethnum = "1.3" fuel-core = { path = "../crates/fuel-core", default-features = false, features = ["rocksdb-production"] } +fuel-core-chain-config = { workspace = true } fuel-core-services = { path = "./../crates/services" } fuel-core-storage = { path = "./../crates/storage" } fuel-core-sync = { path = "./../crates/services/sync", features = ["benchmarking"] } diff --git a/benches/benches/block_target_gas.rs b/benches/benches/block_target_gas.rs index 85266b69be8..2bd8feec1da 100644 --- a/benches/benches/block_target_gas.rs +++ b/benches/benches/block_target_gas.rs @@ -16,12 +16,19 @@ use ed25519_dalek::Signer; use fuel_core::service::{ config::Trigger, Config, + FuelService, ServiceTrait, }; use rand::SeedableRng; use ethnum::U256; +use fuel_core::txpool::types::Word; use fuel_core_benches::*; +use fuel_core_chain_config::ContractConfig; +use fuel_core_storage::{ + tables::ContractsRawCode, + StorageAsMut, +}; use fuel_core_types::{ fuel_asm::{ op, @@ -41,14 +48,31 @@ use fuel_core_types::{ secp256r1, *, }, - fuel_tx::UniqueIdentifier, - fuel_types::AssetId, + fuel_tx::{ + ContractIdExt, + GasCosts, + Input, + Output, + TxPointer, + UniqueIdentifier, + UtxoId, + }, + fuel_types::{ + AssetId, + Bytes32, + ContractId, + }, + fuel_vm::{ + checked_transaction::EstimatePredicates, + consts::WORD_SIZE, + }, }; mod utils; mod block_target_gas_set; +use crate::block_target_gas_set::default_gas_costs::default_gas_costs; use utils::{ make_u128, make_u256, @@ -58,6 +82,100 @@ use utils::{ #[global_allocator] static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; +const STATE_SIZE: u64 = 10_000_000; +const TARGET_BLOCK_GAS_LIMIT: u64 = 1_000_000; +const BASE: u64 = 100_000; + +pub struct SanityBenchmarkRunnerBuilder; + +pub struct SharedSanityBenchmarkFactory { + service: FuelService, + rt: tokio::runtime::Runtime, + contract_id: ContractId, + 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 { + let state_size = get_state_size(); + let (service, rt) = service_with_contract_id(state_size, contract_id); + let rng = rand::rngs::StdRng::seed_from_u64(2322u64); + SharedSanityBenchmarkFactory { + service, + rt, + contract_id, + rng, + } + } +} + +impl SharedSanityBenchmarkFactory { + fn build(&mut self) -> SanityBenchmark { + SanityBenchmark { + service: &mut self.service, + rt: &self.rt, + rng: &mut self.rng, + extra_inputs: vec![], + extra_outputs: vec![], + } + } + + pub fn build_with_new_contract( + &mut self, + contract_instructions: Vec, + ) -> SanityBenchmark { + replace_contract_in_service( + &mut self.service, + &self.contract_id, + contract_instructions, + ); + self.build() + } +} + +pub struct SanityBenchmark<'a> { + service: &'a mut FuelService, + rt: &'a tokio::runtime::Runtime, + rng: &'a mut rand::rngs::StdRng, + extra_inputs: Vec, + extra_outputs: Vec, +} + +impl<'a> SanityBenchmark<'a> { + pub fn with_extra_inputs(mut self, extra_inputs: Vec) -> Self { + self.extra_inputs = extra_inputs; + self + } + + pub fn with_extra_outputs(mut self, extra_outputs: Vec) -> Self { + self.extra_outputs = extra_outputs; + self + } + + pub fn run( + self, + id: &str, + group: &mut BenchmarkGroup, + script: Vec, + script_data: Vec, + ) { + run_with_service_with_extra_inputs( + id, + group, + script, + script_data, + self.service, + VmBench::CONTRACT, + self.rt, + self.rng, + self.extra_inputs, + self.extra_outputs, + ); + } +} + fn run( id: &str, group: &mut BenchmarkGroup, @@ -70,8 +188,6 @@ fn run( .build() .unwrap(); let _drop = rt.enter(); - const TARGET_BLOCK_GAS_LIMIT: u64 = 100_000; - const BASE: u64 = 10_000; let database = Database::rocksdb(); let mut config = Config::local_node(); @@ -82,6 +198,8 @@ fn run( .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; @@ -92,8 +210,8 @@ fn run( b.to_async(&rt).iter(|| { let shared = service.shared.clone(); + let tx = fuel_core_types::fuel_tx::TransactionBuilder::script( - // Infinite loop script.clone().into_iter().collect(), script_data.clone(), ) @@ -102,7 +220,7 @@ fn run( .add_unsigned_coin_input( SecretKey::random(&mut rng), rng.gen(), - u64::MAX, + u64::MAX / 2, AssetId::BASE, Default::default(), Default::default(), @@ -138,44 +256,202 @@ fn run( }); } +/// 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( + state_size: u64, + contract_id: ContractId, +) -> (fuel_core::service::FuelService, tokio::runtime::Runtime) { + use fuel_core::database::vm_database::IncreaseStorageKey; + let rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap(); + let _drop = rt.enter(); + let mut 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.initial_state.as_mut().unwrap().contracts = + Some(vec![ContractConfig { + contract_id, + code: vec![], + salt: Default::default(), + state: None, + balances: None, + tx_id: None, + output_index: None, + tx_pointer_block_height: None, + tx_pointer_tx_idx: None, + }]); + + 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 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); + 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"); + service.start().expect("Unable to start the service"); + (service, rt) +} + +fn get_state_size() -> u64 { + // Override state size if the env var is set + let state_size = std::env::var_os("STATE_SIZE") + .map(|value| { + let value = value.to_str().unwrap(); + let value = value.parse::().unwrap(); + println!("Overriding state size with {}", value); + value + }) + .unwrap_or(STATE_SIZE); + state_size +} + +// Runs benchmark for `script` with prepared `service` and specified contract (by `contract_id`) which should be +// included in service. +// Also include additional inputs and outputs in transaction +#[allow(clippy::too_many_arguments)] +fn run_with_service_with_extra_inputs( + id: &str, + group: &mut BenchmarkGroup, + script: Vec, + script_data: Vec, + service: &fuel_core::service::FuelService, + contract_id: ContractId, + rt: &tokio::runtime::Runtime, + rng: &mut rand::rngs::StdRng, + extra_inputs: Vec, + extra_outputs: Vec, +) { + group.bench_function(id, |b| { + + b.to_async(rt).iter(|| { + let shared = service.shared.clone(); + + + let mut tx_builder = fuel_core_types::fuel_tx::TransactionBuilder::script( + script.clone().into_iter().collect(), + script_data.clone(), + ); + tx_builder + .script_gas_limit(TARGET_BLOCK_GAS_LIMIT - BASE) + .gas_price(1) + .add_unsigned_coin_input( + SecretKey::random(rng), + rng.gen(), + u32::MAX as u64, + AssetId::BASE, + Default::default(), + Default::default(), + ); + let input_count = tx_builder.inputs().len(); + + let contract_input = Input::contract( + UtxoId::default(), + Bytes32::zeroed(), + Bytes32::zeroed(), + TxPointer::default(), + 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()); + } + + for output in &extra_outputs { + tx_builder.add_output(*output); + } + let mut tx = tx_builder.finalize_as_transaction(); + tx.estimate_predicates(&shared.config.chain_conf.consensus_parameters.clone().into()).unwrap(); + async move { + let tx_id = tx.id(&shared.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, "res.tx_status: {:?}", res.tx_status); + 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); + } + } + }) + }); +} + fn block_target_gas(c: &mut Criterion) { let mut group = c.benchmark_group("block target estimation"); - run( - "Script with meq opcode and infinite loop", - &mut group, - [ - op::movi(0x10, (1 << 18) - 1), - op::meq(0x11, RegId::SP, RegId::SP, 0x10), - op::jmpb(RegId::ZERO, 0), - ] - .to_vec(), - vec![], - ); - - run( - "Script with logd opcode and infinite loop", - &mut group, - [ - op::movi(0x10, (1 << 18) - 1), - op::logd(RegId::ZERO, RegId::ZERO, RegId::ZERO, 0x10), - op::jmpb(RegId::ZERO, 0), - ] - .to_vec(), - vec![], - ); - - run( - "Script with gtf opcode and infinite loop", - &mut group, - [ - op::gtf(0x10, RegId::ZERO, GTFArgs::InputCoinOwner as u16), - op::jmpb(RegId::ZERO, 0), - ] - .to_vec(), - vec![], - ); - run_alu(&mut group); run_contract(&mut group); @@ -189,5 +465,101 @@ fn block_target_gas(c: &mut Criterion) { group.finish(); } +fn replace_contract_in_service( + service: &mut FuelService, + contract_id: &ContractId, + contract_instructions: Vec, +) { + let contract_bytecode: Vec<_> = contract_instructions + .iter() + .flat_map(|x| x.to_bytes()) + .collect(); + service + .shared + .database + .storage_as_mut::() + .insert(contract_id, &contract_bytecode) + .unwrap(); +} + +fn script_data(contract_id: &ContractId, asset_id: &AssetId) -> Vec { + contract_id + .iter() + .copied() + .chain((0 as Word).to_be_bytes().iter().copied()) + .chain((0 as Word).to_be_bytes().iter().copied()) + .chain(asset_id.iter().copied()) + .collect() +} + +fn setup_instructions() -> Vec { + vec![ + op::gtf_args(0x10, 0x00, GTFArgs::ScriptData), + op::addi(0x11, 0x10, ContractId::LEN.try_into().unwrap()), + op::addi(0x11, 0x11, WORD_SIZE.try_into().unwrap()), + op::addi(0x11, 0x11, WORD_SIZE.try_into().unwrap()), + op::movi(0x12, (1 << 18) - 1), + ] +} + +/// Returns a bytecode that contains an infinite loop that increases the `u256` iterator by +/// `1` each iteration. A function expects a closure that returns an opcode that must +/// be called infinitely. The closure should accept one argument - +/// the register where the iterator is stored. +fn u256_iterator_loop(opcode: impl Fn(RegId) -> Instruction) -> Vec { + u256_iterator_loop_with_step(opcode, 1) +} + +/// Returns a bytecode that contains an infinite loop that increases the `u256` iterator by +/// `step` each iteration. A function expects a closure that returns an opcode that must +/// be called infinitely. The closure should accept one argument - +/// the register where the iterator is stored. +fn u256_iterator_loop_with_step( + opcode: impl Fn(RegId) -> Instruction, + step: u32, +) -> Vec { + // Register where we store an iterator. + let iterator_register = RegId::new(0x20); + let step_register = RegId::new(0x21); + vec![ + // Store size of the iterator. + op::movi(iterator_register, 32), + // Store step value. + op::movi(step_register, step), + // Allocate 32 bytes for u256 iterator. + op::aloc(iterator_register), + // Store the address of the u256 iterator into `iterator_register`. + op::move_(iterator_register, RegId::HP), + // We need to pad number of isntruciton to be 8-byte aligned. + op::noop(), + // Execute benchmarking opcode. + opcode(iterator_register), + // Increment the iterator by one. + op::wqop( + iterator_register, + iterator_register, + step_register, + MathOp::ADD as u8, + ), + // Jump 4 instructions(jmpb, wqop, opcode, noop) back. + op::jmpb(RegId::ZERO, 1), + ] +} + +fn call_contract_repeat() -> Vec { + let mut instructions = setup_instructions(); + instructions.extend(vec![ + op::call(0x10, RegId::ZERO, 0x11, RegId::CGAS), + op::jmpb(RegId::ZERO, 0), + ]); + instructions +} + +fn call_contract_once() -> Vec { + let mut instructions = setup_instructions(); + instructions.extend(vec![op::call(0x10, RegId::ZERO, 0x11, RegId::CGAS)]); + instructions +} + criterion_group!(benches, block_target_gas); criterion_main!(benches); diff --git a/benches/benches/block_target_gas_set/contract.rs b/benches/benches/block_target_gas_set/contract.rs index 2f84f85fb94..f4b1b8363b1 100644 --- a/benches/benches/block_target_gas_set/contract.rs +++ b/benches/benches/block_target_gas_set/contract.rs @@ -1,20 +1,559 @@ -use crate::*; -// use crate::utils::arb_dependent_cost_values; - -pub fn run_contract(_group: &mut BenchmarkGroup) { - // This breaks the benchmarking - // for i in arb_dependent_cost_values() { - // let id = format!("flow/retd_contract opcode {:?}", i); - // run( - // &id, +use crate::{ + utils::arb_dependent_cost_values, + *, +}; +use fuel_core_types::{ + fuel_types::{ + Address, + Word, + }, + fuel_vm::consts::WORD_SIZE, +}; + +// This register is used by `setup_instructions` function to set contract id. +const CONTRACT_ID_REGISTER: RegId = RegId::new(0x10); +const SEQUENTIAL_STEP: u32 = 10; + +// BAL: Balance of contract ID +// BHEI: Block height +// BHSH: Block hash +// BURN: Burn existing coins +// CALL: Call contract +// CB: Coinbase address +// CCP: Code copy +// CROO: Code Merkle root +// CSIZ: Code size +// LDC: Load code from an external contract +// LOG: Log event +// LOGD: Log data event +// MINT: Mint new coins +// RETD: Return from context with data +// RVRT: Revert +// SMO: Send message to output +// SCWQ: State clear sequential 32 byte slots +// SRW: State read word +// SRWQ: State read sequential 32 byte slots +// SWW: State write word +// SWWQ: State write sequential 32 byte slots +// TIME: Timestamp at height +// TR: Transfer coins to contract +// TRO: Transfer coins to output +pub fn run_contract(group: &mut BenchmarkGroup) { + let contract_id = ContractId::zeroed(); + let asset_id = AssetId::zeroed(); + let script_data = script_data(&contract_id, &asset_id); + + let mut shared_runner_builder = SanityBenchmarkRunnerBuilder::new_shared(contract_id); + + // bal contract + { + let contract_instructions = + u256_iterator_loop(|iterator| op::bal(0x13, iterator, CONTRACT_ID_REGISTER)); + + let instructions = call_contract_once(); + let id = "contract/bal contract"; + + shared_runner_builder + .build_with_new_contract(contract_instructions) + .run(id, group, instructions, script_data.clone()); + } + + { + let mut instructions = setup_instructions(); + instructions.extend(u256_iterator_loop(|iterator| { + op::bal(0x13, iterator, CONTRACT_ID_REGISTER) + })); + let id = "contract/bal script"; + shared_runner_builder + .build() + .run(id, group, instructions, script_data.clone()); + } + + // bhei + run( + "contract/bhei", + group, + vec![op::bhei(0x10), op::jmpb(RegId::ZERO, 0)], + vec![], + ); + + // bhsh + run( + "contract/bhsh", + group, + vec![ + op::movi(0x10, Bytes32::LEN.try_into().unwrap()), + op::aloc(0x10), + op::move_(0x10, RegId::HP), + op::bhsh(0x10, RegId::ZERO), + op::jmpb(RegId::ZERO, 0), + ], + vec![], + ); + + // burn + { + let contract = u256_iterator_loop(|iterator| op::burn(RegId::ONE, iterator)); + let instructions = call_contract_once(); + shared_runner_builder.build_with_new_contract(contract).run( + "contract/burn", + group, + instructions, + script_data.clone(), + ); + } + + // call + for size in arb_dependent_cost_values() { + let mut contract_instructions = std::iter::repeat(op::noop()) + .take(size as usize) + .collect::>(); + contract_instructions.push(op::ret(0x10)); + + let instructions = vec![ + op::gtf_args(CONTRACT_ID_REGISTER, 0x00, GTFArgs::ScriptData), + op::addi(0x11, 0x10, ContractId::LEN.try_into().unwrap()), + op::addi(0x11, 0x11, WORD_SIZE.try_into().unwrap()), + op::addi(0x11, 0x11, WORD_SIZE.try_into().unwrap()), + op::call(CONTRACT_ID_REGISTER, RegId::ZERO, 0x11, RegId::CGAS), + op::jmpb(RegId::ZERO, 0), + ]; + + let id = format!("contract/call {:?}", size); + shared_runner_builder + .build_with_new_contract(contract_instructions) + .run(&id, group, instructions, script_data.clone()); + } + + // cb + { + run( + "contract/cb", + group, + vec![ + op::movi(0x10, Bytes32::LEN.try_into().unwrap()), + op::aloc(0x10), + op::move_(0x10, RegId::HP), + op::cb(0x10), + op::jmpb(RegId::ZERO, 0), + ], + vec![], + ); + } + + // ccp + for i in arb_dependent_cost_values() { + let contract = std::iter::repeat(op::noop()) + .take(i as usize) + .chain(vec![op::ret(RegId::ZERO)]) + .collect(); + + let mut instructions = setup_instructions(); + instructions.extend(vec![ + op::movi(0x13, i), + op::movi(0x14, i), + op::movi(0x15, i), + op::add(0x15, 0x15, 0x15), + op::addi(0x15, 0x15, 32), + op::aloc(0x15), + op::move_(0x15, RegId::HP), + op::ccp(0x15, CONTRACT_ID_REGISTER, RegId::ZERO, 0x13), + op::jmpb(RegId::ZERO, 0), + ]); + let id = format!("contract/ccp {:?}", i); + shared_runner_builder.build_with_new_contract(contract).run( + &id, + group, + instructions, + script_data.clone(), + ); + } + // croo + { + let contract = vec![ + op::gtf_args(0x16, 0x00, GTFArgs::ScriptData), + op::movi(0x15, 32), + op::aloc(0x15), + op::move_(0x14, RegId::HP), + op::croo(0x14, 0x16), + op::ret(RegId::ZERO), + ]; + let instructions = call_contract_repeat(); + shared_runner_builder.build_with_new_contract(contract).run( + "contract/croo", + group, + instructions, + script_data.clone(), + ); + } + + // csiz + for size in arb_dependent_cost_values() { + let contract = std::iter::repeat(op::noop()) + .take(size as usize) + .chain(vec![op::ret(RegId::ZERO)]) + .collect(); + let mut instructions = setup_instructions(); + instructions.extend(vec![ + op::gtf_args(CONTRACT_ID_REGISTER, 0x00, GTFArgs::ScriptData), + op::csiz(0x11, CONTRACT_ID_REGISTER), + op::jmpb(RegId::ZERO, 0), + ]); + let id = format!("contract/csiz {:?}", size); + shared_runner_builder.build_with_new_contract(contract).run( + &id, + group, + instructions, + script_data.clone(), + ); + } + + // ldc + for size in arb_dependent_cost_values() { + let contract = std::iter::repeat(op::noop()) + .take(size as usize) + .chain(vec![op::ret(RegId::ZERO)]) + .collect(); + let mut instructions = setup_instructions(); + instructions.extend(vec![ + op::movi(0x13, size), + op::ldc(CONTRACT_ID_REGISTER, RegId::ZERO, 0x13), + op::jmpb(RegId::ZERO, 0), + ]); + let id = format!("contract/ldc {:?}", size); + shared_runner_builder.build_with_new_contract(contract).run( + &id, + group, + instructions, + script_data.clone(), + ); + } + + // log + { + run( + "contract/log", + group, + vec![op::log(0x10, 0x11, 0x12, 0x13), op::jmpb(RegId::ZERO, 0)], + vec![], + ); + } + + // logd + { + for i in arb_dependent_cost_values() { + let mut instructions = setup_instructions(); + instructions.extend(vec![ + op::movi(0x13, i), + op::logd(0x10, 0x11, RegId::ZERO, 0x13), + op::jmpb(RegId::ZERO, 0), + ]); + let id = format!("contract/logd {:?}", i); + shared_runner_builder.build().run( + &id, + group, + instructions, + script_data.clone(), + ); + } + } + + // mint + { + let contract = u256_iterator_loop(|iterator| op::mint(RegId::ONE, iterator)); + let instructions = call_contract_once(); + shared_runner_builder.build_with_new_contract(contract).run( + "contract/mint", + group, + instructions, + script_data.clone(), + ); + } + + // ret contract + { + let contract = vec![op::ret(RegId::ONE), op::ret(RegId::ZERO)]; + let instructions = call_contract_repeat(); + shared_runner_builder.build_with_new_contract(contract).run( + "contract/ret contract", + group, + instructions, + script_data.clone(), + ); + } + + // retd contract + { + for i in arb_dependent_cost_values() { + let contract = vec![op::movi(0x14, i), op::retd(RegId::ONE, 0x14)]; + let instructions = call_contract_repeat(); + // replace_contract_in_service(&mut service, &contract_id, contract); + let id = format!("contract/retd contract {:?}", i); + shared_runner_builder.build_with_new_contract(contract).run( + &id, + group, + instructions, + script_data.clone(), + ); + } + } + + // run_group_ref( + // &mut c.benchmark_group("rvrt_contract"), + // "rvrt_contract", + // VmBench::contract(rng, op::ret(RegId::ONE)).unwrap(), + // ); + + // TODO: Is `rvrt` even possible to bench? + // { + // let contract = vec![op::rvrt(RegId::ONE)]; + // let instructions = call_contract_repeat(); + // replace_contract_in_service(&mut service, &contract_id, contract); + // run_with_service( + // "contract/rvrt contract", // group, - // vec![ - // op::movi(0x10, i), - // op::retd(RegId::ONE, 0x10), - // op::jmpb(RegId::ZERO, 0), - // ] - // .to_vec(), + // call_contract_repeat(), + // script_data.clone(), + // &service, + // contract_id, + // &rt, + // &mut rng, + // ); + // } + + // smo + { + let predicate = op::ret(RegId::ONE).to_bytes().to_vec(); + let owner = Input::predicate_owner(&predicate); + let coin_input = Input::coin_predicate( + Default::default(), + owner, + u32::MAX as Word, + AssetId::zeroed(), + Default::default(), + Default::default(), + Default::default(), + predicate, + vec![], + ); + let extra_inputs = vec![coin_input]; + for i in arb_dependent_cost_values() { + let contract = vec![ + op::gtf_args(0x15, 0x00, GTFArgs::ScriptData), + // Offset 32 + 8 + 8 + 32 + op::addi(0x15, 0x15, 32 + 8 + 8 + 32), // target address pointer + op::addi(0x16, 0x15, 32), // data ppinter + op::movi(0x17, i), // data length + op::smo(0x15, 0x16, 0x17, 0x18), + op::jmpb(RegId::ZERO, 0), + ]; + let mut instructions = setup_instructions(); + instructions.extend(vec![ + op::movi(0x18, 1), // coins to send + op::call(CONTRACT_ID_REGISTER, 0x12, 0x11, RegId::CGAS), + ]); + let mut data = script_data.clone(); + data.extend( + Address::new([1u8; 32]) + .iter() + .copied() + .chain(vec![2u8; i as usize]), + ); + let id = format!("contract/smo {:?}", i); + shared_runner_builder + .build_with_new_contract(contract) + .with_extra_inputs(extra_inputs.clone()) + .run(&id, group, instructions, data); + } + } + + // scwq + { + let step = SEQUENTIAL_STEP; + let contract = + u256_iterator_loop_with_step(|iterator| op::scwq(iterator, 0x13, 0x15), step); + let mut instructions = vec![op::movi(0x15, step)]; + instructions.extend(call_contract_once()); + shared_runner_builder.build_with_new_contract(contract).run( + "contract/scwq", + group, + instructions, + script_data.clone(), + ); + } + + // srw + + { + let contract = u256_iterator_loop(|iterator| op::srw(0x13, 0x14, iterator)); + let mut instructions = setup_instructions(); + instructions.extend(vec![ + op::movi(0x15, 2000), + op::call(CONTRACT_ID_REGISTER, RegId::ZERO, 0x11, RegId::CGAS), + ]); + shared_runner_builder.build_with_new_contract(contract).run( + "contract/srw", + group, + instructions, + script_data.clone(), + ); + } + + // srwq + { + let step = SEQUENTIAL_STEP; + let mut contract = vec![ + op::movi(0x15, step), + op::movi(0x16, step * Bytes32::LEN as u32), + op::aloc(0x16), + op::move_(0x17, RegId::HP), + ]; + contract.extend(u256_iterator_loop_with_step( + |iterator| op::srwq(0x17, 0x13, iterator, 0x15), + step, + )); + let instructions = call_contract_once(); + shared_runner_builder.build_with_new_contract(contract).run( + "contract/srwq", + group, + instructions, + script_data.clone(), + ); + } + + // sww + { + let contract = u256_iterator_loop(|iterator| op::sww(iterator, 0x29, RegId::ONE)); + let mut instructions = setup_instructions(); + instructions.extend(vec![op::call( + CONTRACT_ID_REGISTER, + RegId::ZERO, + 0x11, + RegId::CGAS, + )]); + shared_runner_builder.build_with_new_contract(contract).run( + "contract/sww", + group, + instructions, + script_data.clone(), + ); + } + + // swwq + { + let step = SEQUENTIAL_STEP; + let contract = u256_iterator_loop_with_step( + |iterator| op::swwq(iterator, 0x13, RegId::ZERO, 0x15), + step, + ); + let mut instructions = vec![op::movi(0x15, step)]; + instructions.extend(call_contract_once()); + shared_runner_builder.build_with_new_contract(contract).run( + "contract/swwq", + group, + instructions, + script_data.clone(), + ); + } + + // time + { + run( + "contract/time", + group, + vec![ + op::movi(0x10, 0), + op::time(0x11, 0x10), + op::jmpb(RegId::ZERO, 0), + ], + vec![], + ); + } + + // tr + { + let contract = u256_iterator_loop(|iterator| op::tr(0x15, 0x14, iterator)); + let mut instructions = setup_instructions(); + instructions.extend(vec![ + op::movi(0x13, (1 << 18) - 1), + op::movi(0x15, 2000), + op::movi(0x14, 1), + op::call(CONTRACT_ID_REGISTER, 0x13, 0x15, RegId::CGAS), + ]); + shared_runner_builder.build_with_new_contract(contract).run( + "contract/tr", + group, + instructions, + script_data.clone(), + ); + } + + // tro + + // The `tro` benchmark is disabled because it would require many, many outputs, because each + // would get spent. But it's okay because that is putting a limit of 255 outputs per transaction + // and that protects us from an attacker exploiting a poorly priced `tro` instruction. + // { + // let amount = 100; + // + // let contract = vec![ + // op::tro(RegId::ZERO, 0x15, 0x14, RegId::HP), + // // op::ret(RegId::ZERO), + // ]; + // let mut instructions = setup_instructions(); + // + // instructions.extend(vec![ + // op::movi(0x14, amount), + // op::movi(0x15, 1), + // op::movi(0x20, 32), + // op::aloc(0x20), + // ]); + // + // for (i, v) in (*AssetId::zeroed()).into_iter().enumerate() { + // instructions.push(op::movi(0x20, v as u32)); + // instructions.push(op::sb(RegId::HP, 0x20, i as u16)); + // } + // + // instructions.extend(vec![ + // op::call(0x10, RegId::ZERO, 0x11, RegId::CGAS), + // // op::jmpb(RegId::ZERO, 0), + // ]); + // + // let predicate = op::ret(RegId::ONE).to_bytes().to_vec(); + // let owner = Input::predicate_owner(&predicate); + // let coin_input = Input::coin_predicate( + // Default::default(), + // owner, + // 1000, + // AssetId::zeroed(), + // Default::default(), + // Default::default(), + // Default::default(), + // predicate, // vec![], // ); + // let coin_output = Output::variable(Address::zeroed(), 100, AssetId::zeroed()); + // let extra_inputs = vec![coin_input]; + // let extra_outputs = vec![coin_output]; + // + // // replace_contract_in_service(&mut service, &contract_id, contract); + // // run_with_service_with_extra_inputs( + // // "contract/tro", + // // group, + // // instructions, + // // script_data.clone(), + // // &service, + // // contract_id, + // // &rt, + // // &mut rng, + // // extra_inputs, + // // extra_outputs, + // // ); + // // } + // shared_runner_builder + // .build_with_new_contract(contract) + // .with_extra_inputs(extra_inputs) + // .with_extra_outputs(extra_outputs) + // .run("contract/tro", group, instructions, script_data.clone()); // } } diff --git a/benches/benches/block_target_gas_set/default_gas_costs.rs b/benches/benches/block_target_gas_set/default_gas_costs.rs new file mode 100644 index 00000000000..7431bbeaa6d --- /dev/null +++ b/benches/benches/block_target_gas_set/default_gas_costs.rs @@ -0,0 +1,172 @@ +use super::*; +pub fn default_gas_costs() -> GasCostsValues { + GasCostsValues { + add: 1, + addi: 1, + aloc: 1, + and: 1, + andi: 1, + bal: 26, + bhei: 1, + bhsh: 1, + burn: 27286, + cb: 1, + cfei: 1, + cfsi: 1, + croo: 37, + div: 1, + divi: 1, + eck1: 3351, + ecr1: 46061, + ed19: 3163, + eq: 1, + exp: 1, + expi: 1, + flag: 1, + gm: 1, + gt: 1, + gtf: 1, + ji: 1, + jmp: 1, + jne: 1, + jnei: 1, + jnzi: 1, + jmpf: 1, + jmpb: 1, + jnzf: 1, + jnzb: 1, + jnef: 1, + jneb: 1, + lb: 1, + log: 92, + lt: 1, + lw: 1, + mint: 25370, + mlog: 1, + vm_initialization: 1, + modi: 1, + mod_op: 1, + movi: 1, + mroo: 4, + mul: 1, + muli: 1, + mldv: 3, + noop: 1, + not: 1, + or: 1, + ori: 1, + poph: 1, + popl: 1, + pshh: 1, + pshl: 1, + move_op: 1, + ret: 135, + sb: 1, + sll: 1, + slli: 1, + srl: 1, + srli: 1, + srw: 237, + sub: 1, + subi: 1, + sw: 1, + sww: 27313, + time: 1, + tr: 38459, + tro: 26269, + wdcm: 1, + wqcm: 2, + wdop: 2, + wqop: 2, + wdml: 2, + wqml: 3, + wddv: 4, + wqdv: 6, + wdmd: 10, + wqmd: 16, + wdam: 8, + wqam: 10, + wdmm: 9, + wqmm: 9, + xor: 1, + xori: 1, + call: DependentCost::LightOperation { + base: 1171, + units_per_gas: 47, + }, + ccp: DependentCost::LightOperation { + base: 39, + units_per_gas: 65, + }, + csiz: DependentCost::LightOperation { + base: 49, + units_per_gas: 214, + }, + k256: DependentCost::LightOperation { + base: 277, + units_per_gas: 3, + }, + ldc: DependentCost::LightOperation { + base: 37, + units_per_gas: 63, + }, + logd: DependentCost::LightOperation { + base: 444, + units_per_gas: 3, + }, + mcl: DependentCost::LightOperation { + base: 1, + units_per_gas: 500, + }, + mcli: DependentCost::LightOperation { + base: 1, + units_per_gas: 500, + }, + mcp: DependentCost::LightOperation { + base: 2, + units_per_gas: 493, + }, + mcpi: DependentCost::LightOperation { + base: 4, + units_per_gas: 1023, + }, + meq: DependentCost::LightOperation { + base: 2, + units_per_gas: 1111, + }, + rvrt: 136, + s256: DependentCost::LightOperation { + base: 44, + units_per_gas: 3, + }, + scwq: DependentCost::HeavyOperation { + base: 28687, + gas_per_unit: 27036, + }, + smo: DependentCost::LightOperation { + base: 58089, + units_per_gas: 1, + }, + srwq: DependentCost::LightOperation { + base: 121, + units_per_gas: 1, + }, + swwq: DependentCost::HeavyOperation { + base: 26790, + gas_per_unit: 25451, + }, + contract_root: DependentCost::LightOperation { + base: 44, + units_per_gas: 2, + }, + state_root: DependentCost::HeavyOperation { + base: 347, + gas_per_unit: 176, + }, + new_storage_per_byte: 1, + retd: DependentCost::LightOperation { + base: 466, + units_per_gas: 3, + }, + } +} diff --git a/benches/benches/block_target_gas_set/mod.rs b/benches/benches/block_target_gas_set/mod.rs index 8bda84df858..cd266169cf3 100644 --- a/benches/benches/block_target_gas_set/mod.rs +++ b/benches/benches/block_target_gas_set/mod.rs @@ -1,3 +1,8 @@ +use fuel_core_types::fuel_tx::{ + DependentCost, + GasCostsValues, +}; + pub mod alu; pub mod crypto; @@ -7,3 +12,5 @@ pub mod flow; pub mod contract; pub mod memory; + +pub mod default_gas_costs; diff --git a/benches/benches/vm_set/blockchain.rs b/benches/benches/vm_set/blockchain.rs index 3b0cb5f2f4e..a716f4d46d6 100644 --- a/benches/benches/vm_set/blockchain.rs +++ b/benches/benches/vm_set/blockchain.rs @@ -47,7 +47,7 @@ pub struct BenchDb { impl BenchDb { const STATE_SIZE: u64 = 10_000_000; - fn new(contract: &ContractId) -> anyhow::Result { + fn new(contract_id: &ContractId) -> anyhow::Result { let tmp_dir = ShallowTempDir::new(); let db = Arc::new(RocksDb::default_open(tmp_dir.path(), None).unwrap()); @@ -56,7 +56,7 @@ impl BenchDb { let mut database = Database::new(db); database.init_contract_state( - contract, + contract_id, (0..Self::STATE_SIZE).map(|_| { use fuel_core::database::vm_database::IncreaseStorageKey; storage_key.to_big_endian(key_bytes.as_mut()); @@ -65,7 +65,7 @@ impl BenchDb { }), )?; database.init_contract_balances( - &ContractId::zeroed(), + contract_id, (0..Self::STATE_SIZE).map(|k| { let key = k / 2; let mut sub_id = Bytes32::zeroed(); @@ -255,7 +255,7 @@ pub fn run(c: &mut Criterion) { run_group_ref( &mut call, format!("{i}"), - VmBench::new(op::call(0x10, RegId::ZERO, 0x11, 0x12)) + VmBench::new(op::call(0x10, RegId::ZERO, 0x11, RegId::CGAS)) .with_db(db.to_vm_database()) .with_contract_code(code) .with_data(data) diff --git a/benches/src/bin/collect.rs b/benches/src/bin/collect.rs index fd0db403d96..0b81125d1d0 100644 --- a/benches/src/bin/collect.rs +++ b/benches/src/bin/collect.rs @@ -319,7 +319,10 @@ fn decode_input(line: &str) -> Option { fn map_to_ratio(baseline: u64, mean: Duration) -> u64 { let mean: u64 = mean.as_nanos().try_into().unwrap(); - mean.checked_div(baseline).unwrap_or(1).max(1) + (mean + (baseline - 1)) + .checked_div(baseline) + .unwrap_or(1) + .max(1) } impl Display for State { diff --git a/benches/src/lib.rs b/benches/src/lib.rs index 7f694daf97b..e6428ca8175 100644 --- a/benches/src/lib.rs +++ b/benches/src/lib.rs @@ -366,9 +366,9 @@ impl TryFrom for VmBenchPrepared { storage_root, }) = contract_code { - let input = tx.inputs().len(); + let input_count = tx.inputs().len(); let output = - Output::contract(input as u8, Bytes32::zeroed(), Bytes32::zeroed()); + Output::contract(input_count as u8, Bytes32::zeroed(), Bytes32::zeroed()); let input = Input::contract( UtxoId::default(), Bytes32::zeroed(), diff --git a/crates/fuel-core/src/database.rs b/crates/fuel-core/src/database.rs index 936407da82f..7f00a5c2ed8 100644 --- a/crates/fuel-core/src/database.rs +++ b/crates/fuel-core/src/database.rs @@ -21,6 +21,7 @@ use fuel_core_storage::{ Result as StorageResult, }; use fuel_core_types::fuel_types::BlockHeight; + use itertools::Itertools; use serde::{ de::DeserializeOwned,