diff --git a/Cargo.lock b/Cargo.lock index 461f4df862cc..d104f9932cbd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4001,6 +4001,7 @@ dependencies = [ "zk_evm 1.3.1", "zk_evm 1.3.3 (git+https://github.com/matter-labs/era-zk_evm.git?tag=v1.3.3-rc2)", "zk_evm 1.4.0", + "zkevm_test_harness 1.4.0", "zksync_contracts", "zksync_eth_signer", "zksync_state", @@ -8022,9 +8023,10 @@ dependencies = [ [[package]] name = "zk_evm_abstractions" version = "0.1.0" -source = "git+https://github.com/matter-labs/era-zk_evm_abstractions.git#15a2af404902d5f10352e3d1fac693cc395fcff9" +source = "git+https://github.com/matter-labs/era-zk_evm_abstractions.git#32dd320953841aa78579d9da08abbc70bcaed175" dependencies = [ "anyhow", + "num_enum", "serde", "static_assertions", "zkevm_opcode_defs 1.3.2", @@ -8104,7 +8106,7 @@ dependencies = [ "codegen 0.2.0", "crossbeam 0.8.2", "derivative", - "env_logger 0.10.0", + "env_logger 0.9.3", "hex", "num-bigint 0.4.4", "num-integer", diff --git a/core/bin/snapshots_creator/src/tests.rs b/core/bin/snapshots_creator/src/tests.rs index db8501c7d796..d344f453e6c9 100644 --- a/core/bin/snapshots_creator/src/tests.rs +++ b/core/bin/snapshots_creator/src/tests.rs @@ -173,7 +173,7 @@ async fn create_l1_batch( ); header.is_finished = true; conn.blocks_dal() - .insert_l1_batch(&header, &[], BlockGasCount::default(), &[], &[]) + .insert_l1_batch(&header, &[], BlockGasCount::default(), &[], &[], 0) .await .unwrap(); conn.blocks_dal() diff --git a/core/lib/dal/migrations/20240104121833_l1-batch-predicted-circuits.down.sql b/core/lib/dal/migrations/20240104121833_l1-batch-predicted-circuits.down.sql new file mode 100644 index 000000000000..925299304ebe --- /dev/null +++ b/core/lib/dal/migrations/20240104121833_l1-batch-predicted-circuits.down.sql @@ -0,0 +1,2 @@ +ALTER TABLE l1_batches + DROP COLUMN IF EXISTS predicted_circuits; diff --git a/core/lib/dal/migrations/20240104121833_l1-batch-predicted-circuits.up.sql b/core/lib/dal/migrations/20240104121833_l1-batch-predicted-circuits.up.sql new file mode 100644 index 000000000000..a957fce4d984 --- /dev/null +++ b/core/lib/dal/migrations/20240104121833_l1-batch-predicted-circuits.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE l1_batches + ADD COLUMN IF NOT EXISTS predicted_circuits INT; diff --git a/core/lib/dal/sqlx-data.json b/core/lib/dal/sqlx-data.json index 183ab24fc458..b009e9d8028c 100644 --- a/core/lib/dal/sqlx-data.json +++ b/core/lib/dal/sqlx-data.json @@ -5788,6 +5788,42 @@ }, "query": "\n SELECT\n number,\n hash\n FROM\n miniblocks\n WHERE\n number >= $1\n ORDER BY\n number ASC\n LIMIT\n $2\n " }, + "70979db81f473950b2fae7816dbad7fe3464f2619cee2d583accaa829aa12b94": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Int8", + "Int4", + "Int4", + "Int8", + "Bool", + "Bytea", + "ByteaArray", + "ByteaArray", + "Bytea", + "ByteaArray", + "Int8", + "Int8", + "Int8", + "Jsonb", + "Jsonb", + "Numeric", + "Int8", + "Int8", + "Bytea", + "Bytea", + "Int4", + "ByteaArray", + "Int8Array", + "Bytea", + "Int4" + ] + } + }, + "query": "\n INSERT INTO\n l1_batches (\n number,\n l1_tx_count,\n l2_tx_count,\n timestamp,\n is_finished,\n fee_account_address,\n l2_to_l1_logs,\n l2_to_l1_messages,\n bloom,\n priority_ops_onchain_data,\n predicted_commit_gas_cost,\n predicted_prove_gas_cost,\n predicted_execute_gas_cost,\n initial_bootloader_heap_content,\n used_contract_hashes,\n base_fee_per_gas,\n l1_gas_price,\n l2_fair_gas_price,\n bootloader_code_hash,\n default_aa_code_hash,\n protocol_version,\n system_logs,\n storage_refunds,\n pubdata_input,\n predicted_circuits,\n created_at,\n updated_at\n )\n VALUES\n (\n $1,\n $2,\n $3,\n $4,\n $5,\n $6,\n $7,\n $8,\n $9,\n $10,\n $11,\n $12,\n $13,\n $14,\n $15,\n $16,\n $17,\n $18,\n $19,\n $20,\n $21,\n $22,\n $23,\n $24,\n $25,\n NOW(),\n NOW()\n )\n " + }, "72a4f50355324cce85ebaef9fa32826095e9290f0c1157094bd0c44e06012e42": { "describe": { "columns": [ @@ -8002,41 +8038,6 @@ }, "query": "\n SELECT\n upgrade_tx_hash\n FROM\n protocol_versions\n WHERE\n id = $1\n " }, - "aa8e569cf406cd0975a6ffaeeafa92f632186181ba8b93518e549e0643f58da8": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Int8", - "Int4", - "Int4", - "Int8", - "Bool", - "Bytea", - "ByteaArray", - "ByteaArray", - "Bytea", - "ByteaArray", - "Int8", - "Int8", - "Int8", - "Jsonb", - "Jsonb", - "Numeric", - "Int8", - "Int8", - "Bytea", - "Bytea", - "Int4", - "ByteaArray", - "Int8Array", - "Bytea" - ] - } - }, - "query": "\n INSERT INTO\n l1_batches (\n number,\n l1_tx_count,\n l2_tx_count,\n timestamp,\n is_finished,\n fee_account_address,\n l2_to_l1_logs,\n l2_to_l1_messages,\n bloom,\n priority_ops_onchain_data,\n predicted_commit_gas_cost,\n predicted_prove_gas_cost,\n predicted_execute_gas_cost,\n initial_bootloader_heap_content,\n used_contract_hashes,\n base_fee_per_gas,\n l1_gas_price,\n l2_fair_gas_price,\n bootloader_code_hash,\n default_aa_code_hash,\n protocol_version,\n system_logs,\n storage_refunds,\n pubdata_input,\n created_at,\n updated_at\n )\n VALUES\n (\n $1,\n $2,\n $3,\n $4,\n $5,\n $6,\n $7,\n $8,\n $9,\n $10,\n $11,\n $12,\n $13,\n $14,\n $15,\n $16,\n $17,\n $18,\n $19,\n $20,\n $21,\n $22,\n $23,\n $24,\n NOW(),\n NOW()\n )\n " - }, "aa91697157517322b0dbb53dca99f41220c51f58a03c61d6b7789eab0504e320": { "describe": { "columns": [ diff --git a/core/lib/dal/src/blocks_dal.rs b/core/lib/dal/src/blocks_dal.rs index ae3ef48bcbb6..239f9074800e 100644 --- a/core/lib/dal/src/blocks_dal.rs +++ b/core/lib/dal/src/blocks_dal.rs @@ -450,6 +450,7 @@ impl BlocksDal<'_, '_> { predicted_block_gas: BlockGasCount, events_queue: &[LogQuery], storage_refunds: &[u32], + predicted_circuits: u32, ) -> anyhow::Result<()> { let priority_onchain_data: Vec> = header .priority_ops_onchain_data @@ -509,6 +510,7 @@ impl BlocksDal<'_, '_> { system_logs, storage_refunds, pubdata_input, + predicted_circuits, created_at, updated_at ) @@ -538,6 +540,7 @@ impl BlocksDal<'_, '_> { $22, $23, $24, + $25, NOW(), NOW() ) @@ -566,6 +569,7 @@ impl BlocksDal<'_, '_> { &system_logs, &storage_refunds, pubdata_input, + predicted_circuits as i32, ) .execute(transaction.conn()) .await?; @@ -2341,7 +2345,7 @@ mod tests { header.l2_to_l1_messages.push(vec![33; 33]); conn.blocks_dal() - .insert_l1_batch(&header, &[], BlockGasCount::default(), &[], &[]) + .insert_l1_batch(&header, &[], BlockGasCount::default(), &[], &[], 0) .await .unwrap(); @@ -2390,7 +2394,7 @@ mod tests { execute: 10, }; conn.blocks_dal() - .insert_l1_batch(&header, &[], predicted_gas, &[], &[]) + .insert_l1_batch(&header, &[], predicted_gas, &[], &[], 0) .await .unwrap(); @@ -2398,7 +2402,7 @@ mod tests { header.timestamp += 100; predicted_gas += predicted_gas; conn.blocks_dal() - .insert_l1_batch(&header, &[], predicted_gas, &[], &[]) + .insert_l1_batch(&header, &[], predicted_gas, &[], &[], 0) .await .unwrap(); diff --git a/core/lib/dal/src/storage_logs_dal.rs b/core/lib/dal/src/storage_logs_dal.rs index ff757b748e8d..21572f3d3c0f 100644 --- a/core/lib/dal/src/storage_logs_dal.rs +++ b/core/lib/dal/src/storage_logs_dal.rs @@ -733,7 +733,7 @@ mod tests { ); header.is_finished = true; conn.blocks_dal() - .insert_l1_batch(&header, &[], BlockGasCount::default(), &[], &[]) + .insert_l1_batch(&header, &[], BlockGasCount::default(), &[], &[], 0) .await .unwrap(); conn.blocks_dal() diff --git a/core/lib/dal/src/sync_dal.rs b/core/lib/dal/src/sync_dal.rs index 4d50f2855bbf..c9a737f581d0 100644 --- a/core/lib/dal/src/sync_dal.rs +++ b/core/lib/dal/src/sync_dal.rs @@ -136,7 +136,7 @@ mod tests { ProtocolVersionId::latest(), ); conn.blocks_dal() - .insert_l1_batch(&l1_batch_header, &[], BlockGasCount::default(), &[], &[]) + .insert_l1_batch(&l1_batch_header, &[], BlockGasCount::default(), &[], &[], 0) .await .unwrap(); conn.blocks_dal() @@ -205,7 +205,7 @@ mod tests { l1_batch_header.number = L1BatchNumber(1); l1_batch_header.timestamp = 1; conn.blocks_dal() - .insert_l1_batch(&l1_batch_header, &[], BlockGasCount::default(), &[], &[]) + .insert_l1_batch(&l1_batch_header, &[], BlockGasCount::default(), &[], &[], 0) .await .unwrap(); conn.blocks_dal() diff --git a/core/lib/multivm/Cargo.toml b/core/lib/multivm/Cargo.toml index 0cf207409904..5f87b5ae5543 100644 --- a/core/lib/multivm/Cargo.toml +++ b/core/lib/multivm/Cargo.toml @@ -14,6 +14,8 @@ zk_evm_1_4_0 = { package = "zk_evm", git = "https://github.com/matter-labs/era-z zk_evm_1_3_3 = { package = "zk_evm", git = "https://github.com/matter-labs/era-zk_evm.git", tag= "v1.3.3-rc2" } zk_evm_1_3_1 = { package = "zk_evm", git = "https://github.com/matter-labs/era-zk_evm.git", tag= "v1.3.1-rc2" } +zkevm_test_harness_1_4_0 = { git = "https://github.com/matter-labs/era-zkevm_test_harness.git", branch = "v1.4.0", package = "zkevm_test_harness" } + zksync_types = { path = "../types" } zksync_state = { path = "../state" } zksync_contracts = { path = "../contracts" } @@ -29,7 +31,6 @@ thiserror = "1.0" tracing = "0.1" vise = { git = "https://github.com/matter-labs/vise.git", version = "0.1.0", rev = "1c9cc500e92cf9ea052b230e114a6f9cce4fb2c1" } - [dev-dependencies] tokio = { version = "1", features = ["time"] } zksync_test_account = { path = "../test_account" } diff --git a/core/lib/multivm/src/glue/types/vm/vm_block_result.rs b/core/lib/multivm/src/glue/types/vm/vm_block_result.rs index 623d3d735d19..f7eda05cc02d 100644 --- a/core/lib/multivm/src/glue/types/vm/vm_block_result.rs +++ b/core/lib/multivm/src/glue/types/vm/vm_block_result.rs @@ -26,6 +26,7 @@ impl GlueFrom for crate::interface::Fi computational_gas_used: value.full_result.gas_used, gas_used: value.full_result.gas_used, pubdata_published: 0, + estimated_circuits_used: 0.0, }, refunds: Refunds::default(), }, @@ -64,6 +65,7 @@ impl GlueFrom for crate::interface::Fi computational_gas_used: value.full_result.computational_gas_used, gas_used: value.full_result.gas_used, pubdata_published: 0, + estimated_circuits_used: 0.0, }, refunds: Refunds::default(), }, @@ -108,6 +110,7 @@ impl GlueFrom for crate::interface: computational_gas_used: value.full_result.computational_gas_used, gas_used: value.full_result.gas_used, pubdata_published: 0, + estimated_circuits_used: 0.0, }, refunds: Refunds::default(), }, @@ -168,6 +171,7 @@ impl GlueFrom computational_gas_used: value.full_result.computational_gas_used, gas_used: value.full_result.gas_used, pubdata_published: 0, + estimated_circuits_used: 0.0, }, refunds: Refunds::default(), } @@ -198,6 +202,7 @@ impl GlueFrom computational_gas_used: 0, gas_used: value.full_result.gas_used, pubdata_published: 0, + estimated_circuits_used: 0.0, }, refunds: Refunds::default(), } @@ -239,6 +244,7 @@ impl GlueFrom computational_gas_used: value.full_result.computational_gas_used, gas_used: value.full_result.gas_used, pubdata_published: 0, + estimated_circuits_used: 0.0, }, refunds: Refunds::default(), } diff --git a/core/lib/multivm/src/glue/types/vm/vm_partial_execution_result.rs b/core/lib/multivm/src/glue/types/vm/vm_partial_execution_result.rs index 4de727a04c10..6a52c7e66987 100644 --- a/core/lib/multivm/src/glue/types/vm/vm_partial_execution_result.rs +++ b/core/lib/multivm/src/glue/types/vm/vm_partial_execution_result.rs @@ -16,6 +16,7 @@ impl GlueFrom // There are no such fields in m5 computational_gas_used: 0, pubdata_published: 0, + estimated_circuits_used: 0.0, }, refunds: crate::interface::Refunds { gas_refunded: 0, @@ -39,6 +40,7 @@ impl GlueFrom computational_gas_used: value.computational_gas_used, total_log_queries: value.logs.total_log_queries_count, pubdata_published: 0, + estimated_circuits_used: 0.0, }, refunds: crate::interface::Refunds { gas_refunded: 0, @@ -62,6 +64,7 @@ impl GlueFrom computational_gas_used: value.computational_gas_used, total_log_queries: value.logs.total_log_queries_count, pubdata_published: 0, + estimated_circuits_used: 0.0, }, refunds: crate::interface::Refunds { gas_refunded: 0, diff --git a/core/lib/multivm/src/interface/types/outputs/execution_result.rs b/core/lib/multivm/src/interface/types/outputs/execution_result.rs index e177b6300120..6471ca1fe193 100644 --- a/core/lib/multivm/src/interface/types/outputs/execution_result.rs +++ b/core/lib/multivm/src/interface/types/outputs/execution_result.rs @@ -101,6 +101,7 @@ impl VmExecutionResultAndLogs { cycles_used: self.statistics.cycles_used, computational_gas_used: self.statistics.computational_gas_used, pubdata_published: self.statistics.pubdata_published, + estimated_circuits_used: self.statistics.estimated_circuits_used, } } } diff --git a/core/lib/multivm/src/interface/types/outputs/statistic.rs b/core/lib/multivm/src/interface/types/outputs/statistic.rs index c1312fc95da8..1f5b233423c0 100644 --- a/core/lib/multivm/src/interface/types/outputs/statistic.rs +++ b/core/lib/multivm/src/interface/types/outputs/statistic.rs @@ -12,6 +12,7 @@ pub struct VmExecutionStatistics { /// Number of log queries produced by the VM during the tx execution. pub total_log_queries: usize, pub pubdata_published: u32, + pub estimated_circuits_used: f32, } /// Oracle metrics of the VM. diff --git a/core/lib/multivm/src/versions/vm_boojum_integration/implementation/execution.rs b/core/lib/multivm/src/versions/vm_boojum_integration/implementation/execution.rs index 9623b21a1945..1d1d19f92b76 100644 --- a/core/lib/multivm/src/versions/vm_boojum_integration/implementation/execution.rs +++ b/core/lib/multivm/src/versions/vm_boojum_integration/implementation/execution.rs @@ -80,6 +80,7 @@ impl Vm { spent_pubdata_counter_before, pubdata_published, logs.total_log_queries_count, + tx_tracer.circuits_tracer.estimated_circuits_used, ); let result = tx_tracer.result_tracer.into_result(); diff --git a/core/lib/multivm/src/versions/vm_boojum_integration/implementation/statistics.rs b/core/lib/multivm/src/versions/vm_boojum_integration/implementation/statistics.rs index c11165cf78d1..36780c8b8458 100644 --- a/core/lib/multivm/src/versions/vm_boojum_integration/implementation/statistics.rs +++ b/core/lib/multivm/src/versions/vm_boojum_integration/implementation/statistics.rs @@ -24,6 +24,7 @@ impl Vm { spent_pubdata_counter_before: u32, pubdata_published: u32, total_log_queries_count: usize, + estimated_circuits_used: f32, ) -> VmExecutionStatistics { let computational_gas_used = self.calculate_computational_gas_used( tracer, @@ -40,6 +41,7 @@ impl Vm { computational_gas_used, total_log_queries: total_log_queries_count, pubdata_published, + estimated_circuits_used, } } diff --git a/core/lib/multivm/src/versions/vm_boojum_integration/old_vm/oracles/precompile.rs b/core/lib/multivm/src/versions/vm_boojum_integration/old_vm/oracles/precompile.rs index 4c798a00a37b..c4986250cbae 100644 --- a/core/lib/multivm/src/versions/vm_boojum_integration/old_vm/oracles/precompile.rs +++ b/core/lib/multivm/src/versions/vm_boojum_integration/old_vm/oracles/precompile.rs @@ -1,7 +1,9 @@ +use std::convert::TryFrom; + use zk_evm_1_4_0::{ abstractions::{Memory, PrecompileCyclesWitness, PrecompilesProcessor}, aux_structures::{LogQuery, MemoryQuery, Timestamp}, - zk_evm_abstractions::precompiles::DefaultPrecompilesProcessor, + zk_evm_abstractions::precompiles::{ecrecover, keccak256, sha256, PrecompileAddress}, }; use super::OracleWithHistory; @@ -15,40 +17,44 @@ use crate::vm_boojum_integration::old_vm::history_recorder::{ /// saving timestamps allows us to check the exact number /// of log queries, that were used during the tx execution. #[derive(Debug, Clone)] -pub struct PrecompilesProcessorWithHistory { +pub struct PrecompilesProcessorWithHistory { pub timestamp_history: HistoryRecorder, H>, - pub default_precompiles_processor: DefaultPrecompilesProcessor, + pub precompile_cycles_history: HistoryRecorder, HistoryEnabled>, } -impl Default for PrecompilesProcessorWithHistory { +impl Default for PrecompilesProcessorWithHistory { fn default() -> Self { Self { timestamp_history: Default::default(), - default_precompiles_processor: DefaultPrecompilesProcessor, + precompile_cycles_history: Default::default(), } } } -impl OracleWithHistory for PrecompilesProcessorWithHistory { +impl OracleWithHistory for PrecompilesProcessorWithHistory { fn rollback_to_timestamp(&mut self, timestamp: Timestamp) { self.timestamp_history.rollback_to_timestamp(timestamp); + self.precompile_cycles_history + .rollback_to_timestamp(timestamp); } } -impl PrecompilesProcessorWithHistory { +impl PrecompilesProcessorWithHistory { pub fn get_timestamp_history(&self) -> &Vec { self.timestamp_history.inner() } pub fn delete_history(&mut self) { self.timestamp_history.delete_history(); + self.precompile_cycles_history.delete_history(); } } -impl PrecompilesProcessor for PrecompilesProcessorWithHistory { +impl PrecompilesProcessor for PrecompilesProcessorWithHistory { fn start_frame(&mut self) { - self.default_precompiles_processor.start_frame(); + // there are no precompiles to rollback, do nothing } + fn execute_precompile( &mut self, monotonic_cycle_counter: u32, @@ -62,13 +68,47 @@ impl PrecompilesProcessor for PrecompilesProcesso // where operations and timestamp have different types. self.timestamp_history .push(query.timestamp, query.timestamp); - self.default_precompiles_processor.execute_precompile( - monotonic_cycle_counter, - query, - memory, - ) + + let address_low = u16::from_le_bytes([query.address.0[19], query.address.0[18]]); + if let Ok(precompile_address) = PrecompileAddress::try_from(address_low) { + let rounds = match precompile_address { + PrecompileAddress::Keccak256 => { + // pure function call, non-revertable + keccak256::keccak256_rounds_function::( + monotonic_cycle_counter, + query, + memory, + ) + .0 + } + PrecompileAddress::SHA256 => { + // pure function call, non-revertable + sha256::sha256_rounds_function::( + monotonic_cycle_counter, + query, + memory, + ) + .0 + } + PrecompileAddress::Ecrecover => { + // pure function call, non-revertable + ecrecover::ecrecover_function::( + monotonic_cycle_counter, + query, + memory, + ) + .0 + } + }; + + self.precompile_cycles_history + .push((precompile_address, rounds), query.timestamp); + }; + + None } + fn finish_frame(&mut self, _panicked: bool) { - self.default_precompiles_processor.finish_frame(_panicked); + // there are no revertable precompile yes, so we are ok } } diff --git a/core/lib/multivm/src/versions/vm_boojum_integration/oracles/storage.rs b/core/lib/multivm/src/versions/vm_boojum_integration/oracles/storage.rs index 2e051db47481..919fd301c573 100644 --- a/core/lib/multivm/src/versions/vm_boojum_integration/oracles/storage.rs +++ b/core/lib/multivm/src/versions/vm_boojum_integration/oracles/storage.rs @@ -59,6 +59,11 @@ pub struct StorageOracle { // Storage refunds that oracle has returned in `estimate_refunds_for_write`. pub(crate) returned_refunds: HistoryRecorder, H>, + + // Keeps track of storage keys that were ever written to. + pub(crate) written_keys: HistoryRecorder, HistoryEnabled>, + // Keeps track of storage keys that were ever read. + pub(crate) read_keys: HistoryRecorder, HistoryEnabled>, } impl OracleWithHistory for StorageOracle { @@ -69,6 +74,8 @@ impl OracleWithHistory for StorageOracle { self.paid_changes.rollback_to_timestamp(timestamp); self.initial_values.rollback_to_timestamp(timestamp); self.returned_refunds.rollback_to_timestamp(timestamp); + self.written_keys.rollback_to_timestamp(timestamp); + self.read_keys.rollback_to_timestamp(timestamp); } } @@ -81,6 +88,8 @@ impl StorageOracle { paid_changes: Default::default(), initial_values: Default::default(), returned_refunds: Default::default(), + written_keys: Default::default(), + read_keys: Default::default(), } } @@ -91,6 +100,8 @@ impl StorageOracle { self.paid_changes.delete_history(); self.initial_values.delete_history(); self.returned_refunds.delete_history(); + self.written_keys.delete_history(); + self.read_keys.delete_history(); } fn is_storage_key_free(&self, key: &StorageKey) -> bool { @@ -108,8 +119,12 @@ impl StorageOracle { } } - pub fn read_value(&mut self, mut query: LogQuery) -> LogQuery { + fn read_value(&mut self, mut query: LogQuery) -> LogQuery { let key = triplet_to_storage_key(query.shard_id, query.address, query.key); + + if !self.read_keys.inner().contains_key(&key) { + self.read_keys.insert(key, (), query.timestamp); + } let current_value = self.storage.read_from_storage(&key); query.read_value = current_value; @@ -127,8 +142,11 @@ impl StorageOracle { query } - pub fn write_value(&mut self, query: LogQuery) -> LogQuery { + fn write_value(&mut self, query: LogQuery) -> LogQuery { let key = triplet_to_storage_key(query.shard_id, query.address, query.key); + if !self.written_keys.inner().contains_key(&key) { + self.written_keys.insert(key, (), query.timestamp); + } let current_value = self.storage .write_to_storage(key, query.written_value, query.timestamp); diff --git a/core/lib/multivm/src/versions/vm_boojum_integration/tests/circuits.rs b/core/lib/multivm/src/versions/vm_boojum_integration/tests/circuits.rs new file mode 100644 index 000000000000..2630c913e02a --- /dev/null +++ b/core/lib/multivm/src/versions/vm_boojum_integration/tests/circuits.rs @@ -0,0 +1,43 @@ +use zksync_types::{Address, Execute, U256}; + +use crate::{ + interface::{TxExecutionMode, VmExecutionMode, VmInterface}, + vm_boojum_integration::{constants::BLOCK_GAS_LIMIT, tests::tester::VmTesterBuilder, HistoryEnabled}, +}; + +// Checks that estimated number of circuits for simple transfer doesn't differ much +// from hardcoded expected value. +#[test] +fn test_circuits() { + let mut vm = VmTesterBuilder::new(HistoryEnabled) + .with_empty_in_memory_storage() + .with_random_rich_accounts(1) + .with_deployer() + .with_gas_limit(BLOCK_GAS_LIMIT) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .build(); + + let account = &mut vm.rich_accounts[0]; + let tx = account.get_l2_tx_for_execute( + Execute { + contract_address: Address::random(), + calldata: Vec::new(), + value: U256::from(1u8), + factory_deps: None, + }, + None, + ); + vm.vm.push_transaction(tx); + let res = vm.vm.inspect(Default::default(), VmExecutionMode::OneTx); + + const EXPECTED_CIRCUITS_USED: f32 = 1.5521; + let delta = + (res.statistics.estimated_circuits_used - EXPECTED_CIRCUITS_USED) / EXPECTED_CIRCUITS_USED; + + if delta.abs() > 0.1 { + panic!( + "Estimation differs from expected result by too much: {}%", + delta * 100.0 + ); + } +} diff --git a/core/lib/multivm/src/versions/vm_boojum_integration/tests/mod.rs b/core/lib/multivm/src/versions/vm_boojum_integration/tests/mod.rs index ffb38dd3725a..95377232b3e3 100644 --- a/core/lib/multivm/src/versions/vm_boojum_integration/tests/mod.rs +++ b/core/lib/multivm/src/versions/vm_boojum_integration/tests/mod.rs @@ -4,12 +4,14 @@ mod default_aa; // mod invalid_bytecode; mod bytecode_publishing; mod call_tracer; +mod circuits; mod gas_limit; mod get_used_contracts; mod is_write_initial; mod l1_tx_execution; mod l2_blocks; mod nonce_holder; +mod precompiles; mod refunds; mod require_eip712; mod rollbacks; diff --git a/core/lib/multivm/src/versions/vm_boojum_integration/tests/precompiles.rs b/core/lib/multivm/src/versions/vm_boojum_integration/tests/precompiles.rs new file mode 100644 index 000000000000..516331d574f4 --- /dev/null +++ b/core/lib/multivm/src/versions/vm_boojum_integration/tests/precompiles.rs @@ -0,0 +1,136 @@ +use zk_evm_1_4_0::zk_evm_abstractions::precompiles::PrecompileAddress; +use zksync_types::{Address, Execute}; + +use crate::{ + interface::{TxExecutionMode, VmExecutionMode, VmInterface}, + vm_boojum_integration::{ + constants::BLOCK_GAS_LIMIT, + tests::{tester::VmTesterBuilder, utils::read_precompiles_contract}, + HistoryEnabled, + }, +}; + +#[test] +fn test_keccak() { + // Execute special transaction and check that at least 1000 keccak calls were made. + let contract = read_precompiles_contract(); + let address = Address::random(); + let mut vm = VmTesterBuilder::new(HistoryEnabled) + .with_empty_in_memory_storage() + .with_random_rich_accounts(1) + .with_deployer() + .with_gas_limit(BLOCK_GAS_LIMIT) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_custom_contracts(vec![(contract, address, true)]) + .build(); + + // calldata for `doKeccak(1000)`. + let keccak1000_calldata = + "370f20ac00000000000000000000000000000000000000000000000000000000000003e8"; + + let account = &mut vm.rich_accounts[0]; + let tx = account.get_l2_tx_for_execute( + Execute { + contract_address: address, + calldata: hex::decode(keccak1000_calldata).unwrap(), + value: Default::default(), + factory_deps: None, + }, + None, + ); + vm.vm.push_transaction(tx); + let _ = vm.vm.inspect(Default::default(), VmExecutionMode::OneTx); + + let keccak_count = vm + .vm + .state + .precompiles_processor + .precompile_cycles_history + .inner() + .iter() + .filter(|(precompile, _)| precompile == &PrecompileAddress::Keccak256) + .count(); + + assert!(keccak_count >= 1000); +} + +#[test] +fn test_sha256() { + // Execute special transaction and check that at least 1000 sha256 calls were made. + let contract = read_precompiles_contract(); + let address = Address::random(); + let mut vm = VmTesterBuilder::new(HistoryEnabled) + .with_empty_in_memory_storage() + .with_random_rich_accounts(1) + .with_deployer() + .with_gas_limit(BLOCK_GAS_LIMIT) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_custom_contracts(vec![(contract, address, true)]) + .build(); + + // calldata for `doSha256(1000)`. + let sha1000_calldata = + "5d0b4fb500000000000000000000000000000000000000000000000000000000000003e8"; + + let account = &mut vm.rich_accounts[0]; + let tx = account.get_l2_tx_for_execute( + Execute { + contract_address: address, + calldata: hex::decode(sha1000_calldata).unwrap(), + value: Default::default(), + factory_deps: None, + }, + None, + ); + vm.vm.push_transaction(tx); + let _ = vm.vm.inspect(Default::default(), VmExecutionMode::OneTx); + + let sha_count = vm + .vm + .state + .precompiles_processor + .precompile_cycles_history + .inner() + .iter() + .filter(|(precompile, _)| precompile == &PrecompileAddress::SHA256) + .count(); + + assert!(sha_count >= 1000); +} + +#[test] +fn test_ecrecover() { + // Execute simple transfer and check that exactly 1 ecrecover call was made (it's done during tx validation). + let mut vm = VmTesterBuilder::new(HistoryEnabled) + .with_empty_in_memory_storage() + .with_random_rich_accounts(1) + .with_deployer() + .with_gas_limit(BLOCK_GAS_LIMIT) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .build(); + + let account = &mut vm.rich_accounts[0]; + let tx = account.get_l2_tx_for_execute( + Execute { + contract_address: account.address, + calldata: Vec::new(), + value: Default::default(), + factory_deps: None, + }, + None, + ); + vm.vm.push_transaction(tx); + let _ = vm.vm.inspect(Default::default(), VmExecutionMode::OneTx); + + let ecrecover_count = vm + .vm + .state + .precompiles_processor + .precompile_cycles_history + .inner() + .iter() + .filter(|(precompile, _)| precompile == &PrecompileAddress::Ecrecover) + .count(); + + assert_eq!(ecrecover_count, 1); +} diff --git a/core/lib/multivm/src/versions/vm_boojum_integration/tests/utils.rs b/core/lib/multivm/src/versions/vm_boojum_integration/tests/utils.rs index 53ae1c17e917..2dd8e2350eb4 100644 --- a/core/lib/multivm/src/versions/vm_boojum_integration/tests/utils.rs +++ b/core/lib/multivm/src/versions/vm_boojum_integration/tests/utils.rs @@ -103,3 +103,9 @@ pub(crate) fn read_max_depth_contract() -> Vec { "core/tests/ts-integration/contracts/zkasm/artifacts/deep_stak.zkasm/deep_stak.zkasm.zbin", ) } + +pub(crate) fn read_precompiles_contract() -> Vec { + read_bytecode( + "etc/contracts-test-data/artifacts-zk/contracts/precompiles/precompiles.sol/Precompiles.json", + ) +} diff --git a/core/lib/multivm/src/versions/vm_boojum_integration/tracers/circuits_capacity.rs b/core/lib/multivm/src/versions/vm_boojum_integration/tracers/circuits_capacity.rs new file mode 100644 index 000000000000..16f5540172a8 --- /dev/null +++ b/core/lib/multivm/src/versions/vm_boojum_integration/tracers/circuits_capacity.rs @@ -0,0 +1,85 @@ +use zkevm_test_harness_1_4_0::{geometry_config::get_geometry_config, toolset::GeometryConfig}; + +const GEOMETRY_CONFIG: GeometryConfig = get_geometry_config(); +const OVERESTIMATE_PERCENT: f32 = 1.05; + +const MAIN_VM_CYCLE_FRACTION: f32 = + OVERESTIMATE_PERCENT / GEOMETRY_CONFIG.cycles_per_vm_snapshot as f32; + +const CODE_DECOMMITTER_SORTER_CYCLE_FRACTION: f32 = + OVERESTIMATE_PERCENT / GEOMETRY_CONFIG.cycles_code_decommitter_sorter as f32; + +const LOG_DEMUXER_CYCLE_FRACTION: f32 = + OVERESTIMATE_PERCENT / GEOMETRY_CONFIG.cycles_per_log_demuxer as f32; + +const STORAGE_SORTER_CYCLE_FRACTION: f32 = + OVERESTIMATE_PERCENT / GEOMETRY_CONFIG.cycles_per_storage_sorter as f32; + +const EVENTS_OR_L1_MESSAGES_SORTER_CYCLE_FRACTION: f32 = + OVERESTIMATE_PERCENT / GEOMETRY_CONFIG.cycles_per_events_or_l1_messages_sorter as f32; + +const RAM_PERMUTATION_CYCLE_FRACTION: f32 = + OVERESTIMATE_PERCENT / GEOMETRY_CONFIG.cycles_per_ram_permutation as f32; + +pub(crate) const CODE_DECOMMITTER_CYCLE_FRACTION: f32 = + OVERESTIMATE_PERCENT / GEOMETRY_CONFIG.cycles_per_code_decommitter as f32; + +pub(crate) const STORAGE_APPLICATION_CYCLE_FRACTION: f32 = + OVERESTIMATE_PERCENT / GEOMETRY_CONFIG.cycles_per_storage_application as f32; + +pub(crate) const KECCAK256_CYCLE_FRACTION: f32 = + OVERESTIMATE_PERCENT / GEOMETRY_CONFIG.cycles_per_keccak256_circuit as f32; + +pub(crate) const SHA256_CYCLE_FRACTION: f32 = + OVERESTIMATE_PERCENT / GEOMETRY_CONFIG.cycles_per_sha256_circuit as f32; + +pub(crate) const ECRECOVER_CYCLE_FRACTION: f32 = + OVERESTIMATE_PERCENT / GEOMETRY_CONFIG.cycles_per_ecrecover_circuit as f32; + +// "Rich addressing" opcodes are opcodes that can write their return value/read the input onto the stack +// and so take 1-2 RAM permutations more than an average opcode. +// In the worst case, a rich addressing may take 3 ram permutations +// (1 for reading the opcode, 1 for writing input value, 1 for writing output value). +pub(crate) const RICH_ADDRESSING_OPCODE_FRACTION: f32 = + MAIN_VM_CYCLE_FRACTION + 3.0 * RAM_PERMUTATION_CYCLE_FRACTION; + +pub(crate) const AVERAGE_OPCODE_FRACTION: f32 = + MAIN_VM_CYCLE_FRACTION + RAM_PERMUTATION_CYCLE_FRACTION; + +// Here "base" fraction is a fraction that will be used unconditionally. +// Usage of StorageApplication is being tracked separately as it depends on whether slot was read before or not. +pub(crate) const STORAGE_READ_BASE_FRACTION: f32 = MAIN_VM_CYCLE_FRACTION + + RAM_PERMUTATION_CYCLE_FRACTION + + LOG_DEMUXER_CYCLE_FRACTION + + STORAGE_SORTER_CYCLE_FRACTION; + +pub(crate) const EVENT_OR_L1_MESSAGE_FRACTION: f32 = MAIN_VM_CYCLE_FRACTION + + RAM_PERMUTATION_CYCLE_FRACTION + + 2.0 * LOG_DEMUXER_CYCLE_FRACTION + + 2.0 * EVENTS_OR_L1_MESSAGES_SORTER_CYCLE_FRACTION; + +// Here "base" fraction is a fraction that will be used unconditionally. +// Usage of StorageApplication is being tracked separately as it depends on whether slot was written before or not. +pub(crate) const STORAGE_WRITE_BASE_FRACTION: f32 = MAIN_VM_CYCLE_FRACTION + + RAM_PERMUTATION_CYCLE_FRACTION + + 2.0 * LOG_DEMUXER_CYCLE_FRACTION + + 2.0 * STORAGE_SORTER_CYCLE_FRACTION; + +pub(crate) const FAR_CALL_FRACTION: f32 = MAIN_VM_CYCLE_FRACTION + + RAM_PERMUTATION_CYCLE_FRACTION + + STORAGE_SORTER_CYCLE_FRACTION + + CODE_DECOMMITTER_SORTER_CYCLE_FRACTION; + +// 5 RAM permutations, because: 1 to read opcode + 2 reads + 2 writes. +// 2 reads and 2 writes are needed because unaligned access is implemented with +// aligned queries. +pub(crate) const UMA_WRITE_FRACTION: f32 = + MAIN_VM_CYCLE_FRACTION + 5.0 * RAM_PERMUTATION_CYCLE_FRACTION; + +// 3 RAM permutations, because: 1 to read opcode + 2 reads. +// 2 reads are needed because unaligned access is implemented with aligned queries. +pub(crate) const UMA_READ_FRACTION: f32 = + MAIN_VM_CYCLE_FRACTION + 3.0 * RAM_PERMUTATION_CYCLE_FRACTION; + +pub(crate) const PRECOMPILE_CALL_COMMON_FRACTION: f32 = + MAIN_VM_CYCLE_FRACTION + RAM_PERMUTATION_CYCLE_FRACTION + LOG_DEMUXER_CYCLE_FRACTION; diff --git a/core/lib/multivm/src/versions/vm_boojum_integration/tracers/circuits_tracer.rs b/core/lib/multivm/src/versions/vm_boojum_integration/tracers/circuits_tracer.rs new file mode 100644 index 000000000000..9f26cd057ff0 --- /dev/null +++ b/core/lib/multivm/src/versions/vm_boojum_integration/tracers/circuits_tracer.rs @@ -0,0 +1,199 @@ +use std::marker::PhantomData; + +use zk_evm_1_4_0::{ + tracing::{BeforeExecutionData, VmLocalStateData}, + zk_evm_abstractions::precompiles::PrecompileAddress, + zkevm_opcode_defs::{LogOpcode, Opcode, UMAOpcode}, +}; +use zksync_state::{StoragePtr, WriteStorage}; + +use super::circuits_capacity::*; +use crate::{ + interface::{dyn_tracers::vm_1_4_0::DynTracer, tracer::TracerExecutionStatus}, + vm_boojum_integration::{ + bootloader_state::BootloaderState, + old_vm::{ + history_recorder::{HistoryMode, VectorHistoryEvent}, + memory::SimpleMemory, + }, + tracers::traits::VmTracer, + types::internals::ZkSyncVmState, + }, +}; + +/// Tracer responsible for collecting information about refunds. +#[derive(Debug)] +pub(crate) struct CircuitsTracer { + pub(crate) estimated_circuits_used: f32, + last_decommitment_history_entry_checked: Option, + last_written_keys_history_entry_checked: Option, + last_read_keys_history_entry_checked: Option, + last_precompile_history_entry_checked: Option, + _phantom_data: PhantomData, +} + +impl CircuitsTracer { + pub(crate) fn new() -> Self { + Self { + estimated_circuits_used: 0.0, + last_decommitment_history_entry_checked: None, + last_written_keys_history_entry_checked: None, + last_read_keys_history_entry_checked: None, + last_precompile_history_entry_checked: None, + _phantom_data: Default::default(), + } + } +} + +impl DynTracer> for CircuitsTracer { + fn before_execution( + &mut self, + _state: VmLocalStateData<'_>, + data: BeforeExecutionData, + _memory: &SimpleMemory, + _storage: StoragePtr, + ) { + let used = match data.opcode.variant.opcode { + Opcode::Nop(_) + | Opcode::Add(_) + | Opcode::Sub(_) + | Opcode::Mul(_) + | Opcode::Div(_) + | Opcode::Jump(_) + | Opcode::Binop(_) + | Opcode::Shift(_) + | Opcode::Ptr(_) => RICH_ADDRESSING_OPCODE_FRACTION, + Opcode::Context(_) | Opcode::Ret(_) | Opcode::NearCall(_) => AVERAGE_OPCODE_FRACTION, + Opcode::Log(LogOpcode::StorageRead) => STORAGE_READ_BASE_FRACTION, + Opcode::Log(LogOpcode::StorageWrite) => STORAGE_WRITE_BASE_FRACTION, + Opcode::Log(LogOpcode::ToL1Message) | Opcode::Log(LogOpcode::Event) => { + EVENT_OR_L1_MESSAGE_FRACTION + } + Opcode::Log(LogOpcode::PrecompileCall) => PRECOMPILE_CALL_COMMON_FRACTION, + Opcode::FarCall(_) => FAR_CALL_FRACTION, + Opcode::UMA(UMAOpcode::AuxHeapWrite | UMAOpcode::HeapWrite) => UMA_WRITE_FRACTION, + Opcode::UMA( + UMAOpcode::AuxHeapRead | UMAOpcode::HeapRead | UMAOpcode::FatPointerRead, + ) => UMA_READ_FRACTION, + Opcode::Invalid(_) => unreachable!(), // invalid opcodes are never executed + }; + + self.estimated_circuits_used += used; + } +} + +impl VmTracer for CircuitsTracer { + fn initialize_tracer(&mut self, state: &mut ZkSyncVmState) { + self.last_decommitment_history_entry_checked = Some( + state + .decommittment_processor + .decommitted_code_hashes + .history() + .len(), + ); + + self.last_written_keys_history_entry_checked = + Some(state.storage.written_keys.history().len()); + + self.last_read_keys_history_entry_checked = Some(state.storage.read_keys.history().len()); + + self.last_precompile_history_entry_checked = Some( + state + .precompiles_processor + .precompile_cycles_history + .history() + .len(), + ); + } + + fn finish_cycle( + &mut self, + state: &mut ZkSyncVmState, + _bootloader_state: &mut BootloaderState, + ) -> TracerExecutionStatus { + // Trace decommitments. + let last_decommitment_history_entry_checked = self + .last_decommitment_history_entry_checked + .expect("Value must be set during init"); + let history = state + .decommittment_processor + .decommitted_code_hashes + .history(); + for (_, history_event) in &history[last_decommitment_history_entry_checked..] { + // We assume that only insertions may happen during a single VM inspection. + assert!(history_event.value.is_some()); + let bytecode_len = state + .decommittment_processor + .known_bytecodes + .inner() + .get(&history_event.key) + .expect("Bytecode must be known at this point") + .len(); + + // Each cycle of `CodeDecommitter` processes 2 words. + // If the number of words in bytecode is odd, then number of cycles must be rounded up. + let decommitter_cycles_used = (bytecode_len + 1) / 2; + self.estimated_circuits_used += + (decommitter_cycles_used as f32) * CODE_DECOMMITTER_CYCLE_FRACTION; + } + self.last_decommitment_history_entry_checked = Some(history.len()); + + // Process storage writes. + let last_writes_history_entry_checked = self + .last_written_keys_history_entry_checked + .expect("Value must be set during init"); + let history = state.storage.written_keys.history(); + for (_, history_event) in &history[last_writes_history_entry_checked..] { + // We assume that only insertions may happen during a single VM inspection. + assert!(history_event.value.is_some()); + + self.estimated_circuits_used += 2.0 * STORAGE_APPLICATION_CYCLE_FRACTION; + } + self.last_written_keys_history_entry_checked = Some(history.len()); + + // Process storage reads. + let last_reads_history_entry_checked = self + .last_read_keys_history_entry_checked + .expect("Value must be set during init"); + let history = state.storage.read_keys.history(); + for (_, history_event) in &history[last_reads_history_entry_checked..] { + // We assume that only insertions may happen during a single VM inspection. + assert!(history_event.value.is_some()); + + // If the slot is already written to, then we've already taken 2 cycles into account. + if !state + .storage + .written_keys + .inner() + .contains_key(&history_event.key) + { + self.estimated_circuits_used += STORAGE_APPLICATION_CYCLE_FRACTION; + } + } + self.last_read_keys_history_entry_checked = Some(history.len()); + + // Process precompiles. + let last_precompile_history_entry_checked = self + .last_precompile_history_entry_checked + .expect("Value must be set during init"); + let history = state + .precompiles_processor + .precompile_cycles_history + .history(); + for (_, history_event) in &history[last_precompile_history_entry_checked..] { + if let VectorHistoryEvent::Push((precompile, cycles)) = history_event { + let fraction = match precompile { + PrecompileAddress::Ecrecover => ECRECOVER_CYCLE_FRACTION, + PrecompileAddress::SHA256 => SHA256_CYCLE_FRACTION, + PrecompileAddress::Keccak256 => KECCAK256_CYCLE_FRACTION, + }; + self.estimated_circuits_used += (*cycles as f32) * fraction; + } else { + panic!("Precompile calls should not be rolled back"); + } + } + self.last_precompile_history_entry_checked = Some(history.len()); + + TracerExecutionStatus::Continue + } +} diff --git a/core/lib/multivm/src/versions/vm_boojum_integration/tracers/default_tracers.rs b/core/lib/multivm/src/versions/vm_boojum_integration/tracers/default_tracers.rs index f0690d996b21..236421726051 100644 --- a/core/lib/multivm/src/versions/vm_boojum_integration/tracers/default_tracers.rs +++ b/core/lib/multivm/src/versions/vm_boojum_integration/tracers/default_tracers.rs @@ -32,7 +32,7 @@ use crate::{ computational_gas_price, gas_spent_on_bytecodes_and_long_messages_this_opcode, print_debug_if_needed, VmHook, }, - RefundsTracer, ResultTracer, + CircuitsTracer, RefundsTracer, ResultTracer, }, types::internals::ZkSyncVmState, VmTracer, @@ -62,6 +62,11 @@ pub(crate) struct DefaultExecutionTracer { pub(crate) pubdata_tracer: Option>, pub(crate) dispatcher: TracerDispatcher, ret_from_the_bootloader: Option, + // This tracer tracks what opcodes were executed and calculates how much circuits will be generated. + // It only takes into account circuits that are generated for actual execution. It doesn't + // take into account e.g circuits produced by the initial bootloader memory commitment. + pub(crate) circuits_tracer: CircuitsTracer, + storage: StoragePtr, _phantom: PhantomData, } @@ -88,6 +93,7 @@ impl DefaultExecutionTracer { dispatcher, pubdata_tracer, ret_from_the_bootloader: None, + circuits_tracer: CircuitsTracer::new(), storage, _phantom: PhantomData, } @@ -161,14 +167,15 @@ impl Debug for DefaultExecutionTracer { /// The macro passes the function call to all tracers. macro_rules! dispatch_tracers { ($self:ident.$function:ident($( $params:expr ),*)) => { - $self.result_tracer.$function($( $params ),*); - $self.dispatcher.$function($( $params ),*); + $self.result_tracer.$function($( $params ),*); + $self.dispatcher.$function($( $params ),*); if let Some(tracer) = &mut $self.refund_tracer { tracer.$function($( $params ),*); } if let Some(tracer) = &mut $self.pubdata_tracer { tracer.$function($( $params ),*); } + $self.circuits_tracer.$function($( $params ),*); }; } diff --git a/core/lib/multivm/src/versions/vm_boojum_integration/tracers/mod.rs b/core/lib/multivm/src/versions/vm_boojum_integration/tracers/mod.rs index 33d043de6eb1..1bdb1b6ccdbf 100644 --- a/core/lib/multivm/src/versions/vm_boojum_integration/tracers/mod.rs +++ b/core/lib/multivm/src/versions/vm_boojum_integration/tracers/mod.rs @@ -1,13 +1,16 @@ +pub(crate) use circuits_tracer::CircuitsTracer; pub(crate) use default_tracers::DefaultExecutionTracer; pub(crate) use pubdata_tracer::PubdataTracer; pub(crate) use refunds::RefundsTracer; pub(crate) use result_tracer::ResultTracer; +pub(crate) mod circuits_tracer; pub(crate) mod default_tracers; pub(crate) mod pubdata_tracer; pub(crate) mod refunds; pub(crate) mod result_tracer; +mod circuits_capacity; pub mod dispatcher; pub(crate) mod traits; pub(crate) mod utils; diff --git a/core/lib/multivm/src/versions/vm_boojum_integration/types/internals/vm_state.rs b/core/lib/multivm/src/versions/vm_boojum_integration/types/internals/vm_state.rs index b775b4b63bca..bff8dbf0f56d 100644 --- a/core/lib/multivm/src/versions/vm_boojum_integration/types/internals/vm_state.rs +++ b/core/lib/multivm/src/versions/vm_boojum_integration/types/internals/vm_state.rs @@ -40,7 +40,7 @@ pub type ZkSyncVmState = VmState< StorageOracle, SimpleMemory, InMemoryEventSink, - PrecompilesProcessorWithHistory, + PrecompilesProcessorWithHistory, DecommitterOracle, DummyTracer, >; @@ -84,7 +84,7 @@ pub(crate) fn new_vm_state( let storage_oracle: StorageOracle = StorageOracle::new(storage.clone()); let mut memory = SimpleMemory::default(); let event_sink = InMemoryEventSink::default(); - let precompiles_processor = PrecompilesProcessorWithHistory::::default(); + let precompiles_processor = PrecompilesProcessorWithHistory::::default(); let mut decommittment_processor: DecommitterOracle = DecommitterOracle::new(storage); diff --git a/core/lib/multivm/src/versions/vm_latest/implementation/execution.rs b/core/lib/multivm/src/versions/vm_latest/implementation/execution.rs index cc76872d29ec..1dfdc5b36499 100644 --- a/core/lib/multivm/src/versions/vm_latest/implementation/execution.rs +++ b/core/lib/multivm/src/versions/vm_latest/implementation/execution.rs @@ -79,6 +79,7 @@ impl Vm { spent_pubdata_counter_before, pubdata_published, logs.total_log_queries_count, + tx_tracer.circuits_tracer.estimated_circuits_used, ); let result = tx_tracer.result_tracer.into_result(); diff --git a/core/lib/multivm/src/versions/vm_latest/implementation/statistics.rs b/core/lib/multivm/src/versions/vm_latest/implementation/statistics.rs index 8bb3f65bea47..3f88629ffb00 100644 --- a/core/lib/multivm/src/versions/vm_latest/implementation/statistics.rs +++ b/core/lib/multivm/src/versions/vm_latest/implementation/statistics.rs @@ -24,6 +24,7 @@ impl Vm { spent_pubdata_counter_before: u32, pubdata_published: u32, total_log_queries_count: usize, + estimated_circuits_used: f32, ) -> VmExecutionStatistics { let computational_gas_used = self.calculate_computational_gas_used( tracer, @@ -40,6 +41,7 @@ impl Vm { computational_gas_used, total_log_queries: total_log_queries_count, pubdata_published, + estimated_circuits_used, } } diff --git a/core/lib/multivm/src/versions/vm_latest/old_vm/oracles/precompile.rs b/core/lib/multivm/src/versions/vm_latest/old_vm/oracles/precompile.rs index 92b88e40fc95..dbc03fb143a5 100644 --- a/core/lib/multivm/src/versions/vm_latest/old_vm/oracles/precompile.rs +++ b/core/lib/multivm/src/versions/vm_latest/old_vm/oracles/precompile.rs @@ -1,7 +1,9 @@ +use std::convert::TryFrom; + use zk_evm_1_4_0::{ abstractions::{Memory, PrecompileCyclesWitness, PrecompilesProcessor}, aux_structures::{LogQuery, MemoryQuery, Timestamp}, - zk_evm_abstractions::precompiles::DefaultPrecompilesProcessor, + zk_evm_abstractions::precompiles::{ecrecover, keccak256, sha256, PrecompileAddress}, }; use super::OracleWithHistory; @@ -13,40 +15,44 @@ use crate::vm_latest::old_vm::history_recorder::{HistoryEnabled, HistoryMode, Hi /// saving timestamps allows us to check the exact number /// of log queries, that were used during the tx execution. #[derive(Debug, Clone)] -pub struct PrecompilesProcessorWithHistory { +pub struct PrecompilesProcessorWithHistory { pub timestamp_history: HistoryRecorder, H>, - pub default_precompiles_processor: DefaultPrecompilesProcessor, + pub precompile_cycles_history: HistoryRecorder, HistoryEnabled>, } -impl Default for PrecompilesProcessorWithHistory { +impl Default for PrecompilesProcessorWithHistory { fn default() -> Self { Self { timestamp_history: Default::default(), - default_precompiles_processor: DefaultPrecompilesProcessor, + precompile_cycles_history: Default::default(), } } } -impl OracleWithHistory for PrecompilesProcessorWithHistory { +impl OracleWithHistory for PrecompilesProcessorWithHistory { fn rollback_to_timestamp(&mut self, timestamp: Timestamp) { self.timestamp_history.rollback_to_timestamp(timestamp); + self.precompile_cycles_history + .rollback_to_timestamp(timestamp); } } -impl PrecompilesProcessorWithHistory { +impl PrecompilesProcessorWithHistory { pub fn get_timestamp_history(&self) -> &Vec { self.timestamp_history.inner() } pub fn delete_history(&mut self) { self.timestamp_history.delete_history(); + self.precompile_cycles_history.delete_history(); } } -impl PrecompilesProcessor for PrecompilesProcessorWithHistory { +impl PrecompilesProcessor for PrecompilesProcessorWithHistory { fn start_frame(&mut self) { - self.default_precompiles_processor.start_frame(); + // there are no precompiles to rollback, do nothing } + fn execute_precompile( &mut self, monotonic_cycle_counter: u32, @@ -60,13 +66,47 @@ impl PrecompilesProcessor for PrecompilesProcesso // where operations and timestamp have different types. self.timestamp_history .push(query.timestamp, query.timestamp); - self.default_precompiles_processor.execute_precompile( - monotonic_cycle_counter, - query, - memory, - ) + + let address_low = u16::from_le_bytes([query.address.0[19], query.address.0[18]]); + if let Ok(precompile_address) = PrecompileAddress::try_from(address_low) { + let rounds = match precompile_address { + PrecompileAddress::Keccak256 => { + // pure function call, non-revertable + keccak256::keccak256_rounds_function::( + monotonic_cycle_counter, + query, + memory, + ) + .0 + } + PrecompileAddress::SHA256 => { + // pure function call, non-revertable + sha256::sha256_rounds_function::( + monotonic_cycle_counter, + query, + memory, + ) + .0 + } + PrecompileAddress::Ecrecover => { + // pure function call, non-revertable + ecrecover::ecrecover_function::( + monotonic_cycle_counter, + query, + memory, + ) + .0 + } + }; + + self.precompile_cycles_history + .push((precompile_address, rounds), query.timestamp); + }; + + None } + fn finish_frame(&mut self, _panicked: bool) { - self.default_precompiles_processor.finish_frame(_panicked); + // there are no revertable precompile yes, so we are ok } } diff --git a/core/lib/multivm/src/versions/vm_latest/oracles/storage.rs b/core/lib/multivm/src/versions/vm_latest/oracles/storage.rs index 2b6b5988e06e..b72651c0a9d1 100644 --- a/core/lib/multivm/src/versions/vm_latest/oracles/storage.rs +++ b/core/lib/multivm/src/versions/vm_latest/oracles/storage.rs @@ -59,6 +59,11 @@ pub struct StorageOracle { // Storage refunds that oracle has returned in `estimate_refunds_for_write`. pub(crate) returned_refunds: HistoryRecorder, H>, + + // Keeps track of storage keys that were ever written to. + pub(crate) written_keys: HistoryRecorder, HistoryEnabled>, + // Keeps track of storage keys that were ever read. + pub(crate) read_keys: HistoryRecorder, HistoryEnabled>, } impl OracleWithHistory for StorageOracle { @@ -69,6 +74,8 @@ impl OracleWithHistory for StorageOracle { self.paid_changes.rollback_to_timestamp(timestamp); self.initial_values.rollback_to_timestamp(timestamp); self.returned_refunds.rollback_to_timestamp(timestamp); + self.written_keys.rollback_to_timestamp(timestamp); + self.read_keys.rollback_to_timestamp(timestamp); } } @@ -81,6 +88,8 @@ impl StorageOracle { paid_changes: Default::default(), initial_values: Default::default(), returned_refunds: Default::default(), + written_keys: Default::default(), + read_keys: Default::default(), } } @@ -91,6 +100,8 @@ impl StorageOracle { self.paid_changes.delete_history(); self.initial_values.delete_history(); self.returned_refunds.delete_history(); + self.written_keys.delete_history(); + self.read_keys.delete_history(); } fn is_storage_key_free(&self, key: &StorageKey) -> bool { @@ -108,8 +119,12 @@ impl StorageOracle { } } - pub fn read_value(&mut self, mut query: LogQuery) -> LogQuery { + fn read_value(&mut self, mut query: LogQuery) -> LogQuery { let key = triplet_to_storage_key(query.shard_id, query.address, query.key); + + if !self.read_keys.inner().contains_key(&key) { + self.read_keys.insert(key, (), query.timestamp); + } let current_value = self.storage.read_from_storage(&key); query.read_value = current_value; @@ -127,8 +142,11 @@ impl StorageOracle { query } - pub fn write_value(&mut self, query: LogQuery) -> LogQuery { + fn write_value(&mut self, query: LogQuery) -> LogQuery { let key = triplet_to_storage_key(query.shard_id, query.address, query.key); + if !self.written_keys.inner().contains_key(&key) { + self.written_keys.insert(key, (), query.timestamp); + } let current_value = self.storage .write_to_storage(key, query.written_value, query.timestamp); diff --git a/core/lib/multivm/src/versions/vm_latest/tests/circuits.rs b/core/lib/multivm/src/versions/vm_latest/tests/circuits.rs new file mode 100644 index 000000000000..c76f3dc1c729 --- /dev/null +++ b/core/lib/multivm/src/versions/vm_latest/tests/circuits.rs @@ -0,0 +1,43 @@ +use zksync_types::{Address, Execute, U256}; + +use crate::{ + interface::{TxExecutionMode, VmExecutionMode, VmInterface}, + vm_latest::{constants::BLOCK_GAS_LIMIT, tests::tester::VmTesterBuilder, HistoryEnabled}, +}; + +// Checks that estimated number of circuits for simple transfer doesn't differ much +// from hardcoded expected value. +#[test] +fn test_circuits() { + let mut vm = VmTesterBuilder::new(HistoryEnabled) + .with_empty_in_memory_storage() + .with_random_rich_accounts(1) + .with_deployer() + .with_gas_limit(BLOCK_GAS_LIMIT) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .build(); + + let account = &mut vm.rich_accounts[0]; + let tx = account.get_l2_tx_for_execute( + Execute { + contract_address: Address::random(), + calldata: Vec::new(), + value: U256::from(1u8), + factory_deps: None, + }, + None, + ); + vm.vm.push_transaction(tx); + let res = vm.vm.inspect(Default::default(), VmExecutionMode::OneTx); + + const EXPECTED_CIRCUITS_USED: f32 = 1.5521; + let delta = + (res.statistics.estimated_circuits_used - EXPECTED_CIRCUITS_USED) / EXPECTED_CIRCUITS_USED; + + if delta.abs() > 0.1 { + panic!( + "Estimation differs from expected result by too much: {}%", + delta * 100.0 + ); + } +} diff --git a/core/lib/multivm/src/versions/vm_latest/tests/mod.rs b/core/lib/multivm/src/versions/vm_latest/tests/mod.rs index ffb38dd3725a..95377232b3e3 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/mod.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/mod.rs @@ -4,12 +4,14 @@ mod default_aa; // mod invalid_bytecode; mod bytecode_publishing; mod call_tracer; +mod circuits; mod gas_limit; mod get_used_contracts; mod is_write_initial; mod l1_tx_execution; mod l2_blocks; mod nonce_holder; +mod precompiles; mod refunds; mod require_eip712; mod rollbacks; diff --git a/core/lib/multivm/src/versions/vm_latest/tests/precompiles.rs b/core/lib/multivm/src/versions/vm_latest/tests/precompiles.rs new file mode 100644 index 000000000000..adc62a4bd2d6 --- /dev/null +++ b/core/lib/multivm/src/versions/vm_latest/tests/precompiles.rs @@ -0,0 +1,136 @@ +use zk_evm_1_4_0::zk_evm_abstractions::precompiles::PrecompileAddress; +use zksync_types::{Address, Execute}; + +use crate::{ + interface::{TxExecutionMode, VmExecutionMode, VmInterface}, + vm_latest::{ + constants::BLOCK_GAS_LIMIT, + tests::{tester::VmTesterBuilder, utils::read_precompiles_contract}, + HistoryEnabled, + }, +}; + +#[test] +fn test_keccak() { + // Execute special transaction and check that at least 1000 keccak calls were made. + let contract = read_precompiles_contract(); + let address = Address::random(); + let mut vm = VmTesterBuilder::new(HistoryEnabled) + .with_empty_in_memory_storage() + .with_random_rich_accounts(1) + .with_deployer() + .with_gas_limit(BLOCK_GAS_LIMIT) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_custom_contracts(vec![(contract, address, true)]) + .build(); + + // calldata for `doKeccak(1000)`. + let keccak1000_calldata = + "370f20ac00000000000000000000000000000000000000000000000000000000000003e8"; + + let account = &mut vm.rich_accounts[0]; + let tx = account.get_l2_tx_for_execute( + Execute { + contract_address: address, + calldata: hex::decode(keccak1000_calldata).unwrap(), + value: Default::default(), + factory_deps: None, + }, + None, + ); + vm.vm.push_transaction(tx); + let _ = vm.vm.inspect(Default::default(), VmExecutionMode::OneTx); + + let keccak_count = vm + .vm + .state + .precompiles_processor + .precompile_cycles_history + .inner() + .iter() + .filter(|(precompile, _)| precompile == &PrecompileAddress::Keccak256) + .count(); + + assert!(keccak_count >= 1000); +} + +#[test] +fn test_sha256() { + // Execute special transaction and check that at least 1000 sha256 calls were made. + let contract = read_precompiles_contract(); + let address = Address::random(); + let mut vm = VmTesterBuilder::new(HistoryEnabled) + .with_empty_in_memory_storage() + .with_random_rich_accounts(1) + .with_deployer() + .with_gas_limit(BLOCK_GAS_LIMIT) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_custom_contracts(vec![(contract, address, true)]) + .build(); + + // calldata for `doSha256(1000)`. + let sha1000_calldata = + "5d0b4fb500000000000000000000000000000000000000000000000000000000000003e8"; + + let account = &mut vm.rich_accounts[0]; + let tx = account.get_l2_tx_for_execute( + Execute { + contract_address: address, + calldata: hex::decode(sha1000_calldata).unwrap(), + value: Default::default(), + factory_deps: None, + }, + None, + ); + vm.vm.push_transaction(tx); + let _ = vm.vm.inspect(Default::default(), VmExecutionMode::OneTx); + + let sha_count = vm + .vm + .state + .precompiles_processor + .precompile_cycles_history + .inner() + .iter() + .filter(|(precompile, _)| precompile == &PrecompileAddress::SHA256) + .count(); + + assert!(sha_count >= 1000); +} + +#[test] +fn test_ecrecover() { + // Execute simple transfer and check that exactly 1 ecrecover call was made (it's done during tx validation). + let mut vm = VmTesterBuilder::new(HistoryEnabled) + .with_empty_in_memory_storage() + .with_random_rich_accounts(1) + .with_deployer() + .with_gas_limit(BLOCK_GAS_LIMIT) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .build(); + + let account = &mut vm.rich_accounts[0]; + let tx = account.get_l2_tx_for_execute( + Execute { + contract_address: account.address, + calldata: Vec::new(), + value: Default::default(), + factory_deps: None, + }, + None, + ); + vm.vm.push_transaction(tx); + let _ = vm.vm.inspect(Default::default(), VmExecutionMode::OneTx); + + let ecrecover_count = vm + .vm + .state + .precompiles_processor + .precompile_cycles_history + .inner() + .iter() + .filter(|(precompile, _)| precompile == &PrecompileAddress::Ecrecover) + .count(); + + assert_eq!(ecrecover_count, 1); +} diff --git a/core/lib/multivm/src/versions/vm_latest/tests/utils.rs b/core/lib/multivm/src/versions/vm_latest/tests/utils.rs index c126b50cb574..7c937033a218 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/utils.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/utils.rs @@ -103,3 +103,9 @@ pub(crate) fn read_max_depth_contract() -> Vec { "core/tests/ts-integration/contracts/zkasm/artifacts/deep_stak.zkasm/deep_stak.zkasm.zbin", ) } + +pub(crate) fn read_precompiles_contract() -> Vec { + read_bytecode( + "etc/contracts-test-data/artifacts-zk/contracts/precompiles/precompiles.sol/Precompiles.json", + ) +} diff --git a/core/lib/multivm/src/versions/vm_latest/tracers/circuits_capacity.rs b/core/lib/multivm/src/versions/vm_latest/tracers/circuits_capacity.rs new file mode 100644 index 000000000000..16f5540172a8 --- /dev/null +++ b/core/lib/multivm/src/versions/vm_latest/tracers/circuits_capacity.rs @@ -0,0 +1,85 @@ +use zkevm_test_harness_1_4_0::{geometry_config::get_geometry_config, toolset::GeometryConfig}; + +const GEOMETRY_CONFIG: GeometryConfig = get_geometry_config(); +const OVERESTIMATE_PERCENT: f32 = 1.05; + +const MAIN_VM_CYCLE_FRACTION: f32 = + OVERESTIMATE_PERCENT / GEOMETRY_CONFIG.cycles_per_vm_snapshot as f32; + +const CODE_DECOMMITTER_SORTER_CYCLE_FRACTION: f32 = + OVERESTIMATE_PERCENT / GEOMETRY_CONFIG.cycles_code_decommitter_sorter as f32; + +const LOG_DEMUXER_CYCLE_FRACTION: f32 = + OVERESTIMATE_PERCENT / GEOMETRY_CONFIG.cycles_per_log_demuxer as f32; + +const STORAGE_SORTER_CYCLE_FRACTION: f32 = + OVERESTIMATE_PERCENT / GEOMETRY_CONFIG.cycles_per_storage_sorter as f32; + +const EVENTS_OR_L1_MESSAGES_SORTER_CYCLE_FRACTION: f32 = + OVERESTIMATE_PERCENT / GEOMETRY_CONFIG.cycles_per_events_or_l1_messages_sorter as f32; + +const RAM_PERMUTATION_CYCLE_FRACTION: f32 = + OVERESTIMATE_PERCENT / GEOMETRY_CONFIG.cycles_per_ram_permutation as f32; + +pub(crate) const CODE_DECOMMITTER_CYCLE_FRACTION: f32 = + OVERESTIMATE_PERCENT / GEOMETRY_CONFIG.cycles_per_code_decommitter as f32; + +pub(crate) const STORAGE_APPLICATION_CYCLE_FRACTION: f32 = + OVERESTIMATE_PERCENT / GEOMETRY_CONFIG.cycles_per_storage_application as f32; + +pub(crate) const KECCAK256_CYCLE_FRACTION: f32 = + OVERESTIMATE_PERCENT / GEOMETRY_CONFIG.cycles_per_keccak256_circuit as f32; + +pub(crate) const SHA256_CYCLE_FRACTION: f32 = + OVERESTIMATE_PERCENT / GEOMETRY_CONFIG.cycles_per_sha256_circuit as f32; + +pub(crate) const ECRECOVER_CYCLE_FRACTION: f32 = + OVERESTIMATE_PERCENT / GEOMETRY_CONFIG.cycles_per_ecrecover_circuit as f32; + +// "Rich addressing" opcodes are opcodes that can write their return value/read the input onto the stack +// and so take 1-2 RAM permutations more than an average opcode. +// In the worst case, a rich addressing may take 3 ram permutations +// (1 for reading the opcode, 1 for writing input value, 1 for writing output value). +pub(crate) const RICH_ADDRESSING_OPCODE_FRACTION: f32 = + MAIN_VM_CYCLE_FRACTION + 3.0 * RAM_PERMUTATION_CYCLE_FRACTION; + +pub(crate) const AVERAGE_OPCODE_FRACTION: f32 = + MAIN_VM_CYCLE_FRACTION + RAM_PERMUTATION_CYCLE_FRACTION; + +// Here "base" fraction is a fraction that will be used unconditionally. +// Usage of StorageApplication is being tracked separately as it depends on whether slot was read before or not. +pub(crate) const STORAGE_READ_BASE_FRACTION: f32 = MAIN_VM_CYCLE_FRACTION + + RAM_PERMUTATION_CYCLE_FRACTION + + LOG_DEMUXER_CYCLE_FRACTION + + STORAGE_SORTER_CYCLE_FRACTION; + +pub(crate) const EVENT_OR_L1_MESSAGE_FRACTION: f32 = MAIN_VM_CYCLE_FRACTION + + RAM_PERMUTATION_CYCLE_FRACTION + + 2.0 * LOG_DEMUXER_CYCLE_FRACTION + + 2.0 * EVENTS_OR_L1_MESSAGES_SORTER_CYCLE_FRACTION; + +// Here "base" fraction is a fraction that will be used unconditionally. +// Usage of StorageApplication is being tracked separately as it depends on whether slot was written before or not. +pub(crate) const STORAGE_WRITE_BASE_FRACTION: f32 = MAIN_VM_CYCLE_FRACTION + + RAM_PERMUTATION_CYCLE_FRACTION + + 2.0 * LOG_DEMUXER_CYCLE_FRACTION + + 2.0 * STORAGE_SORTER_CYCLE_FRACTION; + +pub(crate) const FAR_CALL_FRACTION: f32 = MAIN_VM_CYCLE_FRACTION + + RAM_PERMUTATION_CYCLE_FRACTION + + STORAGE_SORTER_CYCLE_FRACTION + + CODE_DECOMMITTER_SORTER_CYCLE_FRACTION; + +// 5 RAM permutations, because: 1 to read opcode + 2 reads + 2 writes. +// 2 reads and 2 writes are needed because unaligned access is implemented with +// aligned queries. +pub(crate) const UMA_WRITE_FRACTION: f32 = + MAIN_VM_CYCLE_FRACTION + 5.0 * RAM_PERMUTATION_CYCLE_FRACTION; + +// 3 RAM permutations, because: 1 to read opcode + 2 reads. +// 2 reads are needed because unaligned access is implemented with aligned queries. +pub(crate) const UMA_READ_FRACTION: f32 = + MAIN_VM_CYCLE_FRACTION + 3.0 * RAM_PERMUTATION_CYCLE_FRACTION; + +pub(crate) const PRECOMPILE_CALL_COMMON_FRACTION: f32 = + MAIN_VM_CYCLE_FRACTION + RAM_PERMUTATION_CYCLE_FRACTION + LOG_DEMUXER_CYCLE_FRACTION; diff --git a/core/lib/multivm/src/versions/vm_latest/tracers/circuits_tracer.rs b/core/lib/multivm/src/versions/vm_latest/tracers/circuits_tracer.rs new file mode 100644 index 000000000000..c2383b11561f --- /dev/null +++ b/core/lib/multivm/src/versions/vm_latest/tracers/circuits_tracer.rs @@ -0,0 +1,199 @@ +use std::marker::PhantomData; + +use zk_evm_1_4_0::{ + tracing::{BeforeExecutionData, VmLocalStateData}, + zk_evm_abstractions::precompiles::PrecompileAddress, + zkevm_opcode_defs::{LogOpcode, Opcode, UMAOpcode}, +}; +use zksync_state::{StoragePtr, WriteStorage}; + +use super::circuits_capacity::*; +use crate::{ + interface::{dyn_tracers::vm_1_4_0::DynTracer, tracer::TracerExecutionStatus}, + vm_latest::{ + bootloader_state::BootloaderState, + old_vm::{ + history_recorder::{HistoryMode, VectorHistoryEvent}, + memory::SimpleMemory, + }, + tracers::traits::VmTracer, + types::internals::ZkSyncVmState, + }, +}; + +/// Tracer responsible for collecting information about refunds. +#[derive(Debug)] +pub(crate) struct CircuitsTracer { + pub(crate) estimated_circuits_used: f32, + last_decommitment_history_entry_checked: Option, + last_written_keys_history_entry_checked: Option, + last_read_keys_history_entry_checked: Option, + last_precompile_history_entry_checked: Option, + _phantom_data: PhantomData, +} + +impl CircuitsTracer { + pub(crate) fn new() -> Self { + Self { + estimated_circuits_used: 0.0, + last_decommitment_history_entry_checked: None, + last_written_keys_history_entry_checked: None, + last_read_keys_history_entry_checked: None, + last_precompile_history_entry_checked: None, + _phantom_data: Default::default(), + } + } +} + +impl DynTracer> for CircuitsTracer { + fn before_execution( + &mut self, + _state: VmLocalStateData<'_>, + data: BeforeExecutionData, + _memory: &SimpleMemory, + _storage: StoragePtr, + ) { + let used = match data.opcode.variant.opcode { + Opcode::Nop(_) + | Opcode::Add(_) + | Opcode::Sub(_) + | Opcode::Mul(_) + | Opcode::Div(_) + | Opcode::Jump(_) + | Opcode::Binop(_) + | Opcode::Shift(_) + | Opcode::Ptr(_) => RICH_ADDRESSING_OPCODE_FRACTION, + Opcode::Context(_) | Opcode::Ret(_) | Opcode::NearCall(_) => AVERAGE_OPCODE_FRACTION, + Opcode::Log(LogOpcode::StorageRead) => STORAGE_READ_BASE_FRACTION, + Opcode::Log(LogOpcode::StorageWrite) => STORAGE_WRITE_BASE_FRACTION, + Opcode::Log(LogOpcode::ToL1Message) | Opcode::Log(LogOpcode::Event) => { + EVENT_OR_L1_MESSAGE_FRACTION + } + Opcode::Log(LogOpcode::PrecompileCall) => PRECOMPILE_CALL_COMMON_FRACTION, + Opcode::FarCall(_) => FAR_CALL_FRACTION, + Opcode::UMA(UMAOpcode::AuxHeapWrite | UMAOpcode::HeapWrite) => UMA_WRITE_FRACTION, + Opcode::UMA( + UMAOpcode::AuxHeapRead | UMAOpcode::HeapRead | UMAOpcode::FatPointerRead, + ) => UMA_READ_FRACTION, + Opcode::Invalid(_) => unreachable!(), // invalid opcodes are never executed + }; + + self.estimated_circuits_used += used; + } +} + +impl VmTracer for CircuitsTracer { + fn initialize_tracer(&mut self, state: &mut ZkSyncVmState) { + self.last_decommitment_history_entry_checked = Some( + state + .decommittment_processor + .decommitted_code_hashes + .history() + .len(), + ); + + self.last_written_keys_history_entry_checked = + Some(state.storage.written_keys.history().len()); + + self.last_read_keys_history_entry_checked = Some(state.storage.read_keys.history().len()); + + self.last_precompile_history_entry_checked = Some( + state + .precompiles_processor + .precompile_cycles_history + .history() + .len(), + ); + } + + fn finish_cycle( + &mut self, + state: &mut ZkSyncVmState, + _bootloader_state: &mut BootloaderState, + ) -> TracerExecutionStatus { + // Trace decommitments. + let last_decommitment_history_entry_checked = self + .last_decommitment_history_entry_checked + .expect("Value must be set during init"); + let history = state + .decommittment_processor + .decommitted_code_hashes + .history(); + for (_, history_event) in &history[last_decommitment_history_entry_checked..] { + // We assume that only insertions may happen during a single VM inspection. + assert!(history_event.value.is_some()); + let bytecode_len = state + .decommittment_processor + .known_bytecodes + .inner() + .get(&history_event.key) + .expect("Bytecode must be known at this point") + .len(); + + // Each cycle of `CodeDecommitter` processes 2 words. + // If the number of words in bytecode is odd, then number of cycles must be rounded up. + let decommitter_cycles_used = (bytecode_len + 1) / 2; + self.estimated_circuits_used += + (decommitter_cycles_used as f32) * CODE_DECOMMITTER_CYCLE_FRACTION; + } + self.last_decommitment_history_entry_checked = Some(history.len()); + + // Process storage writes. + let last_writes_history_entry_checked = self + .last_written_keys_history_entry_checked + .expect("Value must be set during init"); + let history = state.storage.written_keys.history(); + for (_, history_event) in &history[last_writes_history_entry_checked..] { + // We assume that only insertions may happen during a single VM inspection. + assert!(history_event.value.is_some()); + + self.estimated_circuits_used += 2.0 * STORAGE_APPLICATION_CYCLE_FRACTION; + } + self.last_written_keys_history_entry_checked = Some(history.len()); + + // Process storage reads. + let last_reads_history_entry_checked = self + .last_read_keys_history_entry_checked + .expect("Value must be set during init"); + let history = state.storage.read_keys.history(); + for (_, history_event) in &history[last_reads_history_entry_checked..] { + // We assume that only insertions may happen during a single VM inspection. + assert!(history_event.value.is_some()); + + // If the slot is already written to, then we've already taken 2 cycles into account. + if !state + .storage + .written_keys + .inner() + .contains_key(&history_event.key) + { + self.estimated_circuits_used += STORAGE_APPLICATION_CYCLE_FRACTION; + } + } + self.last_read_keys_history_entry_checked = Some(history.len()); + + // Process precompiles. + let last_precompile_history_entry_checked = self + .last_precompile_history_entry_checked + .expect("Value must be set during init"); + let history = state + .precompiles_processor + .precompile_cycles_history + .history(); + for (_, history_event) in &history[last_precompile_history_entry_checked..] { + if let VectorHistoryEvent::Push((precompile, cycles)) = history_event { + let fraction = match precompile { + PrecompileAddress::Ecrecover => ECRECOVER_CYCLE_FRACTION, + PrecompileAddress::SHA256 => SHA256_CYCLE_FRACTION, + PrecompileAddress::Keccak256 => KECCAK256_CYCLE_FRACTION, + }; + self.estimated_circuits_used += (*cycles as f32) * fraction; + } else { + panic!("Precompile calls should not be rolled back"); + } + } + self.last_precompile_history_entry_checked = Some(history.len()); + + TracerExecutionStatus::Continue + } +} diff --git a/core/lib/multivm/src/versions/vm_latest/tracers/default_tracers.rs b/core/lib/multivm/src/versions/vm_latest/tracers/default_tracers.rs index 0e18d989af62..018272365f8f 100644 --- a/core/lib/multivm/src/versions/vm_latest/tracers/default_tracers.rs +++ b/core/lib/multivm/src/versions/vm_latest/tracers/default_tracers.rs @@ -32,7 +32,7 @@ use crate::{ computational_gas_price, gas_spent_on_bytecodes_and_long_messages_this_opcode, print_debug_if_needed, VmHook, }, - RefundsTracer, ResultTracer, + CircuitsTracer, RefundsTracer, ResultTracer, }, types::internals::ZkSyncVmState, VmTracer, @@ -62,6 +62,11 @@ pub(crate) struct DefaultExecutionTracer { pub(crate) pubdata_tracer: Option>, pub(crate) dispatcher: TracerDispatcher, ret_from_the_bootloader: Option, + // This tracer tracks what opcodes were executed and calculates how much circuits will be generated. + // It only takes into account circuits that are generated for actual execution. It doesn't + // take into account e.g circuits produced by the initial bootloader memory commitment. + pub(crate) circuits_tracer: CircuitsTracer, + storage: StoragePtr, _phantom: PhantomData, } @@ -88,6 +93,7 @@ impl DefaultExecutionTracer { dispatcher, pubdata_tracer, ret_from_the_bootloader: None, + circuits_tracer: CircuitsTracer::new(), storage, _phantom: PhantomData, } @@ -161,14 +167,15 @@ impl Debug for DefaultExecutionTracer { /// The macro passes the function call to all tracers. macro_rules! dispatch_tracers { ($self:ident.$function:ident($( $params:expr ),*)) => { - $self.result_tracer.$function($( $params ),*); - $self.dispatcher.$function($( $params ),*); + $self.result_tracer.$function($( $params ),*); + $self.dispatcher.$function($( $params ),*); if let Some(tracer) = &mut $self.refund_tracer { tracer.$function($( $params ),*); } if let Some(tracer) = &mut $self.pubdata_tracer { tracer.$function($( $params ),*); } + $self.circuits_tracer.$function($( $params ),*); }; } diff --git a/core/lib/multivm/src/versions/vm_latest/tracers/mod.rs b/core/lib/multivm/src/versions/vm_latest/tracers/mod.rs index 33d043de6eb1..1bdb1b6ccdbf 100644 --- a/core/lib/multivm/src/versions/vm_latest/tracers/mod.rs +++ b/core/lib/multivm/src/versions/vm_latest/tracers/mod.rs @@ -1,13 +1,16 @@ +pub(crate) use circuits_tracer::CircuitsTracer; pub(crate) use default_tracers::DefaultExecutionTracer; pub(crate) use pubdata_tracer::PubdataTracer; pub(crate) use refunds::RefundsTracer; pub(crate) use result_tracer::ResultTracer; +pub(crate) mod circuits_tracer; pub(crate) mod default_tracers; pub(crate) mod pubdata_tracer; pub(crate) mod refunds; pub(crate) mod result_tracer; +mod circuits_capacity; pub mod dispatcher; pub(crate) mod traits; pub(crate) mod utils; diff --git a/core/lib/multivm/src/versions/vm_latest/types/internals/vm_state.rs b/core/lib/multivm/src/versions/vm_latest/types/internals/vm_state.rs index da9c81321440..2430e163fe87 100644 --- a/core/lib/multivm/src/versions/vm_latest/types/internals/vm_state.rs +++ b/core/lib/multivm/src/versions/vm_latest/types/internals/vm_state.rs @@ -40,7 +40,7 @@ pub type ZkSyncVmState = VmState< StorageOracle, SimpleMemory, InMemoryEventSink, - PrecompilesProcessorWithHistory, + PrecompilesProcessorWithHistory, DecommitterOracle, DummyTracer, >; @@ -84,7 +84,7 @@ pub(crate) fn new_vm_state( let storage_oracle: StorageOracle = StorageOracle::new(storage.clone()); let mut memory = SimpleMemory::default(); let event_sink = InMemoryEventSink::default(); - let precompiles_processor = PrecompilesProcessorWithHistory::::default(); + let precompiles_processor = PrecompilesProcessorWithHistory::::default(); let mut decommittment_processor: DecommitterOracle = DecommitterOracle::new(storage); diff --git a/core/lib/multivm/src/versions/vm_refunds_enhancement/implementation/statistics.rs b/core/lib/multivm/src/versions/vm_refunds_enhancement/implementation/statistics.rs index 3e9de5de4ec0..d64e71c3ff20 100644 --- a/core/lib/multivm/src/versions/vm_refunds_enhancement/implementation/statistics.rs +++ b/core/lib/multivm/src/versions/vm_refunds_enhancement/implementation/statistics.rs @@ -40,6 +40,7 @@ impl Vm { computational_gas_used, total_log_queries: total_log_queries_count, pubdata_published, + estimated_circuits_used: 0.0, } } diff --git a/core/lib/multivm/src/versions/vm_virtual_blocks/implementation/statistics.rs b/core/lib/multivm/src/versions/vm_virtual_blocks/implementation/statistics.rs index 074e8dae56ed..7657babfe4a4 100644 --- a/core/lib/multivm/src/versions/vm_virtual_blocks/implementation/statistics.rs +++ b/core/lib/multivm/src/versions/vm_virtual_blocks/implementation/statistics.rs @@ -40,6 +40,7 @@ impl Vm { total_log_queries: total_log_queries_count, // This field will be populated by the RefundTracer pubdata_published: 0, + estimated_circuits_used: 0.0, } } diff --git a/core/lib/state/src/test_utils.rs b/core/lib/state/src/test_utils.rs index 2a5664ae0602..21c56678586a 100644 --- a/core/lib/state/src/test_utils.rs +++ b/core/lib/state/src/test_utils.rs @@ -105,7 +105,7 @@ pub(crate) async fn create_l1_batch( ); header.is_finished = true; conn.blocks_dal() - .insert_l1_batch(&header, &[], BlockGasCount::default(), &[], &[]) + .insert_l1_batch(&header, &[], BlockGasCount::default(), &[], &[], 0) .await .unwrap(); conn.blocks_dal() diff --git a/core/lib/types/src/fee.rs b/core/lib/types/src/fee.rs index 53e05fbb59a9..fad4d09f5280 100644 --- a/core/lib/types/src/fee.rs +++ b/core/lib/types/src/fee.rs @@ -24,6 +24,7 @@ pub struct TransactionExecutionMetrics { pub computational_gas_used: u32, pub total_updated_values_size: usize, pub pubdata_published: u32, + pub estimated_circuits_used: f32, } #[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] diff --git a/core/lib/types/src/tx/tx_execution_info.rs b/core/lib/types/src/tx/tx_execution_info.rs index d19757ee970b..968a56d6c55a 100644 --- a/core/lib/types/src/tx/tx_execution_info.rs +++ b/core/lib/types/src/tx/tx_execution_info.rs @@ -69,6 +69,7 @@ pub struct ExecutionMetrics { pub cycles_used: u32, pub computational_gas_used: u32, pub pubdata_published: u32, + pub estimated_circuits_used: f32, } impl ExecutionMetrics { @@ -86,6 +87,7 @@ impl ExecutionMetrics { cycles_used: tx_metrics.cycles_used, computational_gas_used: tx_metrics.computational_gas_used, pubdata_published: tx_metrics.pubdata_published, + estimated_circuits_used: tx_metrics.estimated_circuits_used, } } @@ -119,6 +121,7 @@ impl Add for ExecutionMetrics { cycles_used: self.cycles_used + other.cycles_used, computational_gas_used: self.computational_gas_used + other.computational_gas_used, pubdata_published: self.pubdata_published + other.pubdata_published, + estimated_circuits_used: self.estimated_circuits_used + other.estimated_circuits_used, } } } diff --git a/core/lib/zksync_core/src/api_server/execution_sandbox/vm_metrics.rs b/core/lib/zksync_core/src/api_server/execution_sandbox/vm_metrics.rs index 6842fe438f83..82e082d4dd81 100644 --- a/core/lib/zksync_core/src/api_server/execution_sandbox/vm_metrics.rs +++ b/core/lib/zksync_core/src/api_server/execution_sandbox/vm_metrics.rs @@ -240,5 +240,6 @@ pub(super) fn collect_tx_execution_metrics( computational_gas_used: result.statistics.computational_gas_used, total_updated_values_size: writes_metrics.total_updated_values_size, pubdata_published: result.statistics.pubdata_published, + estimated_circuits_used: result.statistics.estimated_circuits_used, } } diff --git a/core/lib/zksync_core/src/api_server/web3/tests/snapshots.rs b/core/lib/zksync_core/src/api_server/web3/tests/snapshots.rs index e3d233777310..70ad7d28fa25 100644 --- a/core/lib/zksync_core/src/api_server/web3/tests/snapshots.rs +++ b/core/lib/zksync_core/src/api_server/web3/tests/snapshots.rs @@ -15,7 +15,7 @@ async fn seal_l1_batch( let header = create_l1_batch(number.0); storage .blocks_dal() - .insert_l1_batch(&header, &[], BlockGasCount::default(), &[], &[]) + .insert_l1_batch(&header, &[], BlockGasCount::default(), &[], &[], 0) .await?; storage .blocks_dal() diff --git a/core/lib/zksync_core/src/consistency_checker/tests/mod.rs b/core/lib/zksync_core/src/consistency_checker/tests/mod.rs index 1dac556ec6a7..e8cc9f2c26cc 100644 --- a/core/lib/zksync_core/src/consistency_checker/tests/mod.rs +++ b/core/lib/zksync_core/src/consistency_checker/tests/mod.rs @@ -195,7 +195,7 @@ impl SaveAction<'_> { Self::InsertBatch(l1_batch) => { storage .blocks_dal() - .insert_l1_batch(&l1_batch.header, &[], BlockGasCount::default(), &[], &[]) + .insert_l1_batch(&l1_batch.header, &[], BlockGasCount::default(), &[], &[], 0) .await .unwrap(); } diff --git a/core/lib/zksync_core/src/eth_sender/tests.rs b/core/lib/zksync_core/src/eth_sender/tests.rs index 62a5808e2b7b..ad9764ce76e1 100644 --- a/core/lib/zksync_core/src/eth_sender/tests.rs +++ b/core/lib/zksync_core/src/eth_sender/tests.rs @@ -887,7 +887,7 @@ async fn insert_l1_batch(tester: &EthSenderTester, number: L1BatchNumber) -> L1B .storage() .await .blocks_dal() - .insert_l1_batch(&header, &[], Default::default(), &[], &[]) + .insert_l1_batch(&header, &[], Default::default(), &[], &[], 0) .await .unwrap(); tester diff --git a/core/lib/zksync_core/src/genesis.rs b/core/lib/zksync_core/src/genesis.rs index cbd08e329cb0..7172968e0ef3 100644 --- a/core/lib/zksync_core/src/genesis.rs +++ b/core/lib/zksync_core/src/genesis.rs @@ -315,6 +315,7 @@ pub(crate) async fn create_genesis_l1_batch( BlockGasCount::default(), &[], &[], + 0, ) .await .unwrap(); diff --git a/core/lib/zksync_core/src/metadata_calculator/recovery/tests.rs b/core/lib/zksync_core/src/metadata_calculator/recovery/tests.rs index ee2fc0bb8a76..1b729a06cd44 100644 --- a/core/lib/zksync_core/src/metadata_calculator/recovery/tests.rs +++ b/core/lib/zksync_core/src/metadata_calculator/recovery/tests.rs @@ -400,7 +400,7 @@ async fn prepare_clean_recovery_snapshot( ); storage .blocks_dal() - .insert_l1_batch(&l1_batch, &[], Default::default(), &[], &[]) + .insert_l1_batch(&l1_batch, &[], Default::default(), &[], &[], 0) .await .unwrap(); diff --git a/core/lib/zksync_core/src/metadata_calculator/tests.rs b/core/lib/zksync_core/src/metadata_calculator/tests.rs index eb9a5693a4a4..da158ff11ef9 100644 --- a/core/lib/zksync_core/src/metadata_calculator/tests.rs +++ b/core/lib/zksync_core/src/metadata_calculator/tests.rs @@ -288,6 +288,7 @@ async fn test_postgres_backup_recovery( BlockGasCount::default(), &[], &[], + 0, ) .await .unwrap(); @@ -313,7 +314,7 @@ async fn test_postgres_backup_recovery( for batch_header in &removed_batches { let mut txn = storage.start_transaction().await.unwrap(); txn.blocks_dal() - .insert_l1_batch(batch_header, &[], BlockGasCount::default(), &[], &[]) + .insert_l1_batch(batch_header, &[], BlockGasCount::default(), &[], &[], 0) .await .unwrap(); insert_initial_writes_for_batch(&mut txn, batch_header.number).await; @@ -496,7 +497,7 @@ pub(super) async fn extend_db_state_from_l1_batch( storage .blocks_dal() - .insert_l1_batch(&header, &[], BlockGasCount::default(), &[], &[]) + .insert_l1_batch(&header, &[], BlockGasCount::default(), &[], &[], 0) .await .unwrap(); storage diff --git a/core/lib/zksync_core/src/reorg_detector/tests.rs b/core/lib/zksync_core/src/reorg_detector/tests.rs index dc5d0711445f..f9495286b1f8 100644 --- a/core/lib/zksync_core/src/reorg_detector/tests.rs +++ b/core/lib/zksync_core/src/reorg_detector/tests.rs @@ -36,7 +36,7 @@ async fn seal_l1_batch(storage: &mut StorageProcessor<'_>, number: u32, hash: H2 let header = create_l1_batch(number); storage .blocks_dal() - .insert_l1_batch(&header, &[], BlockGasCount::default(), &[], &[]) + .insert_l1_batch(&header, &[], BlockGasCount::default(), &[], &[], 0) .await .unwrap(); storage diff --git a/core/lib/zksync_core/src/state_keeper/io/seal_logic.rs b/core/lib/zksync_core/src/state_keeper/io/seal_logic.rs index 15f10ce3ce7d..908e24fd2d5f 100644 --- a/core/lib/zksync_core/src/state_keeper/io/seal_logic.rs +++ b/core/lib/zksync_core/src/state_keeper/io/seal_logic.rs @@ -149,6 +149,10 @@ impl UpdatesManager { self.l1_batch.l1_gas_count, &events_queue, &finished_batch.final_execution_state.storage_refunds, + self.l1_batch + .block_execution_metrics + .estimated_circuits_used + .ceil() as u32, ) .await .unwrap(); diff --git a/core/lib/zksync_core/src/state_keeper/io/tests/tester.rs b/core/lib/zksync_core/src/state_keeper/io/tests/tester.rs index 2f2a38577998..3adf1f3bff06 100644 --- a/core/lib/zksync_core/src/state_keeper/io/tests/tester.rs +++ b/core/lib/zksync_core/src/state_keeper/io/tests/tester.rs @@ -146,7 +146,7 @@ impl Tester { let mut storage = pool.access_storage_tagged("state_keeper").await.unwrap(); storage .blocks_dal() - .insert_l1_batch(&batch_header, &[], Default::default(), &[], &[]) + .insert_l1_batch(&batch_header, &[], Default::default(), &[], &[], 0) .await .unwrap(); storage diff --git a/core/lib/zksync_core/src/state_keeper/seal_criteria/conditional_sealer.rs b/core/lib/zksync_core/src/state_keeper/seal_criteria/conditional_sealer.rs index f239f108f5ba..cc7ba37ef9c8 100644 --- a/core/lib/zksync_core/src/state_keeper/seal_criteria/conditional_sealer.rs +++ b/core/lib/zksync_core/src/state_keeper/seal_criteria/conditional_sealer.rs @@ -133,12 +133,8 @@ impl SequencerSealer { Box::new(criteria::SlotsCriterion), Box::new(criteria::GasCriterion), Box::new(criteria::PubDataBytesCriterion), - Box::new(criteria::InitialWritesCriterion), - Box::new(criteria::RepeatedWritesCriterion), - Box::new(criteria::MaxCyclesCriterion), - Box::new(criteria::ComputationalGasCriterion), + Box::new(criteria::CircuitsCriterion), Box::new(criteria::TxEncodingSizeCriterion), - Box::new(criteria::L2ToL1LogsCriterion), ] } } diff --git a/core/lib/zksync_core/src/state_keeper/seal_criteria/criteria/geometry_seal_criteria.rs b/core/lib/zksync_core/src/state_keeper/seal_criteria/criteria/geometry_seal_criteria.rs index 9f99554e58a2..7878621a7296 100644 --- a/core/lib/zksync_core/src/state_keeper/seal_criteria/criteria/geometry_seal_criteria.rs +++ b/core/lib/zksync_core/src/state_keeper/seal_criteria/criteria/geometry_seal_criteria.rs @@ -1,12 +1,7 @@ use std::fmt; -use multivm::vm_latest::constants::{ERGS_PER_CIRCUIT, MAX_CYCLES_FOR_TX}; use zksync_config::configs::chain::StateKeeperConfig; -use zksync_types::{ - circuit::{GEOMETRY_CONFIG, SCHEDULER_UPPER_BOUND}, - tx::tx_execution_info::{DeduplicatedWritesMetrics, ExecutionMetrics}, - ProtocolVersionId, -}; +use zksync_types::{tx::tx_execution_info::ExecutionMetrics, ProtocolVersionId}; // Local uses use crate::state_keeper::seal_criteria::{SealCriterion, SealData, SealResolution}; @@ -15,20 +10,12 @@ use crate::state_keeper::seal_criteria::{SealCriterion, SealData, SealResolution // Otherwise witness generation will fail and proof won't be generated. #[derive(Debug, Default)] -pub struct RepeatedWritesCriterion; -#[derive(Debug, Default)] -pub struct InitialWritesCriterion; -#[derive(Debug, Default)] -pub struct MaxCyclesCriterion; -#[derive(Debug, Default)] -pub struct ComputationalGasCriterion; -#[derive(Debug, Default)] -pub struct L2ToL1LogsCriterion; +pub struct CircuitsCriterion; trait MetricExtractor { const PROM_METRIC_CRITERION_NAME: &'static str; fn limit_per_block(protocol_version: ProtocolVersionId) -> usize; - fn extract(metric: &ExecutionMetrics, writes: &DeduplicatedWritesMetrics) -> usize; + fn extract(metric: &ExecutionMetrics) -> usize; } impl SealCriterion for T @@ -51,15 +38,13 @@ where * config.close_block_at_geometry_percentage) .round(); - if T::extract(&tx_data.execution_metrics, &tx_data.writes_metrics) > reject_bound as usize { + if T::extract(&tx_data.execution_metrics) > reject_bound as usize { SealResolution::Unexecutable("ZK proof cannot be generated for a transaction".into()) - } else if T::extract(&block_data.execution_metrics, &block_data.writes_metrics) + } else if T::extract(&block_data.execution_metrics) >= T::limit_per_block(protocol_version_id) { SealResolution::ExcludeAndSeal - } else if T::extract(&block_data.execution_metrics, &block_data.writes_metrics) - > close_bound as usize - { + } else if T::extract(&block_data.execution_metrics) > close_bound as usize { SealResolution::IncludeAndSeal } else { SealResolution::NoSeal @@ -71,85 +56,21 @@ where } } -impl MetricExtractor for RepeatedWritesCriterion { - const PROM_METRIC_CRITERION_NAME: &'static str = "repeated_storage_writes"; - - fn limit_per_block(protocol_version_id: ProtocolVersionId) -> usize { - if protocol_version_id.is_pre_boojum() { - GEOMETRY_CONFIG.limit_for_repeated_writes_pubdata_hasher as usize - } else { - // In boojum there is no limit for repeated writes. - usize::MAX - } - } - - fn extract(_metrics: &ExecutionMetrics, writes: &DeduplicatedWritesMetrics) -> usize { - writes.repeated_storage_writes - } -} - -impl MetricExtractor for InitialWritesCriterion { - const PROM_METRIC_CRITERION_NAME: &'static str = "initial_storage_writes"; - - fn limit_per_block(protocol_version_id: ProtocolVersionId) -> usize { - if protocol_version_id.is_pre_boojum() { - GEOMETRY_CONFIG.limit_for_initial_writes_pubdata_hasher as usize - } else { - // In boojum there is no limit for initial writes. - usize::MAX - } - } - - fn extract(_metrics: &ExecutionMetrics, writes: &DeduplicatedWritesMetrics) -> usize { - writes.initial_storage_writes - } -} - -impl MetricExtractor for MaxCyclesCriterion { - const PROM_METRIC_CRITERION_NAME: &'static str = "max_cycles"; - - fn limit_per_block(_protocol_version_id: ProtocolVersionId) -> usize { - MAX_CYCLES_FOR_TX as usize - } - - fn extract(metrics: &ExecutionMetrics, _writes: &DeduplicatedWritesMetrics) -> usize { - metrics.cycles_used as usize - } -} - -impl MetricExtractor for ComputationalGasCriterion { - const PROM_METRIC_CRITERION_NAME: &'static str = "computational_gas"; +impl MetricExtractor for CircuitsCriterion { + const PROM_METRIC_CRITERION_NAME: &'static str = "circuits"; fn limit_per_block(_protocol_version_id: ProtocolVersionId) -> usize { // We subtract constant to take into account that circuits may be not fully filled. // This constant should be greater than number of circuits types // but we keep it larger to be on the safe side. - const MARGIN_NUMBER_OF_CIRCUITS: usize = 100; - const MAX_NUMBER_OF_MUTLIINSTANCE_CIRCUITS: usize = - SCHEDULER_UPPER_BOUND as usize - MARGIN_NUMBER_OF_CIRCUITS; + const MARGIN_NUMBER_OF_CIRCUITS: usize = 10000; + const MAX_NUMBER_OF_CIRCUITS: usize = (1 << 14) + (1 << 13) - MARGIN_NUMBER_OF_CIRCUITS; - MAX_NUMBER_OF_MUTLIINSTANCE_CIRCUITS * ERGS_PER_CIRCUIT as usize + MAX_NUMBER_OF_CIRCUITS } - fn extract(metrics: &ExecutionMetrics, _writes: &DeduplicatedWritesMetrics) -> usize { - metrics.computational_gas_used as usize - } -} - -impl MetricExtractor for L2ToL1LogsCriterion { - const PROM_METRIC_CRITERION_NAME: &'static str = "l2_to_l1_logs"; - - fn limit_per_block(protocol_version_id: ProtocolVersionId) -> usize { - if protocol_version_id.is_pre_boojum() { - GEOMETRY_CONFIG.limit_for_l1_messages_merklizer as usize - } else { - // In boojum there is no limit for L2 to L1 logs. - usize::MAX - } - } - - fn extract(metrics: &ExecutionMetrics, _writes: &DeduplicatedWritesMetrics) -> usize { - metrics.l2_to_l1_logs + fn extract(metrics: &ExecutionMetrics) -> usize { + metrics.estimated_circuits_used.ceil() as usize } } @@ -167,7 +88,6 @@ mod tests { fn test_no_seal_block_resolution( block_execution_metrics: ExecutionMetrics, - block_writes_metrics: DeduplicatedWritesMetrics, criterion: &dyn SealCriterion, protocol_version: ProtocolVersionId, ) { @@ -178,7 +98,6 @@ mod tests { 0, &SealData { execution_metrics: block_execution_metrics, - writes_metrics: block_writes_metrics, ..SealData::default() }, &SealData::default(), @@ -189,7 +108,6 @@ mod tests { fn test_include_and_seal_block_resolution( block_execution_metrics: ExecutionMetrics, - block_writes_metrics: DeduplicatedWritesMetrics, criterion: &dyn SealCriterion, protocol_version: ProtocolVersionId, ) { @@ -200,7 +118,6 @@ mod tests { 0, &SealData { execution_metrics: block_execution_metrics, - writes_metrics: block_writes_metrics, ..SealData::default() }, &SealData::default(), @@ -211,7 +128,6 @@ mod tests { fn test_exclude_and_seal_block_resolution( block_execution_metrics: ExecutionMetrics, - block_writes_metrics: DeduplicatedWritesMetrics, criterion: &dyn SealCriterion, protocol_version: ProtocolVersionId, ) { @@ -222,7 +138,6 @@ mod tests { 0, &SealData { execution_metrics: block_execution_metrics, - writes_metrics: block_writes_metrics, ..SealData::default() }, &SealData::default(), @@ -233,7 +148,6 @@ mod tests { fn test_unexecutable_tx_resolution( tx_execution_metrics: ExecutionMetrics, - tx_writes_metrics: DeduplicatedWritesMetrics, criterion: &dyn SealCriterion, protocol_version: ProtocolVersionId, ) { @@ -245,7 +159,6 @@ mod tests { &SealData::default(), &SealData { execution_metrics: tx_execution_metrics, - writes_metrics: tx_writes_metrics, ..SealData::default() }, protocol_version, @@ -260,17 +173,11 @@ mod tests { macro_rules! test_scenario_execution_metrics { ($criterion: tt, $metric_name: ident, $metric_type: ty, $protocol_version: expr) => { let config = get_config(); - let writes_metrics = DeduplicatedWritesMetrics::default(); let block_execution_metrics = ExecutionMetrics { $metric_name: ($criterion::limit_per_block($protocol_version) / 2) as $metric_type, ..ExecutionMetrics::default() }; - test_no_seal_block_resolution( - block_execution_metrics, - writes_metrics, - &$criterion, - $protocol_version, - ); + test_no_seal_block_resolution(block_execution_metrics, &$criterion, $protocol_version); let block_execution_metrics = ExecutionMetrics { $metric_name: ($criterion::limit_per_block($protocol_version) - 1) as $metric_type, @@ -279,7 +186,6 @@ mod tests { test_include_and_seal_block_resolution( block_execution_metrics, - writes_metrics, &$criterion, $protocol_version, ); @@ -291,7 +197,6 @@ mod tests { test_exclude_and_seal_block_resolution( block_execution_metrics, - writes_metrics, &$criterion, $protocol_version, ); @@ -304,117 +209,16 @@ mod tests { ..ExecutionMetrics::default() }; - test_unexecutable_tx_resolution( - tx_execution_metrics, - writes_metrics, - &$criterion, - $protocol_version, - ); - }; - } - - macro_rules! test_scenario_writes_metrics { - ($criterion:tt, $metric_name:ident, $metric_type:ty, $protocol_version:expr) => { - let config = get_config(); - let execution_metrics = ExecutionMetrics::default(); - let block_writes_metrics = DeduplicatedWritesMetrics { - $metric_name: ($criterion::limit_per_block($protocol_version) / 2) as $metric_type, - ..Default::default() - }; - test_no_seal_block_resolution( - execution_metrics, - block_writes_metrics, - &$criterion, - $protocol_version, - ); - - let block_writes_metrics = DeduplicatedWritesMetrics { - $metric_name: ($criterion::limit_per_block($protocol_version) - 1) as $metric_type, - ..Default::default() - }; - - test_include_and_seal_block_resolution( - execution_metrics, - block_writes_metrics, - &$criterion, - $protocol_version, - ); - - let block_writes_metrics = DeduplicatedWritesMetrics { - $metric_name: ($criterion::limit_per_block($protocol_version)) as $metric_type, - ..Default::default() - }; - - test_exclude_and_seal_block_resolution( - execution_metrics, - block_writes_metrics, - &$criterion, - $protocol_version, - ); - - let tx_writes_metrics = DeduplicatedWritesMetrics { - $metric_name: ($criterion::limit_per_block($protocol_version) as f64 - * config.reject_tx_at_geometry_percentage - + 1f64) - .round() as $metric_type, - ..Default::default() - }; - - test_unexecutable_tx_resolution( - execution_metrics, - tx_writes_metrics, - &$criterion, - $protocol_version, - ); + test_unexecutable_tx_resolution(tx_execution_metrics, &$criterion, $protocol_version); }; } - #[test] - fn repeated_writes_seal_criterion() { - test_scenario_writes_metrics!( - RepeatedWritesCriterion, - repeated_storage_writes, - usize, - ProtocolVersionId::Version17 - ); - } - - #[test] - fn initial_writes_seal_criterion() { - test_scenario_writes_metrics!( - InitialWritesCriterion, - initial_storage_writes, - usize, - ProtocolVersionId::Version17 - ); - } - - #[test] - fn max_cycles_seal_criterion() { - test_scenario_execution_metrics!( - MaxCyclesCriterion, - cycles_used, - u32, - ProtocolVersionId::Version17 - ); - } - #[test] fn computational_gas_seal_criterion() { test_scenario_execution_metrics!( - ComputationalGasCriterion, - computational_gas_used, - u32, - ProtocolVersionId::Version17 - ); - } - - #[test] - fn l2_to_l1_logs_seal_criterion() { - test_scenario_execution_metrics!( - L2ToL1LogsCriterion, - l2_to_l1_logs, - usize, + CircuitsCriterion, + estimated_circuits_used, + f32, ProtocolVersionId::Version17 ); } diff --git a/core/lib/zksync_core/src/state_keeper/seal_criteria/criteria/mod.rs b/core/lib/zksync_core/src/state_keeper/seal_criteria/criteria/mod.rs index 8e0d89e8e0f2..4e30f2a8b608 100644 --- a/core/lib/zksync_core/src/state_keeper/seal_criteria/criteria/mod.rs +++ b/core/lib/zksync_core/src/state_keeper/seal_criteria/criteria/mod.rs @@ -5,12 +5,7 @@ mod slots; mod tx_encoding_size; pub(in crate::state_keeper) use self::{ - gas::GasCriterion, - geometry_seal_criteria::{ - ComputationalGasCriterion, InitialWritesCriterion, L2ToL1LogsCriterion, MaxCyclesCriterion, - RepeatedWritesCriterion, - }, - pubdata_bytes::PubDataBytesCriterion, - slots::SlotsCriterion, + gas::GasCriterion, geometry_seal_criteria::CircuitsCriterion, + pubdata_bytes::PubDataBytesCriterion, slots::SlotsCriterion, tx_encoding_size::TxEncodingSizeCriterion, }; diff --git a/core/lib/zksync_core/src/state_keeper/tests/mod.rs b/core/lib/zksync_core/src/state_keeper/tests/mod.rs index b9d8da1e7e12..afe11b9367f3 100644 --- a/core/lib/zksync_core/src/state_keeper/tests/mod.rs +++ b/core/lib/zksync_core/src/state_keeper/tests/mod.rs @@ -146,6 +146,7 @@ pub(super) fn create_execution_result( computational_gas_used: 0, total_log_queries, pubdata_published: 0, + estimated_circuits_used: 0.0, }, refunds: Refunds::default(), } diff --git a/etc/contracts-test-data/contracts/precompiles/precompiles.sol b/etc/contracts-test-data/contracts/precompiles/precompiles.sol new file mode 100644 index 000000000000..d9e23c46a6fa --- /dev/null +++ b/etc/contracts-test-data/contracts/precompiles/precompiles.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity ^0.8.0; + +contract Precompiles { + function doKeccak(uint256 iters) public pure returns (uint256) { + uint256 sum = 0; + for (uint256 i = 0; i < iters; i += 1) { + sum += uint(keccak256(abi.encode(i))) % 256; + } + return sum; + } + + function doSha256(uint256 iters) public pure returns (uint256) { + uint256 sum = 0; + for (uint256 i = 0; i < iters; i += 1) { + sum += uint(sha256(abi.encode(i))) % 256; + } + return sum; + } +} diff --git a/prover/Cargo.lock b/prover/Cargo.lock index 27ac29283485..1ed73bdb4d1d 100644 --- a/prover/Cargo.lock +++ b/prover/Cargo.lock @@ -6671,7 +6671,7 @@ dependencies = [ [[package]] name = "zk_evm_abstractions" version = "0.1.0" -source = "git+https://github.com/matter-labs/era-zk_evm_abstractions.git#7502a661d7d38906d849dcd3e7a15e5848af6581" +source = "git+https://github.com/matter-labs/era-zk_evm_abstractions.git#32dd320953841aa78579d9da08abbc70bcaed175" dependencies = [ "anyhow", "serde",