From 6cc54555972804be4cd2ca118f0e425c490fbfca Mon Sep 17 00:00:00 2001 From: pompon0 Date: Thu, 13 Jun 2024 10:30:28 +0200 Subject: [PATCH] feat: verification of L1Batch witness (BFT-471) (#2019) Verification of an L1 batch, based on StoredBatchInfo. We extract the hash of the last block of the transaction via merkle path from the root_state (for batch n and n-1) and we recompute the rolling block hash based on transactions in the payload. The verification is not perfect as there are still some fields in payload for which we don't have a commitment - we should address that later. Verification will be used for syncing L1Batches over p2p network (not implemented yet). Also removed serde serialization from TransactionRequest which is unused - we don't need to maintain encoding compatibility for it. --- Cargo.lock | 9 + .../src/intrinsic_costs.rs | 6 +- .../system-constants-generator/src/utils.rs | 8 +- .../src/eip712_signature/typed_structure.rs | 2 +- .../src/eip712_signature/utils.rs | 2 +- ...2d457914c737660b37e9f66b576bbc9a7904.json} | 5 +- ...5a9ac877fdd28bda99661e423405e695223d.json} | 5 +- core/lib/dal/src/consensus/mod.rs | 10 +- core/lib/dal/src/consensus/proto/mod.proto | 4 +- core/lib/dal/src/consensus_dal.rs | 61 ++-- core/lib/dal/src/models/tests.rs | 2 +- core/lib/dal/src/sync_dal.rs | 43 ++- core/lib/dal/src/tests/mod.rs | 6 +- core/lib/dal/src/transactions_web3_dal.rs | 62 +++- core/lib/mempool/src/tests.rs | 4 +- core/lib/merkle_tree/src/getters.rs | 8 +- core/lib/merkle_tree/src/hasher/proofs.rs | 18 +- .../tests/integration/merkle_tree.rs | 10 +- .../types/outputs/execution_result.rs | 7 +- .../src/versions/vm_1_3_2/test_utils.rs | 2 +- .../src/versions/vm_1_3_2/transaction_data.rs | 6 +- core/lib/multivm/src/versions/vm_1_3_2/vm.rs | 2 +- .../types/internals/transaction_data.rs | 9 +- .../types/internals/transaction_data.rs | 9 +- .../types/internals/transaction_data.rs | 9 +- .../src/versions/vm_latest/tests/block_tip.rs | 2 +- .../versions/vm_latest/tests/call_tracer.rs | 4 +- .../src/versions/vm_latest/tests/circuits.rs | 2 +- .../versions/vm_latest/tests/code_oracle.rs | 6 +- .../src/versions/vm_latest/tests/gas_limit.rs | 10 +- .../vm_latest/tests/get_used_contracts.rs | 4 +- .../vm_latest/tests/l1_tx_execution.rs | 2 +- .../src/versions/vm_latest/tests/l2_blocks.rs | 7 +- .../versions/vm_latest/tests/nonce_holder.rs | 2 +- .../versions/vm_latest/tests/precompiles.rs | 6 +- .../vm_latest/tests/prestate_tracer.rs | 4 +- .../vm_latest/tests/require_eip712.rs | 4 +- .../src/versions/vm_latest/tests/rollbacks.rs | 4 +- .../src/versions/vm_latest/tests/sekp256r1.rs | 2 +- .../src/versions/vm_latest/tests/storage.rs | 5 +- .../tests/tracing_execution_error.rs | 2 +- .../src/versions/vm_latest/tests/transfer.rs | 6 +- .../src/versions/vm_latest/tests/upgrade.rs | 4 +- .../types/internals/transaction_data.rs | 9 +- .../multivm/src/versions/vm_m5/test_utils.rs | 2 +- .../src/versions/vm_m5/transaction_data.rs | 4 +- .../multivm/src/versions/vm_m6/test_utils.rs | 2 +- .../src/versions/vm_m6/transaction_data.rs | 6 +- core/lib/multivm/src/versions/vm_m6/vm.rs | 2 +- .../types/internals/transaction_data.rs | 9 +- .../types/internals/transaction_data.rs | 9 +- core/lib/types/src/abi.rs | 1 - core/lib/types/src/l1/mod.rs | 4 +- core/lib/types/src/l2/mod.rs | 52 +-- core/lib/types/src/lib.rs | 22 +- core/lib/types/src/protocol_upgrade.rs | 6 +- core/lib/types/src/transaction_request.rs | 109 +++---- core/lib/types/src/tx/execute.rs | 19 +- .../src/execution_sandbox/execute.rs | 6 +- core/node/api_server/src/tx_sender/mod.rs | 4 +- core/node/consensus/Cargo.toml | 8 + core/node/consensus/src/batch.rs | 275 ++++++++++++++++ core/node/consensus/src/lib.rs | 4 + core/node/consensus/src/storage/mod.rs | 26 +- core/node/consensus/src/storage/testonly.rs | 23 ++ core/node/consensus/src/testonly.rs | 299 ++++++++++++++++-- core/node/consensus/src/tests.rs | 44 +++ core/node/eth_watch/src/tests.rs | 5 +- core/node/metadata_calculator/Cargo.toml | 1 + .../metadata_calculator/src/api_server/mod.rs | 18 +- core/node/state_keeper/Cargo.toml | 6 +- .../state_keeper/src/batch_executor/mod.rs | 3 +- .../src/batch_executor/tests/tester.rs | 52 +-- core/node/state_keeper/src/testonly/mod.rs | 81 +++++ .../src/updates/l2_block_updates.rs | 2 +- core/node/test_utils/src/lib.rs | 2 +- core/node/vm_runner/src/tests/mod.rs | 2 +- .../src/sdk/operations/deploy_contract.rs | 4 +- .../src/sdk/operations/execute_contract.rs | 4 +- .../loadnext/src/sdk/operations/transfer.rs | 4 +- core/tests/loadnext/src/sdk/signer.rs | 8 +- core/tests/test_account/src/lib.rs | 84 ++--- core/tests/vm-benchmark/harness/src/lib.rs | 2 +- prover/Cargo.lock | 23 ++ 84 files changed, 1191 insertions(+), 440 deletions(-) rename core/lib/dal/.sqlx/{query-a1829ef4532c8db6c1c907026e8643b7b722e0e467ad03978e9efe652c92a975.json => query-0f1856e55a370280a078d048f09e2d457914c737660b37e9f66b576bbc9a7904.json} (95%) rename core/lib/dal/.sqlx/{query-d0636ad46d8978f18292b3e66209bcc9e940c555a8629afa0960d99ca177f220.json => query-778f92b1ac91e1ae279f588053d75a9ac877fdd28bda99661e423405e695223d.json} (95%) create mode 100644 core/node/consensus/src/batch.rs diff --git a/Cargo.lock b/Cargo.lock index ffea732c3be..cfe47a2a4b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8747,6 +8747,7 @@ dependencies = [ "tracing", "vise", "zksync_config", + "zksync_crypto", "zksync_dal", "zksync_health_check", "zksync_merkle_tree", @@ -8827,6 +8828,7 @@ dependencies = [ "async-trait", "rand 0.8.5", "secrecy", + "tempfile", "test-casing", "tokio", "tracing", @@ -8840,13 +8842,20 @@ dependencies = [ "zksync_consensus_storage", "zksync_consensus_utils", "zksync_dal", + "zksync_l1_contract_interface", + "zksync_merkle_tree", + "zksync_metadata_calculator", "zksync_node_api_server", "zksync_node_genesis", "zksync_node_sync", "zksync_node_test_utils", "zksync_protobuf", + "zksync_state", "zksync_state_keeper", + "zksync_system_constants", + "zksync_test_account", "zksync_types", + "zksync_utils", "zksync_web3_decl", ] diff --git a/core/bin/system-constants-generator/src/intrinsic_costs.rs b/core/bin/system-constants-generator/src/intrinsic_costs.rs index 4f5e988e7b1..c94592defee 100644 --- a/core/bin/system-constants-generator/src/intrinsic_costs.rs +++ b/core/bin/system-constants-generator/src/intrinsic_costs.rs @@ -74,7 +74,7 @@ pub(crate) fn l2_gas_constants() -> IntrinsicSystemGasConstants { 0, Some(U256::zero()), None, - None, + vec![], ) .into(), ], @@ -99,7 +99,7 @@ pub(crate) fn l2_gas_constants() -> IntrinsicSystemGasConstants { 0, Some(U256::zero()), Some(vec![0u8; DELTA_IN_TX_SIZE]), - None, + vec![], ) .into()], true, @@ -117,7 +117,7 @@ pub(crate) fn l2_gas_constants() -> IntrinsicSystemGasConstants { 0, Some(U256::zero()), None, - Some(vec![vec![0u8; 32]]), + vec![vec![0u8; 32]], ) .into()], true, diff --git a/core/bin/system-constants-generator/src/utils.rs b/core/bin/system-constants-generator/src/utils.rs index d6f1ea85eff..329ff77738c 100644 --- a/core/bin/system-constants-generator/src/utils.rs +++ b/core/bin/system-constants-generator/src/utils.rs @@ -99,7 +99,7 @@ pub(super) fn get_l2_tx( U256::from(0), L2ChainId::from(270), signer, - None, + vec![], Default::default(), ) .unwrap() @@ -128,7 +128,7 @@ pub(super) fn get_l1_tx( pubdata_price: u32, custom_gas_limit: Option, custom_calldata: Option>, - factory_deps: Option>>, + factory_deps: Vec>, ) -> L1Tx { L1Tx { execute: Execute { @@ -157,10 +157,10 @@ pub(super) fn get_l1_txs(number_of_txs: usize) -> (Vec, Vec StructMember for TypedStructure { } /// Interface for defining the structure for the EIP712 signature. -pub trait EIP712TypedStructure: Serialize { +pub trait EIP712TypedStructure { const TYPE_NAME: &'static str; fn build_structure(&self, builder: &mut BUILDER); diff --git a/core/lib/crypto_primitives/src/eip712_signature/utils.rs b/core/lib/crypto_primitives/src/eip712_signature/utils.rs index 743d646ec58..526bb3b6b22 100644 --- a/core/lib/crypto_primitives/src/eip712_signature/utils.rs +++ b/core/lib/crypto_primitives/src/eip712_signature/utils.rs @@ -4,7 +4,7 @@ use crate::eip712_signature::typed_structure::{EIP712TypedStructure, Eip712Domai /// Formats the data that needs to be signed in json according to the standard eip-712. /// Compatible with `eth_signTypedData` RPC call. -pub fn get_eip712_json( +pub fn get_eip712_json( eip712_domain: &Eip712Domain, typed_struct: &T, ) -> Value { diff --git a/core/lib/dal/.sqlx/query-a1829ef4532c8db6c1c907026e8643b7b722e0e467ad03978e9efe652c92a975.json b/core/lib/dal/.sqlx/query-0f1856e55a370280a078d048f09e2d457914c737660b37e9f66b576bbc9a7904.json similarity index 95% rename from core/lib/dal/.sqlx/query-a1829ef4532c8db6c1c907026e8643b7b722e0e467ad03978e9efe652c92a975.json rename to core/lib/dal/.sqlx/query-0f1856e55a370280a078d048f09e2d457914c737660b37e9f66b576bbc9a7904.json index 605b6c1f025..498e839a63d 100644 --- a/core/lib/dal/.sqlx/query-a1829ef4532c8db6c1c907026e8643b7b722e0e467ad03978e9efe652c92a975.json +++ b/core/lib/dal/.sqlx/query-0f1856e55a370280a078d048f09e2d457914c737660b37e9f66b576bbc9a7904.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n transactions.*\n FROM\n transactions\n INNER JOIN miniblocks ON miniblocks.number = transactions.miniblock_number\n WHERE\n miniblocks.number = $1\n ORDER BY\n index_in_block\n ", + "query": "\n SELECT\n transactions.*\n FROM\n transactions\n INNER JOIN miniblocks ON miniblocks.number = transactions.miniblock_number\n WHERE\n miniblocks.number BETWEEN $1 AND $2\n ORDER BY\n miniblock_number,\n index_in_block\n ", "describe": { "columns": [ { @@ -186,6 +186,7 @@ ], "parameters": { "Left": [ + "Int8", "Int8" ] }, @@ -228,5 +229,5 @@ true ] }, - "hash": "a1829ef4532c8db6c1c907026e8643b7b722e0e467ad03978e9efe652c92a975" + "hash": "0f1856e55a370280a078d048f09e2d457914c737660b37e9f66b576bbc9a7904" } diff --git a/core/lib/dal/.sqlx/query-d0636ad46d8978f18292b3e66209bcc9e940c555a8629afa0960d99ca177f220.json b/core/lib/dal/.sqlx/query-778f92b1ac91e1ae279f588053d75a9ac877fdd28bda99661e423405e695223d.json similarity index 95% rename from core/lib/dal/.sqlx/query-d0636ad46d8978f18292b3e66209bcc9e940c555a8629afa0960d99ca177f220.json rename to core/lib/dal/.sqlx/query-778f92b1ac91e1ae279f588053d75a9ac877fdd28bda99661e423405e695223d.json index c9f08e92810..aa7d4c65a39 100644 --- a/core/lib/dal/.sqlx/query-d0636ad46d8978f18292b3e66209bcc9e940c555a8629afa0960d99ca177f220.json +++ b/core/lib/dal/.sqlx/query-778f92b1ac91e1ae279f588053d75a9ac877fdd28bda99661e423405e695223d.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n miniblocks.number,\n COALESCE(\n miniblocks.l1_batch_number,\n (\n SELECT\n (MAX(number) + 1)\n FROM\n l1_batches\n ),\n (\n SELECT\n MAX(l1_batch_number) + 1\n FROM\n snapshot_recovery\n )\n ) AS \"l1_batch_number!\",\n (miniblocks.l1_tx_count + miniblocks.l2_tx_count) AS \"tx_count!\",\n miniblocks.timestamp,\n miniblocks.l1_gas_price,\n miniblocks.l2_fair_gas_price,\n miniblocks.fair_pubdata_price,\n miniblocks.bootloader_code_hash,\n miniblocks.default_aa_code_hash,\n miniblocks.virtual_blocks,\n miniblocks.hash,\n miniblocks.protocol_version AS \"protocol_version!\",\n miniblocks.fee_account_address AS \"fee_account_address!\"\n FROM\n miniblocks\n WHERE\n miniblocks.number = $1\n ", + "query": "\n SELECT\n miniblocks.number,\n COALESCE(\n miniblocks.l1_batch_number,\n (\n SELECT\n (MAX(number) + 1)\n FROM\n l1_batches\n ),\n (\n SELECT\n MAX(l1_batch_number) + 1\n FROM\n snapshot_recovery\n )\n ) AS \"l1_batch_number!\",\n (miniblocks.l1_tx_count + miniblocks.l2_tx_count) AS \"tx_count!\",\n miniblocks.timestamp,\n miniblocks.l1_gas_price,\n miniblocks.l2_fair_gas_price,\n miniblocks.fair_pubdata_price,\n miniblocks.bootloader_code_hash,\n miniblocks.default_aa_code_hash,\n miniblocks.virtual_blocks,\n miniblocks.hash,\n miniblocks.protocol_version AS \"protocol_version!\",\n miniblocks.fee_account_address AS \"fee_account_address!\"\n FROM\n miniblocks\n WHERE\n miniblocks.number BETWEEN $1 AND $2\n ", "describe": { "columns": [ { @@ -71,6 +71,7 @@ ], "parameters": { "Left": [ + "Int8", "Int8" ] }, @@ -90,5 +91,5 @@ false ] }, - "hash": "d0636ad46d8978f18292b3e66209bcc9e940c555a8629afa0960d99ca177f220" + "hash": "778f92b1ac91e1ae279f588053d75a9ac877fdd28bda99661e423405e695223d" } diff --git a/core/lib/dal/src/consensus/mod.rs b/core/lib/dal/src/consensus/mod.rs index f7a3b066624..8e1f246b657 100644 --- a/core/lib/dal/src/consensus/mod.rs +++ b/core/lib/dal/src/consensus/mod.rs @@ -277,10 +277,7 @@ impl ProtoRepr for proto::Transaction { .and_then(|x| parse_h256(x)) .map(h256_to_u256) .context("execute.value")?, - factory_deps: match execute.factory_deps.is_empty() { - true => None, - false => Some(execute.factory_deps.clone()), - }, + factory_deps: execute.factory_deps.clone(), }, received_timestamp_ms: 0, // This timestamp is local to the node raw_bytes: self.raw_bytes.as_ref().map(|x| x.clone().into()), @@ -361,10 +358,7 @@ impl ProtoRepr for proto::Transaction { contract_address: Some(this.execute.contract_address.as_bytes().into()), calldata: Some(this.execute.calldata.clone()), value: Some(u256_to_h256(this.execute.value).as_bytes().into()), - factory_deps: match &this.execute.factory_deps { - Some(inner) => inner.clone(), - None => vec![], - }, + factory_deps: this.execute.factory_deps.clone(), }; Self { common_data: Some(common_data), diff --git a/core/lib/dal/src/consensus/proto/mod.proto b/core/lib/dal/src/consensus/proto/mod.proto index 89e3568fbb5..a5364761183 100644 --- a/core/lib/dal/src/consensus/proto/mod.proto +++ b/core/lib/dal/src/consensus/proto/mod.proto @@ -18,6 +18,8 @@ message Payload { } message Transaction { + reserved 5; + reserved "received_timestamp_ms"; oneof common_data { L1TxCommonData l1 = 1; L2TxCommonData l2 = 2; @@ -80,7 +82,7 @@ message Execute { optional bytes contract_address = 1; // required; H160 optional bytes calldata = 2; // required optional bytes value = 3; // required; U256 - repeated bytes factory_deps = 4; // optional + repeated bytes factory_deps = 4; } message InputData { diff --git a/core/lib/dal/src/consensus_dal.rs b/core/lib/dal/src/consensus_dal.rs index 041bd5c39a8..f2742cbedd8 100644 --- a/core/lib/dal/src/consensus_dal.rs +++ b/core/lib/dal/src/consensus_dal.rs @@ -279,33 +279,54 @@ impl ConsensusDal<'_, '_> { .await } - /// Converts the L2 block `block_number` into consensus payload. `Payload` is an - /// opaque format for the L2 block that consensus understands and generates a - /// certificate for it. - pub async fn block_payload( + /// Fetches a range of L2 blocks from storage and converts them to `Payload`s. + pub async fn block_payloads( &mut self, - block_number: validator::BlockNumber, - ) -> DalResult> { - let instrumentation = - Instrumented::new("block_payload").with_arg("block_number", &block_number); - let block_number = u32::try_from(block_number.0) - .map_err(|err| instrumentation.arg_error("block_number", err))?; - let block_number = L2BlockNumber(block_number); + numbers: std::ops::Range, + ) -> DalResult> { + let numbers = (|| { + anyhow::Ok(std::ops::Range { + start: L2BlockNumber(numbers.start.0.try_into().context("start")?), + end: L2BlockNumber(numbers.end.0.try_into().context("end")?), + }) + })() + .map_err(|err| { + Instrumented::new("block_payloads") + .with_arg("numbers", &numbers) + .arg_error("numbers", err) + })?; - let Some(block) = self + let blocks = self .storage .sync_dal() - .sync_block_inner(block_number) - .await? - else { - return Ok(None); - }; - let transactions = self + .sync_blocks_inner(numbers.clone()) + .await?; + let mut transactions = self .storage .transactions_web3_dal() - .get_raw_l2_block_transactions(block_number) + .get_raw_l2_blocks_transactions(numbers) .await?; - Ok(Some(block.into_payload(transactions))) + Ok(blocks + .into_iter() + .map(|b| { + let txs = transactions.remove(&b.number).unwrap_or_default(); + b.into_payload(txs) + }) + .collect()) + } + + /// Fetches an L2 block from storage and converts it to `Payload`. `Payload` is an + /// opaque format for the L2 block that consensus understands and generates a + /// certificate for it. + pub async fn block_payload( + &mut self, + number: validator::BlockNumber, + ) -> DalResult> { + Ok(self + .block_payloads(number..number + 1) + .await? + .into_iter() + .next()) } /// Inserts a certificate for the L2 block `cert.header().number`. It verifies that diff --git a/core/lib/dal/src/models/tests.rs b/core/lib/dal/src/models/tests.rs index 373fbf3a7b4..34cfde108f1 100644 --- a/core/lib/dal/src/models/tests.rs +++ b/core/lib/dal/src/models/tests.rs @@ -20,7 +20,7 @@ fn default_execute() -> Execute { 8cdfd0000000000000000000000000000000000000000000000000000000157d600d0", ) .unwrap(), - factory_deps: None, + factory_deps: vec![], } } diff --git a/core/lib/dal/src/sync_dal.rs b/core/lib/dal/src/sync_dal.rs index 1296cb6e24a..898770c38f5 100644 --- a/core/lib/dal/src/sync_dal.rs +++ b/core/lib/dal/src/sync_dal.rs @@ -15,11 +15,15 @@ pub struct SyncDal<'a, 'c> { } impl SyncDal<'_, '_> { - pub(super) async fn sync_block_inner( + pub(super) async fn sync_blocks_inner( &mut self, - block_number: L2BlockNumber, - ) -> DalResult> { - let block = sqlx::query_as!( + numbers: std::ops::Range, + ) -> DalResult> { + // Check if range is non-empty, because BETWEEN in SQL in `unordered`. + if numbers.is_empty() { + return Ok(vec![]); + } + let blocks = sqlx::query_as!( StorageSyncBlock, r#" SELECT @@ -53,35 +57,44 @@ impl SyncDal<'_, '_> { FROM miniblocks WHERE - miniblocks.number = $1 + miniblocks.number BETWEEN $1 AND $2 "#, - i64::from(block_number.0) + i64::from(numbers.start.0), + i64::from(numbers.end.0 - 1), ) .try_map(SyncBlock::try_from) - .instrument("sync_dal_sync_block.block") - .with_arg("block_number", &block_number) - .fetch_optional(self.storage) + .instrument("sync_dal_sync_blocks.block") + .with_arg("numbers", &numbers) + .fetch_all(self.storage) .await?; - Ok(block) + Ok(blocks) } pub async fn sync_block( &mut self, - block_number: L2BlockNumber, + number: L2BlockNumber, include_transactions: bool, ) -> DalResult> { let _latency = MethodLatency::new("sync_dal_sync_block"); - let Some(block) = self.sync_block_inner(block_number).await? else { + let numbers = number..number + 1; + let Some(block) = self + .sync_blocks_inner(numbers.clone()) + .await? + .into_iter() + .next() + else { return Ok(None); }; let transactions = if include_transactions { - let transactions = self + let mut transactions = self .storage .transactions_web3_dal() - .get_raw_l2_block_transactions(block_number) + .get_raw_l2_blocks_transactions(numbers) .await?; - Some(transactions) + // If there are no transactions in the block, + // return `Some(vec![])`. + Some(transactions.remove(&number).unwrap_or_default()) } else { None }; diff --git a/core/lib/dal/src/tests/mod.rs b/core/lib/dal/src/tests/mod.rs index 500da25ace8..c4dab124655 100644 --- a/core/lib/dal/src/tests/mod.rs +++ b/core/lib/dal/src/tests/mod.rs @@ -66,7 +66,7 @@ pub(crate) fn mock_l2_transaction() -> L2Tx { Default::default(), L2ChainId::from(270), &K256PrivateKey::random(), - None, + vec![], Default::default(), ) .unwrap(); @@ -98,7 +98,7 @@ pub(crate) fn mock_l1_execute() -> L1Tx { contract_address: H160::random(), value: Default::default(), calldata: vec![], - factory_deps: None, + factory_deps: vec![], }; L1Tx { @@ -126,7 +126,7 @@ pub(crate) fn mock_protocol_upgrade_transaction() -> ProtocolUpgradeTx { contract_address: H160::random(), value: Default::default(), calldata: vec![], - factory_deps: None, + factory_deps: vec![], }; ProtocolUpgradeTx { diff --git a/core/lib/dal/src/transactions_web3_dal.rs b/core/lib/dal/src/transactions_web3_dal.rs index b7cbf16c89c..2d380a8059a 100644 --- a/core/lib/dal/src/transactions_web3_dal.rs +++ b/core/lib/dal/src/transactions_web3_dal.rs @@ -1,7 +1,12 @@ +use std::collections::HashMap; + +use anyhow::Context as _; use sqlx::types::chrono::NaiveDateTime; use zksync_db_connection::{ - connection::Connection, error::DalResult, instrument::InstrumentExt, interpolate_query, - match_query_as, + connection::Connection, + error::{DalResult, SqlxContext as _}, + instrument::InstrumentExt, + interpolate_query, match_query_as, }; use zksync_types::{ api, api::TransactionReceipt, event::DEPLOY_EVENT_SIGNATURE, Address, L2BlockNumber, L2ChainId, @@ -379,12 +384,17 @@ impl TransactionsWeb3Dal<'_, '_> { Ok(U256::from(pending_nonce)) } - /// Returns the server transactions (not API ones) from a certain L2 block. - /// Returns an empty list if the L2 block doesn't exist. - pub async fn get_raw_l2_block_transactions( + /// Returns the server transactions (not API ones) from a L2 block range. + pub async fn get_raw_l2_blocks_transactions( &mut self, - l2_block: L2BlockNumber, - ) -> DalResult> { + blocks: std::ops::Range, + ) -> DalResult>> { + // Check if range is non-empty, because BETWEEN in SQL in `unordered`. + if blocks.is_empty() { + return Ok(HashMap::default()); + } + // We do an inner join with `miniblocks.number`, because + // transaction insertions are not atomic with miniblock insertion. let rows = sqlx::query_as!( StorageTransaction, r#" @@ -394,18 +404,46 @@ impl TransactionsWeb3Dal<'_, '_> { transactions INNER JOIN miniblocks ON miniblocks.number = transactions.miniblock_number WHERE - miniblocks.number = $1 + miniblocks.number BETWEEN $1 AND $2 ORDER BY + miniblock_number, index_in_block "#, - i64::from(l2_block.0) + i64::from(blocks.start.0), + i64::from(blocks.end.0 - 1), ) - .instrument("get_raw_l2_block_transactions") - .with_arg("l2_block", &l2_block) + .try_map(|row| { + let to_block_number = |n: Option| { + anyhow::Ok(L2BlockNumber( + n.context("missing")?.try_into().context("overflow")?, + )) + }; + Ok(( + to_block_number(row.miniblock_number).decode_column("miniblock_number")?, + Transaction::from(row), + )) + }) + .instrument("get_raw_l2_blocks_transactions") + .with_arg("blocks", &blocks) .fetch_all(self.storage) .await?; + let mut txs: HashMap> = HashMap::new(); + for (n, tx) in rows { + txs.entry(n).or_default().push(tx); + } + Ok(txs) + } - Ok(rows.into_iter().map(Into::into).collect()) + /// Returns the server transactions (not API ones) from an L2 block. + pub async fn get_raw_l2_block_transactions( + &mut self, + block: L2BlockNumber, + ) -> DalResult> { + Ok(self + .get_raw_l2_blocks_transactions(block..block + 1) + .await? + .remove(&block) + .unwrap_or_default()) } } diff --git a/core/lib/mempool/src/tests.rs b/core/lib/mempool/src/tests.rs index a8c7128baa9..6ea1be3b514 100644 --- a/core/lib/mempool/src/tests.rs +++ b/core/lib/mempool/src/tests.rs @@ -377,7 +377,7 @@ fn gen_l2_tx_with_timestamp(address: Address, nonce: Nonce, received_at_ms: u64) Fee::default(), address, U256::zero(), - None, + vec![], Default::default(), ); txn.received_timestamp_ms = received_at_ms; @@ -388,7 +388,7 @@ fn gen_l1_tx(priority_id: PriorityOpId) -> Transaction { let execute = Execute { contract_address: Address::repeat_byte(0x11), calldata: vec![1, 2, 3], - factory_deps: None, + factory_deps: vec![], value: U256::zero(), }; let op_data = L1TxCommonData { diff --git a/core/lib/merkle_tree/src/getters.rs b/core/lib/merkle_tree/src/getters.rs index c20c182adef..34978f5dc6a 100644 --- a/core/lib/merkle_tree/src/getters.rs +++ b/core/lib/merkle_tree/src/getters.rs @@ -131,7 +131,9 @@ mod tests { let entries = tree.entries_with_proofs(0, &[missing_key]).unwrap(); assert_eq!(entries.len(), 1); assert!(entries[0].base.is_empty()); - entries[0].verify(&tree.hasher, tree.hasher.empty_tree_hash()); + entries[0] + .verify(&tree.hasher, tree.hasher.empty_tree_hash()) + .unwrap(); } #[test] @@ -151,8 +153,8 @@ mod tests { let entries = tree.entries_with_proofs(0, &[key, missing_key]).unwrap(); assert_eq!(entries.len(), 2); assert!(!entries[0].base.is_empty()); - entries[0].verify(&tree.hasher, output.root_hash); + entries[0].verify(&tree.hasher, output.root_hash).unwrap(); assert!(entries[1].base.is_empty()); - entries[1].verify(&tree.hasher, output.root_hash); + entries[1].verify(&tree.hasher, output.root_hash).unwrap(); } } diff --git a/core/lib/merkle_tree/src/hasher/proofs.rs b/core/lib/merkle_tree/src/hasher/proofs.rs index 3e61c9e1d86..9af732af489 100644 --- a/core/lib/merkle_tree/src/hasher/proofs.rs +++ b/core/lib/merkle_tree/src/hasher/proofs.rs @@ -81,18 +81,26 @@ impl BlockOutputWithProofs { impl TreeEntryWithProof { /// Verifies this proof. /// - /// # Panics + /// # Errors /// - /// Panics if the proof doesn't verify. - pub fn verify(&self, hasher: &dyn HashTree, trusted_root_hash: ValueHash) { + /// Returns an error <=> proof is invalid. + pub fn verify( + &self, + hasher: &dyn HashTree, + trusted_root_hash: ValueHash, + ) -> anyhow::Result<()> { if self.base.leaf_index == 0 { - assert!( + ensure!( self.base.value.is_zero(), "Invalid missing value specification: leaf index is zero, but value is non-default" ); } let root_hash = hasher.fold_merkle_path(&self.merkle_path, self.base); - assert_eq!(root_hash, trusted_root_hash, "Root hash mismatch"); + ensure!( + root_hash == trusted_root_hash, + "Root hash mismatch: got {root_hash}, want {trusted_root_hash}" + ); + Ok(()) } } diff --git a/core/lib/merkle_tree/tests/integration/merkle_tree.rs b/core/lib/merkle_tree/tests/integration/merkle_tree.rs index f778862720d..a83b982cc49 100644 --- a/core/lib/merkle_tree/tests/integration/merkle_tree.rs +++ b/core/lib/merkle_tree/tests/integration/merkle_tree.rs @@ -86,7 +86,7 @@ fn entry_proofs_are_computed_correctly_on_empty_tree(kv_count: u64) { let entries = tree.entries_with_proofs(0, &existing_keys).unwrap(); assert_eq!(entries.len(), existing_keys.len()); for (input_entry, entry) in kvs.iter().zip(entries) { - entry.verify(&Blake2Hasher, expected_hash); + entry.verify(&Blake2Hasher, expected_hash).unwrap(); assert_eq!(entry.base, *input_entry); } @@ -110,7 +110,7 @@ fn entry_proofs_are_computed_correctly_on_empty_tree(kv_count: u64) { for (key, entry) in missing_keys.iter().zip(entries) { assert!(entry.base.is_empty()); assert_eq!(entry.base.key, *key); - entry.verify(&Blake2Hasher, expected_hash); + entry.verify(&Blake2Hasher, expected_hash).unwrap(); } } @@ -228,7 +228,7 @@ fn entry_proofs_are_computed_correctly_with_intermediate_commits(chunk_size: usi for (i, (key, entry)) in all_keys.iter().zip(entries).enumerate() { assert_eq!(entry.base.key, *key); assert_eq!(entry.base.is_empty(), i >= (version + 1) * chunk_size); - entry.verify(&Blake2Hasher, output.root_hash); + entry.verify(&Blake2Hasher, output.root_hash).unwrap(); } } @@ -239,7 +239,7 @@ fn entry_proofs_are_computed_correctly_with_intermediate_commits(chunk_size: usi for (i, (key, entry)) in all_keys.iter().zip(entries).enumerate() { assert_eq!(entry.base.key, *key); assert_eq!(entry.base.is_empty(), i >= (version + 1) * chunk_size); - entry.verify(&Blake2Hasher, root_hash); + entry.verify(&Blake2Hasher, root_hash).unwrap(); } } } @@ -415,7 +415,7 @@ fn proofs_are_computed_correctly_with_key_updates(updated_keys: usize) { let proofs = tree.entries_with_proofs(1, &keys).unwrap(); for (entry, proof) in kvs.iter().zip(proofs) { assert_eq!(proof.base, *entry); - proof.verify(&Blake2Hasher, *expected_hash); + proof.verify(&Blake2Hasher, *expected_hash).unwrap(); } } 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 3ce7d31f212..faa702f411b 100644 --- a/core/lib/multivm/src/interface/types/outputs/execution_result.rs +++ b/core/lib/multivm/src/interface/types/outputs/execution_result.rs @@ -64,12 +64,7 @@ impl ExecutionResult { impl VmExecutionResultAndLogs { pub fn get_execution_metrics(&self, tx: Option<&Transaction>) -> ExecutionMetrics { let contracts_deployed = tx - .map(|tx| { - tx.execute - .factory_deps - .as_ref() - .map_or(0, |deps| deps.len() as u16) - }) + .map(|tx| tx.execute.factory_deps.len() as u16) .unwrap_or(0); // We published the data as ABI-encoded `bytes`, so the total length is: diff --git a/core/lib/multivm/src/versions/vm_1_3_2/test_utils.rs b/core/lib/multivm/src/versions/vm_1_3_2/test_utils.rs index 375a8bdb7ad..603725790f8 100644 --- a/core/lib/multivm/src/versions/vm_1_3_2/test_utils.rs +++ b/core/lib/multivm/src/versions/vm_1_3_2/test_utils.rs @@ -155,7 +155,7 @@ pub fn get_create_execute(code: &[u8], calldata: &[u8]) -> Execute { Execute { contract_address: CONTRACT_DEPLOYER_ADDRESS, calldata, - factory_deps: Some(vec![code.to_vec()]), + factory_deps: vec![code.to_vec()], value: U256::zero(), } } diff --git a/core/lib/multivm/src/versions/vm_1_3_2/transaction_data.rs b/core/lib/multivm/src/versions/vm_1_3_2/transaction_data.rs index 896af8d84f4..788a52206e8 100644 --- a/core/lib/multivm/src/versions/vm_1_3_2/transaction_data.rs +++ b/core/lib/multivm/src/versions/vm_1_3_2/transaction_data.rs @@ -89,7 +89,7 @@ impl From for TransactionData { ], data: execute_tx.execute.calldata, signature: common_data.signature, - factory_deps: execute_tx.execute.factory_deps.unwrap_or_default(), + factory_deps: execute_tx.execute.factory_deps, paymaster_input: common_data.paymaster_params.paymaster_input, reserved_dynamic: vec![], } @@ -118,7 +118,7 @@ impl From for TransactionData { data: execute_tx.execute.calldata, // The signature isn't checked for L1 transactions so we don't care signature: vec![], - factory_deps: execute_tx.execute.factory_deps.unwrap_or_default(), + factory_deps: execute_tx.execute.factory_deps, paymaster_input: vec![], reserved_dynamic: vec![], } @@ -147,7 +147,7 @@ impl From for TransactionData { data: execute_tx.execute.calldata, // The signature isn't checked for L1 transactions so we don't care signature: vec![], - factory_deps: execute_tx.execute.factory_deps.unwrap_or_default(), + factory_deps: execute_tx.execute.factory_deps, paymaster_input: vec![], reserved_dynamic: vec![], } diff --git a/core/lib/multivm/src/versions/vm_1_3_2/vm.rs b/core/lib/multivm/src/versions/vm_1_3_2/vm.rs index d76704f892b..36ba32a8120 100644 --- a/core/lib/multivm/src/versions/vm_1_3_2/vm.rs +++ b/core/lib/multivm/src/versions/vm_1_3_2/vm.rs @@ -196,7 +196,7 @@ impl VmInterface for Vm { } self.last_tx_compressed_bytecodes = vec![]; let bytecodes = if with_compression { - let deps = tx.execute.factory_deps.as_deref().unwrap_or_default(); + let deps = &tx.execute.factory_deps; let mut deps_hashes = HashSet::with_capacity(deps.len()); let mut bytecode_hashes = vec![]; let filtered_deps = deps.iter().filter_map(|bytecode| { diff --git a/core/lib/multivm/src/versions/vm_1_4_1/types/internals/transaction_data.rs b/core/lib/multivm/src/versions/vm_1_4_1/types/internals/transaction_data.rs index 61c14156dfb..1379b853a54 100644 --- a/core/lib/multivm/src/versions/vm_1_4_1/types/internals/transaction_data.rs +++ b/core/lib/multivm/src/versions/vm_1_4_1/types/internals/transaction_data.rs @@ -91,7 +91,7 @@ impl From for TransactionData { ], data: execute_tx.execute.calldata, signature: common_data.signature, - factory_deps: execute_tx.execute.factory_deps.unwrap_or_default(), + factory_deps: execute_tx.execute.factory_deps, paymaster_input: common_data.paymaster_params.paymaster_input, reserved_dynamic: vec![], raw_bytes: execute_tx.raw_bytes.map(|a| a.0), @@ -121,7 +121,7 @@ impl From for TransactionData { data: execute_tx.execute.calldata, // The signature isn't checked for L1 transactions so we don't care signature: vec![], - factory_deps: execute_tx.execute.factory_deps.unwrap_or_default(), + factory_deps: execute_tx.execute.factory_deps, paymaster_input: vec![], reserved_dynamic: vec![], raw_bytes: None, @@ -151,7 +151,7 @@ impl From for TransactionData { data: execute_tx.execute.calldata, // The signature isn't checked for L1 transactions so we don't care signature: vec![], - factory_deps: execute_tx.execute.factory_deps.unwrap_or_default(), + factory_deps: execute_tx.execute.factory_deps, paymaster_input: vec![], reserved_dynamic: vec![], raw_bytes: None, @@ -284,12 +284,11 @@ impl TryInto for TransactionData { paymaster_input: self.paymaster_input, }, }; - let factory_deps = (!self.factory_deps.is_empty()).then_some(self.factory_deps); let execute = Execute { contract_address: self.to, value: self.value, calldata: self.data, - factory_deps, + factory_deps: self.factory_deps, }; Ok(L2Tx { diff --git a/core/lib/multivm/src/versions/vm_1_4_2/types/internals/transaction_data.rs b/core/lib/multivm/src/versions/vm_1_4_2/types/internals/transaction_data.rs index a201df01af6..3498e51ec30 100644 --- a/core/lib/multivm/src/versions/vm_1_4_2/types/internals/transaction_data.rs +++ b/core/lib/multivm/src/versions/vm_1_4_2/types/internals/transaction_data.rs @@ -91,7 +91,7 @@ impl From for TransactionData { ], data: execute_tx.execute.calldata, signature: common_data.signature, - factory_deps: execute_tx.execute.factory_deps.unwrap_or_default(), + factory_deps: execute_tx.execute.factory_deps, paymaster_input: common_data.paymaster_params.paymaster_input, reserved_dynamic: vec![], raw_bytes: execute_tx.raw_bytes.map(|a| a.0), @@ -121,7 +121,7 @@ impl From for TransactionData { data: execute_tx.execute.calldata, // The signature isn't checked for L1 transactions so we don't care signature: vec![], - factory_deps: execute_tx.execute.factory_deps.unwrap_or_default(), + factory_deps: execute_tx.execute.factory_deps, paymaster_input: vec![], reserved_dynamic: vec![], raw_bytes: None, @@ -151,7 +151,7 @@ impl From for TransactionData { data: execute_tx.execute.calldata, // The signature isn't checked for L1 transactions so we don't care signature: vec![], - factory_deps: execute_tx.execute.factory_deps.unwrap_or_default(), + factory_deps: execute_tx.execute.factory_deps, paymaster_input: vec![], reserved_dynamic: vec![], raw_bytes: None, @@ -284,12 +284,11 @@ impl TryInto for TransactionData { paymaster_input: self.paymaster_input, }, }; - let factory_deps = (!self.factory_deps.is_empty()).then_some(self.factory_deps); let execute = Execute { contract_address: self.to, value: self.value, calldata: self.data, - factory_deps, + factory_deps: self.factory_deps, }; Ok(L2Tx { diff --git a/core/lib/multivm/src/versions/vm_boojum_integration/types/internals/transaction_data.rs b/core/lib/multivm/src/versions/vm_boojum_integration/types/internals/transaction_data.rs index 8cc4e256740..ad740a279dc 100644 --- a/core/lib/multivm/src/versions/vm_boojum_integration/types/internals/transaction_data.rs +++ b/core/lib/multivm/src/versions/vm_boojum_integration/types/internals/transaction_data.rs @@ -91,7 +91,7 @@ impl From for TransactionData { ], data: execute_tx.execute.calldata, signature: common_data.signature, - factory_deps: execute_tx.execute.factory_deps.unwrap_or_default(), + factory_deps: execute_tx.execute.factory_deps, paymaster_input: common_data.paymaster_params.paymaster_input, reserved_dynamic: vec![], raw_bytes: execute_tx.raw_bytes.map(|a| a.0), @@ -121,7 +121,7 @@ impl From for TransactionData { data: execute_tx.execute.calldata, // The signature isn't checked for L1 transactions so we don't care signature: vec![], - factory_deps: execute_tx.execute.factory_deps.unwrap_or_default(), + factory_deps: execute_tx.execute.factory_deps, paymaster_input: vec![], reserved_dynamic: vec![], raw_bytes: None, @@ -151,7 +151,7 @@ impl From for TransactionData { data: execute_tx.execute.calldata, // The signature isn't checked for L1 transactions so we don't care signature: vec![], - factory_deps: execute_tx.execute.factory_deps.unwrap_or_default(), + factory_deps: execute_tx.execute.factory_deps, paymaster_input: vec![], reserved_dynamic: vec![], raw_bytes: None, @@ -298,12 +298,11 @@ impl TryInto for TransactionData { paymaster_input: self.paymaster_input, }, }; - let factory_deps = (!self.factory_deps.is_empty()).then_some(self.factory_deps); let execute = Execute { contract_address: self.to, value: self.value, calldata: self.data, - factory_deps, + factory_deps: self.factory_deps, }; Ok(L2Tx { diff --git a/core/lib/multivm/src/versions/vm_latest/tests/block_tip.rs b/core/lib/multivm/src/versions/vm_latest/tests/block_tip.rs index bf1acb981f3..78136602dae 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/block_tip.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/block_tip.rs @@ -167,7 +167,7 @@ fn execute_test(test_data: L1MessengerTestData) -> TestStatistics { contract_address: CONTRACT_FORCE_DEPLOYER_ADDRESS, calldata: data, value: U256::zero(), - factory_deps: None, + factory_deps: vec![], }, None, ); diff --git a/core/lib/multivm/src/versions/vm_latest/tests/call_tracer.rs b/core/lib/multivm/src/versions/vm_latest/tests/call_tracer.rs index c97b38b6afc..a4d0eb2d17e 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/call_tracer.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/call_tracer.rs @@ -37,7 +37,7 @@ fn test_max_depth() { contract_address: address, calldata: vec![], value: Default::default(), - factory_deps: None, + factory_deps: vec![], }, None, ); @@ -72,7 +72,7 @@ fn test_basic_behavior() { contract_address: address, calldata: hex::decode(increment_by_6_calldata).unwrap(), value: Default::default(), - factory_deps: None, + factory_deps: vec![], }, None, ); diff --git a/core/lib/multivm/src/versions/vm_latest/tests/circuits.rs b/core/lib/multivm/src/versions/vm_latest/tests/circuits.rs index c582bd28c88..02ec2dc58aa 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/circuits.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/circuits.rs @@ -25,7 +25,7 @@ fn test_circuits() { contract_address: Address::random(), calldata: Vec::new(), value: U256::from(1u8), - factory_deps: None, + factory_deps: vec![], }, None, ); diff --git a/core/lib/multivm/src/versions/vm_latest/tests/code_oracle.rs b/core/lib/multivm/src/versions/vm_latest/tests/code_oracle.rs index feb60f93a23..8c8c6e2d097 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/code_oracle.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/code_oracle.rs @@ -72,7 +72,7 @@ fn test_code_oracle() { ]) .unwrap(), value: U256::zero(), - factory_deps: None, + factory_deps: vec![], }, None, ); @@ -93,7 +93,7 @@ fn test_code_oracle() { ]) .unwrap(), value: U256::zero(), - factory_deps: None, + factory_deps: vec![], }, None, ); @@ -155,7 +155,7 @@ fn test_code_oracle_big_bytecode() { ]) .unwrap(), value: U256::zero(), - factory_deps: None, + factory_deps: vec![], }, None, ); diff --git a/core/lib/multivm/src/versions/vm_latest/tests/gas_limit.rs b/core/lib/multivm/src/versions/vm_latest/tests/gas_limit.rs index 533d9ec660e..34e1e2d25f3 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/gas_limit.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/gas_limit.rs @@ -1,3 +1,4 @@ +use zksync_test_account::Account; use zksync_types::{fee::Fee, Execute}; use crate::{ @@ -20,15 +21,10 @@ fn test_tx_gas_limit_offset() { let gas_limit = 9999.into(); let tx = vm.rich_accounts[0].get_l2_tx_for_execute( - Execute { - contract_address: Default::default(), - calldata: vec![], - value: Default::default(), - factory_deps: None, - }, + Execute::default(), Some(Fee { gas_limit, - ..Default::default() + ..Account::default_fee() }), ); diff --git a/core/lib/multivm/src/versions/vm_latest/tests/get_used_contracts.rs b/core/lib/multivm/src/versions/vm_latest/tests/get_used_contracts.rs index 38a4d7cbb43..7bc08b6fb49 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/get_used_contracts.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/get_used_contracts.rs @@ -70,7 +70,7 @@ fn test_get_used_contracts() { contract_address: CONTRACT_DEPLOYER_ADDRESS, calldata: big_calldata, value: Default::default(), - factory_deps: Some(vec![vec![1; 32]]), + factory_deps: vec![vec![1; 32]], }, 1, ); @@ -81,7 +81,7 @@ fn test_get_used_contracts() { assert!(res2.result.is_failed()); - for factory_dep in tx2.execute.factory_deps.unwrap() { + for factory_dep in tx2.execute.factory_deps { let hash = hash_bytecode(&factory_dep); let hash_to_u256 = h256_to_u256(hash); assert!(known_bytecodes_without_aa_code(&vm.vm) diff --git a/core/lib/multivm/src/versions/vm_latest/tests/l1_tx_execution.rs b/core/lib/multivm/src/versions/vm_latest/tests/l1_tx_execution.rs index 2144ad9812d..5a87ce59be2 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/l1_tx_execution.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/l1_tx_execution.rs @@ -172,7 +172,7 @@ fn test_l1_tx_execution_high_gas_limit() { Execute { contract_address: L1_MESSENGER_ADDRESS, value: 0.into(), - factory_deps: None, + factory_deps: vec![], calldata, }, 0, diff --git a/core/lib/multivm/src/versions/vm_latest/tests/l2_blocks.rs b/core/lib/multivm/src/versions/vm_latest/tests/l2_blocks.rs index 59b161019f7..e62786bb55e 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/l2_blocks.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/l2_blocks.rs @@ -37,12 +37,7 @@ fn get_l1_noop() -> Transaction { gas_per_pubdata_limit: REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_BYTE.into(), ..Default::default() }), - execute: Execute { - contract_address: H160::zero(), - calldata: vec![], - value: U256::zero(), - factory_deps: None, - }, + execute: Execute::default(), received_timestamp_ms: 0, raw_bytes: None, } diff --git a/core/lib/multivm/src/versions/vm_latest/tests/nonce_holder.rs b/core/lib/multivm/src/versions/vm_latest/tests/nonce_holder.rs index 309e26120af..076ecb52361 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/nonce_holder.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/nonce_holder.rs @@ -67,7 +67,7 @@ fn test_nonce_holder() { contract_address: account.address, calldata: vec![12], value: Default::default(), - factory_deps: None, + factory_deps: vec![], }, None, Nonce(nonce), diff --git a/core/lib/multivm/src/versions/vm_latest/tests/precompiles.rs b/core/lib/multivm/src/versions/vm_latest/tests/precompiles.rs index 652f9c0c03f..2ab40faf22c 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/precompiles.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/precompiles.rs @@ -34,7 +34,7 @@ fn test_keccak() { contract_address: address, calldata: hex::decode(keccak1000_calldata).unwrap(), value: Default::default(), - factory_deps: None, + factory_deps: vec![], }, None, ); @@ -78,7 +78,7 @@ fn test_sha256() { contract_address: address, calldata: hex::decode(sha1000_calldata).unwrap(), value: Default::default(), - factory_deps: None, + factory_deps: vec![], }, None, ); @@ -115,7 +115,7 @@ fn test_ecrecover() { contract_address: account.address, calldata: Vec::new(), value: Default::default(), - factory_deps: None, + factory_deps: vec![], }, None, ); diff --git a/core/lib/multivm/src/versions/vm_latest/tests/prestate_tracer.rs b/core/lib/multivm/src/versions/vm_latest/tests/prestate_tracer.rs index 63620c7d9ff..893ca57bc4d 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/prestate_tracer.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/prestate_tracer.rs @@ -91,7 +91,7 @@ fn test_prestate_tracer_diff_mode() { contract_address: vm.test_contract.unwrap(), calldata: Default::default(), value: U256::from(100000), - factory_deps: None, + factory_deps: vec![], }; vm.vm @@ -101,7 +101,7 @@ fn test_prestate_tracer_diff_mode() { contract_address: deployed_address2, calldata: Default::default(), value: U256::from(200000), - factory_deps: None, + factory_deps: vec![], }; vm.vm diff --git a/core/lib/multivm/src/versions/vm_latest/tests/require_eip712.rs b/core/lib/multivm/src/versions/vm_latest/tests/require_eip712.rs index f4d6051272e..5178c5dc29c 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/require_eip712.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/require_eip712.rs @@ -66,7 +66,7 @@ async fn test_require_eip712() { contract_address: account_abstraction.address, calldata: encoded_input, value: Default::default(), - factory_deps: None, + factory_deps: vec![], }, None, ); @@ -131,7 +131,7 @@ async fn test_require_eip712() { }, account_abstraction.address, U256::from(28374938), - None, + vec![], Default::default(), ); diff --git a/core/lib/multivm/src/versions/vm_latest/tests/rollbacks.rs b/core/lib/multivm/src/versions/vm_latest/tests/rollbacks.rs index 436981dd158..e0c3ec4157d 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/rollbacks.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/rollbacks.rs @@ -103,7 +103,7 @@ fn test_vm_loadnext_rollbacks() { } .to_bytes(), value: Default::default(), - factory_deps: None, + factory_deps: vec![], }, None, ); @@ -121,7 +121,7 @@ fn test_vm_loadnext_rollbacks() { } .to_bytes(), value: Default::default(), - factory_deps: None, + factory_deps: vec![], }, None, ); diff --git a/core/lib/multivm/src/versions/vm_latest/tests/sekp256r1.rs b/core/lib/multivm/src/versions/vm_latest/tests/sekp256r1.rs index 18917456888..07b25eb0a8b 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/sekp256r1.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/sekp256r1.rs @@ -51,7 +51,7 @@ fn test_sekp256r1() { contract_address: P256VERIFY_PRECOMPILE_ADDRESS, calldata: [digest, encoded_r, encoded_s, x, y].concat(), value: U256::zero(), - factory_deps: None, + factory_deps: vec![], }, None, ); diff --git a/core/lib/multivm/src/versions/vm_latest/tests/storage.rs b/core/lib/multivm/src/versions/vm_latest/tests/storage.rs index b39c0dc53b7..b7c14c54f6d 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/storage.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/storage.rs @@ -1,5 +1,6 @@ use ethabi::Token; use zksync_contracts::{load_contract, read_bytecode}; +use zksync_test_account::Account; use zksync_types::{fee::Fee, Address, Execute, U256}; use crate::{ @@ -50,7 +51,7 @@ fn test_storage(txs: Vec) -> u32 { contract_address: test_contract_address, calldata, value: 0.into(), - factory_deps: None, + factory_deps: vec![], }, fee_overrides, ); @@ -164,7 +165,7 @@ fn test_transient_storage_behavior_panic() { let small_fee = Fee { // Something very-very small to make the validation fail gas_limit: 10_000.into(), - ..Default::default() + ..Account::default_fee() }; test_storage(vec![ diff --git a/core/lib/multivm/src/versions/vm_latest/tests/tracing_execution_error.rs b/core/lib/multivm/src/versions/vm_latest/tests/tracing_execution_error.rs index f02de899b03..58c5ef77dc4 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/tracing_execution_error.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/tracing_execution_error.rs @@ -30,7 +30,7 @@ fn test_tracing_of_execution_errors() { contract_address, calldata: get_execute_error_calldata(), value: Default::default(), - factory_deps: Some(vec![]), + factory_deps: vec![], }, None, ); diff --git a/core/lib/multivm/src/versions/vm_latest/tests/transfer.rs b/core/lib/multivm/src/versions/vm_latest/tests/transfer.rs index 6351c216f3a..f4198d541f7 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/transfer.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/transfer.rs @@ -76,7 +76,7 @@ fn test_send_or_transfer(test_option: TestOptions) { contract_address: test_contract_address, calldata, value: U256::zero(), - factory_deps: None, + factory_deps: vec![], }, None, ); @@ -176,7 +176,7 @@ fn test_reentrancy_protection_send_or_transfer(test_option: TestOptions) { .encode_input(&[]) .unwrap(), value: U256::from(1), - factory_deps: None, + factory_deps: vec![], }, None, ); @@ -193,7 +193,7 @@ fn test_reentrancy_protection_send_or_transfer(test_option: TestOptions) { contract_address: test_contract_address, calldata, value, - factory_deps: None, + factory_deps: vec![], }, None, ); diff --git a/core/lib/multivm/src/versions/vm_latest/tests/upgrade.rs b/core/lib/multivm/src/versions/vm_latest/tests/upgrade.rs index 559cf588453..80e16248fb2 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/upgrade.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/upgrade.rs @@ -279,7 +279,7 @@ fn get_forced_deploy_tx(deployment: &[ForceDeployment]) -> Transaction { let execute = Execute { contract_address: CONTRACT_DEPLOYER_ADDRESS, calldata, - factory_deps: None, + factory_deps: vec![], value: U256::zero(), }; @@ -329,7 +329,7 @@ fn get_complex_upgrade_tx( let execute = Execute { contract_address: COMPLEX_UPGRADER_ADDRESS, calldata: complex_upgrader_calldata, - factory_deps: None, + factory_deps: vec![], value: U256::zero(), }; diff --git a/core/lib/multivm/src/versions/vm_latest/types/internals/transaction_data.rs b/core/lib/multivm/src/versions/vm_latest/types/internals/transaction_data.rs index 2bc77ca0f73..502be0dc22c 100644 --- a/core/lib/multivm/src/versions/vm_latest/types/internals/transaction_data.rs +++ b/core/lib/multivm/src/versions/vm_latest/types/internals/transaction_data.rs @@ -91,7 +91,7 @@ impl From for TransactionData { ], data: execute_tx.execute.calldata, signature: common_data.signature, - factory_deps: execute_tx.execute.factory_deps.unwrap_or_default(), + factory_deps: execute_tx.execute.factory_deps, paymaster_input: common_data.paymaster_params.paymaster_input, reserved_dynamic: vec![], raw_bytes: execute_tx.raw_bytes.map(|a| a.0), @@ -121,7 +121,7 @@ impl From for TransactionData { data: execute_tx.execute.calldata, // The signature isn't checked for L1 transactions so we don't care signature: vec![], - factory_deps: execute_tx.execute.factory_deps.unwrap_or_default(), + factory_deps: execute_tx.execute.factory_deps, paymaster_input: vec![], reserved_dynamic: vec![], raw_bytes: None, @@ -151,7 +151,7 @@ impl From for TransactionData { data: execute_tx.execute.calldata, // The signature isn't checked for L1 transactions so we don't care signature: vec![], - factory_deps: execute_tx.execute.factory_deps.unwrap_or_default(), + factory_deps: execute_tx.execute.factory_deps, paymaster_input: vec![], reserved_dynamic: vec![], raw_bytes: None, @@ -278,12 +278,11 @@ impl TryInto for TransactionData { paymaster_input: self.paymaster_input, }, }; - let factory_deps = (!self.factory_deps.is_empty()).then_some(self.factory_deps); let execute = Execute { contract_address: self.to, value: self.value, calldata: self.data, - factory_deps, + factory_deps: self.factory_deps, }; Ok(L2Tx { diff --git a/core/lib/multivm/src/versions/vm_m5/test_utils.rs b/core/lib/multivm/src/versions/vm_m5/test_utils.rs index e91b365d534..785eb49835f 100644 --- a/core/lib/multivm/src/versions/vm_m5/test_utils.rs +++ b/core/lib/multivm/src/versions/vm_m5/test_utils.rs @@ -153,7 +153,7 @@ pub fn get_create_execute(code: &[u8], calldata: &[u8]) -> Execute { Execute { contract_address: CONTRACT_DEPLOYER_ADDRESS, calldata, - factory_deps: Some(vec![code.to_vec()]), + factory_deps: vec![code.to_vec()], value: U256::zero(), } } diff --git a/core/lib/multivm/src/versions/vm_m5/transaction_data.rs b/core/lib/multivm/src/versions/vm_m5/transaction_data.rs index 0a093462c1f..7ef739fd5bf 100644 --- a/core/lib/multivm/src/versions/vm_m5/transaction_data.rs +++ b/core/lib/multivm/src/versions/vm_m5/transaction_data.rs @@ -89,7 +89,7 @@ impl From for TransactionData { ], data: execute_tx.execute.calldata, signature: common_data.signature.clone(), - factory_deps: execute_tx.execute.factory_deps.unwrap_or_default(), + factory_deps: execute_tx.execute.factory_deps, paymaster_input: common_data.paymaster_params.paymaster_input.clone(), reserved_dynamic: vec![], } @@ -118,7 +118,7 @@ impl From for TransactionData { data: execute_tx.execute.calldata, // The signature isn't checked for L1 transactions so we don't care signature: vec![], - factory_deps: execute_tx.execute.factory_deps.unwrap_or_default(), + factory_deps: execute_tx.execute.factory_deps, paymaster_input: vec![], reserved_dynamic: vec![], } diff --git a/core/lib/multivm/src/versions/vm_m6/test_utils.rs b/core/lib/multivm/src/versions/vm_m6/test_utils.rs index bd724dca5ca..ecad7d911b4 100644 --- a/core/lib/multivm/src/versions/vm_m6/test_utils.rs +++ b/core/lib/multivm/src/versions/vm_m6/test_utils.rs @@ -153,7 +153,7 @@ pub fn get_create_execute(code: &[u8], calldata: &[u8]) -> Execute { Execute { contract_address: CONTRACT_DEPLOYER_ADDRESS, calldata, - factory_deps: Some(vec![code.to_vec()]), + factory_deps: vec![code.to_vec()], value: U256::zero(), } } diff --git a/core/lib/multivm/src/versions/vm_m6/transaction_data.rs b/core/lib/multivm/src/versions/vm_m6/transaction_data.rs index 0abac18e5ed..99ce4671c29 100644 --- a/core/lib/multivm/src/versions/vm_m6/transaction_data.rs +++ b/core/lib/multivm/src/versions/vm_m6/transaction_data.rs @@ -90,7 +90,7 @@ impl From for TransactionData { ], data: execute_tx.execute.calldata, signature: common_data.signature.clone(), - factory_deps: execute_tx.execute.factory_deps.unwrap_or_default(), + factory_deps: execute_tx.execute.factory_deps, paymaster_input: common_data.paymaster_params.paymaster_input.clone(), reserved_dynamic: vec![], } @@ -119,7 +119,7 @@ impl From for TransactionData { data: execute_tx.execute.calldata, // The signature isn't checked for L1 transactions so we don't care signature: vec![], - factory_deps: execute_tx.execute.factory_deps.unwrap_or_default(), + factory_deps: execute_tx.execute.factory_deps, paymaster_input: vec![], reserved_dynamic: vec![], } @@ -148,7 +148,7 @@ impl From for TransactionData { data: execute_tx.execute.calldata, // The signature isn't checked for L1 transactions so we don't care signature: vec![], - factory_deps: execute_tx.execute.factory_deps.unwrap_or_default(), + factory_deps: execute_tx.execute.factory_deps, paymaster_input: vec![], reserved_dynamic: vec![], } diff --git a/core/lib/multivm/src/versions/vm_m6/vm.rs b/core/lib/multivm/src/versions/vm_m6/vm.rs index 36303c57744..8fd512ef575 100644 --- a/core/lib/multivm/src/versions/vm_m6/vm.rs +++ b/core/lib/multivm/src/versions/vm_m6/vm.rs @@ -224,7 +224,7 @@ impl VmInterface for Vm { self.last_tx_compressed_bytecodes = vec![]; let bytecodes = if with_compression { - let deps = tx.execute.factory_deps.as_deref().unwrap_or_default(); + let deps = &tx.execute.factory_deps; let mut deps_hashes = HashSet::with_capacity(deps.len()); let mut bytecode_hashes = vec![]; let filtered_deps = deps.iter().filter_map(|bytecode| { diff --git a/core/lib/multivm/src/versions/vm_refunds_enhancement/types/internals/transaction_data.rs b/core/lib/multivm/src/versions/vm_refunds_enhancement/types/internals/transaction_data.rs index b7ad5e64094..205090ba633 100644 --- a/core/lib/multivm/src/versions/vm_refunds_enhancement/types/internals/transaction_data.rs +++ b/core/lib/multivm/src/versions/vm_refunds_enhancement/types/internals/transaction_data.rs @@ -91,7 +91,7 @@ impl From for TransactionData { ], data: execute_tx.execute.calldata, signature: common_data.signature, - factory_deps: execute_tx.execute.factory_deps.unwrap_or_default(), + factory_deps: execute_tx.execute.factory_deps, paymaster_input: common_data.paymaster_params.paymaster_input, reserved_dynamic: vec![], raw_bytes: execute_tx.raw_bytes.map(|a| a.0), @@ -121,7 +121,7 @@ impl From for TransactionData { data: execute_tx.execute.calldata, // The signature isn't checked for L1 transactions so we don't care signature: vec![], - factory_deps: execute_tx.execute.factory_deps.unwrap_or_default(), + factory_deps: execute_tx.execute.factory_deps, paymaster_input: vec![], reserved_dynamic: vec![], raw_bytes: None, @@ -151,7 +151,7 @@ impl From for TransactionData { data: execute_tx.execute.calldata, // The signature isn't checked for L1 transactions so we don't care signature: vec![], - factory_deps: execute_tx.execute.factory_deps.unwrap_or_default(), + factory_deps: execute_tx.execute.factory_deps, paymaster_input: vec![], reserved_dynamic: vec![], raw_bytes: None, @@ -298,12 +298,11 @@ impl TryInto for TransactionData { paymaster_input: self.paymaster_input, }, }; - let factory_deps = (!self.factory_deps.is_empty()).then_some(self.factory_deps); let execute = Execute { contract_address: self.to, value: self.value, calldata: self.data, - factory_deps, + factory_deps: self.factory_deps, }; Ok(L2Tx { diff --git a/core/lib/multivm/src/versions/vm_virtual_blocks/types/internals/transaction_data.rs b/core/lib/multivm/src/versions/vm_virtual_blocks/types/internals/transaction_data.rs index a62b96ca92f..b42950399f6 100644 --- a/core/lib/multivm/src/versions/vm_virtual_blocks/types/internals/transaction_data.rs +++ b/core/lib/multivm/src/versions/vm_virtual_blocks/types/internals/transaction_data.rs @@ -91,7 +91,7 @@ impl From for TransactionData { ], data: execute_tx.execute.calldata, signature: common_data.signature, - factory_deps: execute_tx.execute.factory_deps.unwrap_or_default(), + factory_deps: execute_tx.execute.factory_deps, paymaster_input: common_data.paymaster_params.paymaster_input, reserved_dynamic: vec![], raw_bytes: execute_tx.raw_bytes.map(|a| a.0), @@ -121,7 +121,7 @@ impl From for TransactionData { data: execute_tx.execute.calldata, // The signature isn't checked for L1 transactions so we don't care signature: vec![], - factory_deps: execute_tx.execute.factory_deps.unwrap_or_default(), + factory_deps: execute_tx.execute.factory_deps, paymaster_input: vec![], reserved_dynamic: vec![], raw_bytes: None, @@ -151,7 +151,7 @@ impl From for TransactionData { data: execute_tx.execute.calldata, // The signature isn't checked for L1 transactions so we don't care signature: vec![], - factory_deps: execute_tx.execute.factory_deps.unwrap_or_default(), + factory_deps: execute_tx.execute.factory_deps, paymaster_input: vec![], reserved_dynamic: vec![], raw_bytes: None, @@ -298,12 +298,11 @@ impl TryInto for TransactionData { paymaster_input: self.paymaster_input, }, }; - let factory_deps = (!self.factory_deps.is_empty()).then_some(self.factory_deps); let execute = Execute { contract_address: self.to, value: self.value, calldata: self.data, - factory_deps, + factory_deps: self.factory_deps, }; Ok(L2Tx { diff --git a/core/lib/types/src/abi.rs b/core/lib/types/src/abi.rs index 5778c4d8d40..84f8aba6486 100644 --- a/core/lib/types/src/abi.rs +++ b/core/lib/types/src/abi.rs @@ -338,7 +338,6 @@ pub enum Transaction { factory_deps: Vec>, /// Auxiliary data, not hashed. eth_block: u64, - received_timestamp_ms: u64, }, /// RLP encoding of a L2 transaction. L2(Vec), diff --git a/core/lib/types/src/l1/mod.rs b/core/lib/types/src/l1/mod.rs index 796a8621c39..348600b6ee8 100644 --- a/core/lib/types/src/l1/mod.rs +++ b/core/lib/types/src/l1/mod.rs @@ -266,7 +266,7 @@ impl L1Tx { impl From for abi::NewPriorityRequest { fn from(t: L1Tx) -> Self { - let factory_deps = t.execute.factory_deps.unwrap_or_default(); + let factory_deps = t.execute.factory_deps; Self { tx_id: t.common_data.serial_id.0.into(), tx_hash: t.common_data.canonical_tx_hash.to_fixed_bytes(), @@ -347,7 +347,7 @@ impl TryFrom for L1Tx { let execute = Execute { contract_address: u256_to_account_address(&req.transaction.to), calldata: req.transaction.data, - factory_deps: Some(req.factory_deps), + factory_deps: req.factory_deps, value: req.transaction.value, }; Ok(Self { diff --git a/core/lib/types/src/l2/mod.rs b/core/lib/types/src/l2/mod.rs index 38d26cf0232..57edc6181c8 100644 --- a/core/lib/types/src/l2/mod.rs +++ b/core/lib/types/src/l2/mod.rs @@ -15,8 +15,8 @@ use crate::{ transaction_request::PaymasterParams, tx::Execute, web3::Bytes, - Address, EIP712TypedStructure, Eip712Domain, ExecuteTransactionCommon, InputData, L2ChainId, - Nonce, PackedEthSignature, StructBuilder, Transaction, EIP_1559_TX_TYPE, EIP_2930_TX_TYPE, + Address, EIP712TypedStructure, ExecuteTransactionCommon, InputData, L2ChainId, Nonce, + PackedEthSignature, StructBuilder, Transaction, EIP_1559_TX_TYPE, EIP_2930_TX_TYPE, EIP_712_TX_TYPE, H256, LEGACY_TX_TYPE, PRIORITY_OPERATION_L2_TX_TYPE, PROTOCOL_UPGRADE_TX_TYPE, U256, U64, }; @@ -159,7 +159,7 @@ impl L2Tx { fee: Fee, initiator_address: Address, value: U256, - factory_deps: Option>>, + factory_deps: Vec>, paymaster_params: PaymasterParams, ) -> Self { Self { @@ -192,11 +192,11 @@ impl L2Tx { value: U256, chain_id: L2ChainId, private_key: &K256PrivateKey, - factory_deps: Option>>, + factory_deps: Vec>, paymaster_params: PaymasterParams, ) -> Result { let initiator_address = private_key.address(); - let mut res = Self::new( + let tx = Self::new( contract_address, calldata, nonce, @@ -206,10 +206,19 @@ impl L2Tx { factory_deps, paymaster_params, ); - - let data = res.get_signed_bytes(chain_id); - res.set_signature(PackedEthSignature::sign_raw(private_key, &data).context("sign_raw")?); - Ok(res) + // We do a whole dance to reconstruct missing data: RLP encoding, hash and signature. + let mut req: TransactionRequest = tx.into(); + req.chain_id = Some(chain_id.as_u64()); + let data = req + .get_default_signed_message() + .context("get_default_signed_message()")?; + let sig = PackedEthSignature::sign_raw(private_key, &data).context("sign_raw")?; + let raw = req.get_signed_bytes(&sig).context("get_signed_bytes")?; + let (req, hash) = + TransactionRequest::from_bytes_unverified(&raw).context("from_bytes_unverified()")?; + let mut tx = L2Tx::from_request_unverified(req).context("from_request_unverified()")?; + tx.set_input(raw, hash); + Ok(tx) } /// Returns the hash of the transaction. @@ -237,18 +246,10 @@ impl L2Tx { } pub fn get_signed_bytes(&self, chain_id: L2ChainId) -> H256 { - let mut tx: TransactionRequest = self.clone().into(); - tx.chain_id = Some(chain_id.as_u64()); - if tx.is_eip712_tx() { - PackedEthSignature::typed_data_to_signed_bytes(&Eip712Domain::new(chain_id), &tx) - } else { - // It is ok to unwrap, because the `chain_id` is set. - let mut data = tx.get_rlp().unwrap(); - if let Some(tx_type) = tx.transaction_type { - data.insert(0, tx_type.as_u32() as u8); - } - PackedEthSignature::message_to_signed_bytes(&data) - } + let mut req: TransactionRequest = self.clone().into(); + req.chain_id = Some(chain_id.as_u64()); + // It is ok to unwrap, because the `chain_id` is set. + req.get_default_signed_message().unwrap() } pub fn set_signature(&mut self, signature: PackedEthSignature) { @@ -266,7 +267,7 @@ impl L2Tx { pub fn abi_encoding_len(&self) -> usize { let data_len = self.execute.calldata.len(); let signature_len = self.common_data.signature.len(); - let factory_deps_len = self.execute.factory_deps_length(); + let factory_deps_len = self.execute.factory_deps.len(); let paymaster_input_len = self.common_data.paymaster_params.paymaster_input.len(); encoding_len( @@ -289,9 +290,8 @@ impl L2Tx { pub fn factory_deps_len(&self) -> u32 { self.execute .factory_deps - .as_ref() - .map(|deps| deps.iter().fold(0u32, |len, item| len + item.len() as u32)) - .unwrap_or_default() + .iter() + .fold(0u32, |len, item| len + item.len() as u32) } } @@ -486,7 +486,7 @@ mod tests { contract_address: Default::default(), calldata: vec![], value: U256::zero(), - factory_deps: None, + factory_deps: vec![], }, common_data: L2TxCommonData { nonce: Nonce(0), diff --git a/core/lib/types/src/lib.rs b/core/lib/types/src/lib.rs index fd5af40e35f..2617bf0e498 100644 --- a/core/lib/types/src/lib.rs +++ b/core/lib/types/src/lib.rs @@ -192,12 +192,7 @@ impl Transaction { // Returns how many slots it takes to encode the transaction pub fn encoding_len(&self) -> usize { let data_len = self.execute.calldata.len(); - let factory_deps_len = self - .execute - .factory_deps - .as_ref() - .map(|deps| deps.len()) - .unwrap_or_default(); + let factory_deps_len = self.execute.factory_deps.len(); let (signature_len, paymaster_input_len) = match &self.common_data { ExecuteTransactionCommon::L1(_) => (0, 0), ExecuteTransactionCommon::L2(l2_common_data) => ( @@ -251,7 +246,7 @@ impl TryFrom for abi::Transaction { fn try_from(tx: Transaction) -> anyhow::Result { use ExecuteTransactionCommon as E; - let factory_deps = tx.execute.factory_deps.unwrap_or_default(); + let factory_deps = tx.execute.factory_deps; Ok(match tx.common_data { E::L2(data) => Self::L2( data.input @@ -288,7 +283,6 @@ impl TryFrom for abi::Transaction { .into(), factory_deps, eth_block: data.eth_block, - received_timestamp_ms: tx.received_timestamp_ms, }, E::ProtocolUpgrade(data) => Self::L1 { tx: abi::L2CanonicalTransaction { @@ -320,7 +314,6 @@ impl TryFrom for abi::Transaction { .into(), factory_deps, eth_block: data.eth_block, - received_timestamp_ms: tx.received_timestamp_ms, }, }) } @@ -334,7 +327,6 @@ impl TryFrom for Transaction { tx, factory_deps, eth_block, - received_timestamp_ms, } => { let factory_deps_hashes: Vec<_> = factory_deps .iter() @@ -391,17 +383,19 @@ impl TryFrom for Transaction { execute: Execute { contract_address: u256_to_account_address(&tx.to), calldata: tx.data, - factory_deps: Some(factory_deps), + factory_deps, value: tx.value, }, raw_bytes: None, - received_timestamp_ms, + received_timestamp_ms: helpers::unix_timestamp_ms(), } } abi::Transaction::L2(raw) => { - let (req, _) = + let (req, hash) = transaction_request::TransactionRequest::from_bytes_unverified(&raw)?; - L2Tx::from_request_unverified(req)?.into() + let mut tx = L2Tx::from_request_unverified(req)?; + tx.set_input(raw, hash); + tx.into() } }) } diff --git a/core/lib/types/src/protocol_upgrade.rs b/core/lib/types/src/protocol_upgrade.rs index d3951f44962..c1bcc2f5cac 100644 --- a/core/lib/types/src/protocol_upgrade.rs +++ b/core/lib/types/src/protocol_upgrade.rs @@ -15,8 +15,8 @@ use zksync_contracts::{ use zksync_utils::h256_to_u256; use crate::{ - abi, ethabi::ParamType, helpers, web3::Log, Address, Execute, ExecuteTransactionCommon, - Transaction, TransactionType, H256, U256, + abi, ethabi::ParamType, web3::Log, Address, Execute, ExecuteTransactionCommon, Transaction, + TransactionType, H256, U256, }; /// Represents a call to be made during governance operation. @@ -125,7 +125,6 @@ impl ProtocolUpgrade { tx: upgrade.l2_protocol_upgrade_tx, factory_deps: upgrade.factory_deps, eth_block, - received_timestamp_ms: helpers::unix_timestamp_ms(), }) .context("Transaction::try_from()")? .try_into() @@ -154,7 +153,6 @@ pub fn decode_set_chain_id_event( .expect("Event block number is missing") .as_u64(), factory_deps: vec![], - received_timestamp_ms: helpers::unix_timestamp_ms(), }) .unwrap() .try_into() diff --git a/core/lib/types/src/transaction_request.rs b/core/lib/types/src/transaction_request.rs index 7cf2d9f432b..a59b21409cd 100644 --- a/core/lib/types/src/transaction_request.rs +++ b/core/lib/types/src/transaction_request.rs @@ -223,13 +223,11 @@ pub enum SerializationTransactionError { GasPerPubDataLimitZero, } +#[derive(Clone, Debug, PartialEq, Default)] /// Description of a Transaction, pending or in the chain. -#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Default)] -#[serde(rename_all = "camelCase")] pub struct TransactionRequest { /// Nonce pub nonce: U256, - #[serde(default, skip_serializing_if = "Option::is_none")] pub from: Option
, /// Recipient (None when contract creation) pub to: Option
, @@ -240,32 +238,23 @@ pub struct TransactionRequest { /// Gas amount pub gas: U256, /// EIP-1559 part of gas price that goes to miners - #[serde(default, skip_serializing_if = "Option::is_none")] pub max_priority_fee_per_gas: Option, /// Input data pub input: Bytes, /// ECDSA recovery id - #[serde(default, skip_serializing_if = "Option::is_none")] pub v: Option, /// ECDSA signature r, 32 bytes - #[serde(default, skip_serializing_if = "Option::is_none")] pub r: Option, /// ECDSA signature s, 32 bytes - #[serde(default, skip_serializing_if = "Option::is_none")] pub s: Option, /// Raw transaction data - #[serde(default, skip_serializing_if = "Option::is_none")] pub raw: Option, /// Transaction type, Some(1) for AccessList transaction, None for Legacy - #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")] pub transaction_type: Option, /// Access list - #[serde(default, skip_serializing_if = "Option::is_none")] pub access_list: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] pub eip712_meta: Option, /// Chain ID - #[serde(default, skip_serializing_if = "Option::is_none")] pub chain_id: Option, } @@ -299,7 +288,7 @@ impl PaymasterParams { pub struct Eip712Meta { pub gas_per_pubdata: U256, #[serde(default)] - pub factory_deps: Option>>, + pub factory_deps: Vec>, pub custom_signature: Option>, pub paymaster_params: Option, } @@ -307,13 +296,9 @@ pub struct Eip712Meta { impl Eip712Meta { pub fn rlp_append(&self, rlp: &mut RlpStream) { rlp.append(&self.gas_per_pubdata); - if let Some(factory_deps) = &self.factory_deps { - rlp.begin_list(factory_deps.len()); - for dep in factory_deps.iter() { - rlp.append(&dep.as_slice()); - } - } else { - rlp.begin_list(0); + rlp.begin_list(self.factory_deps.len()); + for dep in &self.factory_deps { + rlp.append(&dep.as_slice()); } rlp_opt(rlp, &self.custom_signature); @@ -383,30 +368,34 @@ impl EIP712TypedStructure for TransactionRequest { impl TransactionRequest { pub fn get_custom_signature(&self) -> Option> { - self.eip712_meta - .as_ref() - .and_then(|meta| meta.custom_signature.as_ref()) - .cloned() + self.eip712_meta.as_ref()?.custom_signature.clone() } pub fn get_paymaster(&self) -> Option
{ - self.eip712_meta - .clone() - .and_then(|meta| meta.paymaster_params) - .map(|params| params.paymaster) + Some( + self.eip712_meta + .as_ref()? + .paymaster_params + .as_ref()? + .paymaster, + ) } pub fn get_paymaster_input(&self) -> Option> { - self.eip712_meta - .clone() - .and_then(|meta| meta.paymaster_params) - .map(|params| params.paymaster_input) + Some( + self.eip712_meta + .as_ref()? + .paymaster_params + .as_ref()? + .paymaster_input + .clone(), + ) } pub fn get_factory_deps(&self) -> Vec> { self.eip712_meta - .clone() - .and_then(|meta| meta.factory_deps) + .as_ref() + .map(|meta| meta.factory_deps.clone()) .unwrap_or_default() } @@ -476,7 +465,7 @@ impl TransactionRequest { /// Encodes `TransactionRequest` to RLP. /// It may fail if `chain_id` is `None` while required. - pub fn get_rlp(&self) -> anyhow::Result> { + pub fn get_rlp(&self) -> Result, SerializationTransactionError> { let mut rlp_stream = RlpStream::new(); self.rlp(&mut rlp_stream, None)?; Ok(rlp_stream.as_raw().into()) @@ -670,7 +659,7 @@ impl TransactionRequest { s: Some(rlp.val_at(9)?), eip712_meta: Some(Eip712Meta { gas_per_pubdata: rlp.val_at(12)?, - factory_deps: rlp.list_at(13).ok(), + factory_deps: rlp.list_at(13)?, custom_signature: rlp.val_at(14).ok(), paymaster_params: if let Ok(params) = rlp.list_at(15) { PaymasterParams::from_vector(params)? @@ -689,21 +678,16 @@ impl TransactionRequest { } _ => return Err(SerializationTransactionError::UnknownTransactionFormat), }; - let factory_deps_ref = tx - .eip712_meta - .as_ref() - .and_then(|m| m.factory_deps.as_ref()); - if let Some(deps) = factory_deps_ref { - validate_factory_deps(deps)?; + if let Some(meta) = &tx.eip712_meta { + validate_factory_deps(&meta.factory_deps)?; } tx.raw = Some(Bytes(bytes.to_vec())); let default_signed_message = tx.get_default_signed_message()?; - tx.from = match tx.from { - Some(_) => tx.from, - None => tx.recover_default_signer(default_signed_message).ok(), - }; + if tx.from.is_none() { + tx.from = tx.recover_default_signer(default_signed_message).ok(); + } // `tx.raw` is set, so unwrap is safe here. let hash = tx @@ -723,7 +707,7 @@ impl TransactionRequest { Ok((tx, hash)) } - fn get_default_signed_message(&self) -> Result { + pub fn get_default_signed_message(&self) -> Result { if self.is_eip712_tx() { let chain_id = self .chain_id @@ -733,9 +717,7 @@ impl TransactionRequest { self, )) } else { - let mut rlp_stream = RlpStream::new(); - self.rlp(&mut rlp_stream, None)?; - let mut data = rlp_stream.out().to_vec(); + let mut data = self.get_rlp()?; if let Some(tx_type) = self.transaction_type { data.insert(0, tx_type.as_u64() as u8); } @@ -824,21 +806,14 @@ impl TransactionRequest { impl L2Tx { pub(crate) fn from_request_unverified( - value: TransactionRequest, + mut value: TransactionRequest, ) -> Result { let fee = value.get_fee_data_checked()?; let nonce = value.get_nonce_checked()?; let raw_signature = value.get_signature().unwrap_or_default(); - // Destruct `eip712_meta` in one go to avoid cloning. - let (factory_deps, paymaster_params) = value - .eip712_meta - .map(|eip712_meta| (eip712_meta.factory_deps, eip712_meta.paymaster_params)) - .unwrap_or_default(); - - if let Some(deps) = factory_deps.as_ref() { - validate_factory_deps(deps)?; - } + let meta = value.eip712_meta.take().unwrap_or_default(); + validate_factory_deps(&meta.factory_deps)?; let mut tx = L2Tx::new( value @@ -849,8 +824,8 @@ impl L2Tx { fee, value.from.unwrap_or_default(), value.value, - factory_deps, - paymaster_params.unwrap_or_default(), + meta.factory_deps, + meta.paymaster_params.unwrap_or_default(), ); tx.common_data.transaction_type = match value.transaction_type.map(|t| t.as_u64() as u8) { @@ -895,7 +870,7 @@ impl From for CallRequest { fn from(tx: L2Tx) -> Self { let mut meta = Eip712Meta { gas_per_pubdata: tx.common_data.fee.gas_per_pubdata_limit, - factory_deps: None, + factory_deps: vec![], custom_signature: Some(tx.common_data.signature.clone()), paymaster_params: Some(tx.common_data.paymaster_params.clone()), }; @@ -1060,7 +1035,7 @@ mod tests { transaction_type: Some(U64::from(EIP_712_TX_TYPE)), eip712_meta: Some(Eip712Meta { gas_per_pubdata: U256::from(4u32), - factory_deps: Some(vec![vec![2; 32]]), + factory_deps: vec![vec![2; 32]], custom_signature: Some(vec![1, 2, 3]), paymaster_params: Some(PaymasterParams { paymaster: Default::default(), @@ -1108,7 +1083,7 @@ mod tests { transaction_type: Some(U64::from(EIP_712_TX_TYPE)), eip712_meta: Some(Eip712Meta { gas_per_pubdata: U256::from(4u32), - factory_deps: Some(vec![vec![2; 32]]), + factory_deps: vec![vec![2; 32]], custom_signature: Some(vec![]), paymaster_params: None, }), @@ -1145,7 +1120,7 @@ mod tests { transaction_type: Some(U64::from(EIP_712_TX_TYPE)), eip712_meta: Some(Eip712Meta { gas_per_pubdata: U256::from(4u32), - factory_deps: Some(vec![vec![2; 32]]), + factory_deps: vec![vec![2; 32]], custom_signature: Some(vec![1, 2, 3]), paymaster_params: Some(PaymasterParams { paymaster: Default::default(), @@ -1423,7 +1398,7 @@ mod tests { transaction_type: Some(U64::from(EIP_712_TX_TYPE)), eip712_meta: Some(Eip712Meta { gas_per_pubdata: U256::from(4u32), - factory_deps: Some(factory_deps), + factory_deps, custom_signature: Some(vec![1, 2, 3]), paymaster_params: Some(PaymasterParams { paymaster: Default::default(), diff --git a/core/lib/types/src/tx/execute.rs b/core/lib/types/src/tx/execute.rs index e54f469b135..22546df99cb 100644 --- a/core/lib/types/src/tx/execute.rs +++ b/core/lib/types/src/tx/execute.rs @@ -16,18 +16,13 @@ pub struct Execute { pub value: U256, /// Factory dependencies: list of contract bytecodes associated with the deploy transaction. - /// This field is always `None` for all the transaction that do not cause the contract deployment. - /// For the deployment transactions, this field is always `Some`, even if there s no "dependencies" for the - /// contract being deployed, since the bytecode of the contract itself is also included into this list. - pub factory_deps: Option>>, + #[serde(default)] + pub factory_deps: Vec>, } impl std::fmt::Debug for Execute { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let factory_deps = match &self.factory_deps { - Some(deps) => format!("Some(<{} factory deps>)", deps.len()), - None => "None".to_string(), - }; + let factory_deps = format!("<{} factory deps>", self.factory_deps.len()); f.debug_struct("Execute") .field("contract_address", &self.contract_address) .field("calldata", &hex::encode(&self.calldata)) @@ -83,12 +78,4 @@ impl Execute { FUNCTION_SIGNATURE.iter().copied().chain(params).collect() } - - /// Number of new factory dependencies in this transaction - pub fn factory_deps_length(&self) -> usize { - self.factory_deps - .as_ref() - .map(|deps| deps.len()) - .unwrap_or_default() - } } diff --git a/core/node/api_server/src/execution_sandbox/execute.rs b/core/node/api_server/src/execution_sandbox/execute.rs index 72c94e2a428..9a844df2867 100644 --- a/core/node/api_server/src/execution_sandbox/execute.rs +++ b/core/node/api_server/src/execution_sandbox/execute.rs @@ -117,11 +117,7 @@ impl TransactionExecutor { return mock_executor.execute_tx(&tx, &block_args); } - let total_factory_deps = tx - .execute - .factory_deps - .as_ref() - .map_or(0, |deps| deps.len() as u16); + let total_factory_deps = tx.execute.factory_deps.len() as u16; let (published_bytecodes, execution_result) = tokio::task::spawn_blocking(move || { let span = span!(Level::DEBUG, "execute_in_sandbox").entered(); diff --git a/core/node/api_server/src/tx_sender/mod.rs b/core/node/api_server/src/tx_sender/mod.rs index c4fd6dff692..1dd3f4c6e94 100644 --- a/core/node/api_server/src/tx_sender/mod.rs +++ b/core/node/api_server/src/tx_sender/mod.rs @@ -531,9 +531,9 @@ impl TxSender { ); return Err(SubmitTxError::MaxPriorityFeeGreaterThanMaxFee); } - if tx.execute.factory_deps_length() > MAX_NEW_FACTORY_DEPS { + if tx.execute.factory_deps.len() > MAX_NEW_FACTORY_DEPS { return Err(SubmitTxError::TooManyFactoryDependencies( - tx.execute.factory_deps_length(), + tx.execute.factory_deps.len(), MAX_NEW_FACTORY_DEPS, )); } diff --git a/core/node/consensus/Cargo.toml b/core/node/consensus/Cargo.toml index 9cfb3c86b0b..b22fde34e7c 100644 --- a/core/node/consensus/Cargo.toml +++ b/core/node/consensus/Cargo.toml @@ -21,20 +21,28 @@ zksync_consensus_bft.workspace = true zksync_consensus_utils.workspace = true zksync_protobuf.workspace = true zksync_dal.workspace = true +zksync_state.workspace = true +zksync_l1_contract_interface.workspace = true +zksync_metadata_calculator.workspace = true +zksync_merkle_tree.workspace = true zksync_state_keeper.workspace = true zksync_node_sync.workspace = true +zksync_system_constants.workspace = true zksync_types.workspace = true +zksync_utils.workspace = true zksync_web3_decl.workspace = true anyhow.workspace = true async-trait.workspace = true secrecy.workspace = true +tempfile.workspace = true tracing.workspace = true [dev-dependencies] zksync_node_genesis.workspace = true zksync_node_test_utils.workspace = true zksync_node_api_server.workspace = true +zksync_test_account.workspace = true tokio.workspace = true test-casing.workspace = true diff --git a/core/node/consensus/src/batch.rs b/core/node/consensus/src/batch.rs new file mode 100644 index 00000000000..d393a845ec6 --- /dev/null +++ b/core/node/consensus/src/batch.rs @@ -0,0 +1,275 @@ +//! L1 Batch representation for sending over p2p network. +use anyhow::Context as _; +use zksync_concurrency::{ctx, error::Wrap as _}; +use zksync_consensus_roles::validator; +use zksync_dal::consensus_dal::Payload; +use zksync_l1_contract_interface::i_executor; +use zksync_metadata_calculator::api_server::{TreeApiClient, TreeEntryWithProof}; +use zksync_system_constants as constants; +use zksync_types::{ + abi, + block::{unpack_block_info, L2BlockHasher}, + AccountTreeId, L1BatchNumber, L2BlockNumber, ProtocolVersionId, StorageKey, Transaction, H256, + U256, +}; +use zksync_utils::{h256_to_u256, u256_to_h256}; + +use crate::ConnectionPool; + +/// Commitment to the last block of a batch. +pub(crate) struct LastBlockCommit { + /// Hash of the `StoredBatchInfo` which is stored on L1. + /// The hashed `StoredBatchInfo` contains a `root_hash` of the L2 state, + /// which contains state of the `SystemContext` contract, + /// which contains enough data to reconstruct the hash + /// of the last L2 block of the batch. + pub(crate) info: H256, +} + +/// Witness proving what is the last block of a batch. +/// Contains the hash and the number of the last block. +pub(crate) struct LastBlockWitness { + info: i_executor::structures::StoredBatchInfo, + protocol_version: ProtocolVersionId, + + current_l2_block_info: TreeEntryWithProof, + tx_rolling_hash: TreeEntryWithProof, + l2_block_hash_entry: TreeEntryWithProof, +} + +/// Commitment to an L1 batch. +pub(crate) struct L1BatchCommit { + pub(crate) number: L1BatchNumber, + pub(crate) this_batch: LastBlockCommit, + pub(crate) prev_batch: LastBlockCommit, +} + +/// L1Batch with witness that can be +/// verified against `L1BatchCommit`. +pub struct L1BatchWithWitness { + pub(crate) blocks: Vec, + pub(crate) this_batch: LastBlockWitness, + pub(crate) prev_batch: LastBlockWitness, +} + +impl LastBlockWitness { + /// Address of the SystemContext contract. + fn system_context_addr() -> AccountTreeId { + AccountTreeId::new(constants::SYSTEM_CONTEXT_ADDRESS) + } + + /// Storage key of the `SystemContext.current_l2_block_info` field. + fn current_l2_block_info_key() -> U256 { + StorageKey::new( + Self::system_context_addr(), + constants::SYSTEM_CONTEXT_CURRENT_L2_BLOCK_INFO_POSITION, + ) + .hashed_key_u256() + } + + /// Storage key of the `SystemContext.tx_rolling_hash` field. + fn tx_rolling_hash_key() -> U256 { + StorageKey::new( + Self::system_context_addr(), + constants::SYSTEM_CONTEXT_CURRENT_TX_ROLLING_HASH_POSITION, + ) + .hashed_key_u256() + } + + /// Storage key of the entry of the `SystemContext.l2BlockHash[]` array, corresponding to l2 + /// block with number i. + fn l2_block_hash_entry_key(i: L2BlockNumber) -> U256 { + let key = h256_to_u256(constants::SYSTEM_CONTEXT_CURRENT_L2_BLOCK_HASHES_POSITION) + + U256::from(i.0) % U256::from(constants::SYSTEM_CONTEXT_STORED_L2_BLOCK_HASHES); + StorageKey::new(Self::system_context_addr(), u256_to_h256(key)).hashed_key_u256() + } + + /// Loads a `LastBlockWitness` from storage. + async fn load( + ctx: &ctx::Ctx, + n: L1BatchNumber, + pool: &ConnectionPool, + tree: &dyn TreeApiClient, + ) -> ctx::Result { + let mut conn = pool.connection(ctx).await.wrap("pool.connection()")?; + let batch = conn + .batch(ctx, n) + .await + .wrap("batch()")? + .context("batch not in storage")?; + + let proofs = tree + .get_proofs( + n, + vec![ + Self::current_l2_block_info_key(), + Self::tx_rolling_hash_key(), + ], + ) + .await + .context("get_proofs()")?; + if proofs.len() != 2 { + return Err(anyhow::format_err!("proofs.len()!=2").into()); + } + let current_l2_block_info = proofs[0].clone(); + let tx_rolling_hash = proofs[1].clone(); + let (block_number, _) = unpack_block_info(current_l2_block_info.value.as_bytes().into()); + let prev = L2BlockNumber( + block_number + .checked_sub(1) + .context("L2BlockNumber underflow")? + .try_into() + .context("L2BlockNumber overflow")?, + ); + let proofs = tree + .get_proofs(n, vec![Self::l2_block_hash_entry_key(prev)]) + .await + .context("get_proofs()")?; + if proofs.len() != 1 { + return Err(anyhow::format_err!("proofs.len()!=1").into()); + } + let l2_block_hash_entry = proofs[0].clone(); + Ok(Self { + info: i_executor::structures::StoredBatchInfo::from(&batch), + protocol_version: batch + .header + .protocol_version + .context("missing protocol_version")?, + + current_l2_block_info, + tx_rolling_hash, + l2_block_hash_entry, + }) + } + + /// Verifies the proof against the commit and returns the hash + /// of the last L2 block. + pub(crate) fn verify(&self, comm: &LastBlockCommit) -> anyhow::Result<(L2BlockNumber, H256)> { + // Verify info. + anyhow::ensure!(comm.info == self.info.hash()); + + // Check the protocol version. + anyhow::ensure!( + self.protocol_version >= ProtocolVersionId::Version13, + "unsupported protocol version" + ); + + let (block_number, block_timestamp) = + unpack_block_info(self.current_l2_block_info.value.as_bytes().into()); + let prev = L2BlockNumber( + block_number + .checked_sub(1) + .context("L2BlockNumber underflow")? + .try_into() + .context("L2BlockNumber overflow")?, + ); + + // Verify merkle paths. + self.current_l2_block_info + .verify(Self::current_l2_block_info_key(), self.info.batch_hash) + .context("invalid merkle path for current_l2_block_info")?; + self.tx_rolling_hash + .verify(Self::tx_rolling_hash_key(), self.info.batch_hash) + .context("invalid merkle path for tx_rolling_hash")?; + self.l2_block_hash_entry + .verify(Self::l2_block_hash_entry_key(prev), self.info.batch_hash) + .context("invalid merkle path for l2_block_hash entry")?; + + let block_number = L2BlockNumber(block_number.try_into().context("block_number overflow")?); + // Derive hash of the last block + Ok(( + block_number, + L2BlockHasher::hash( + block_number, + block_timestamp, + self.l2_block_hash_entry.value, + self.tx_rolling_hash.value, + self.protocol_version, + ), + )) + } + + /// Last L2 block of the batch. + pub fn last_block(&self) -> validator::BlockNumber { + let (n, _) = unpack_block_info(self.current_l2_block_info.value.as_bytes().into()); + validator::BlockNumber(n) + } +} + +impl L1BatchWithWitness { + /// Loads an `L1BatchWithWitness` from storage. + pub(crate) async fn load( + ctx: &ctx::Ctx, + number: L1BatchNumber, + pool: &ConnectionPool, + tree: &dyn TreeApiClient, + ) -> ctx::Result { + let prev_batch = LastBlockWitness::load(ctx, number - 1, pool, tree) + .await + .with_wrap(|| format!("LastBlockWitness::make({})", number - 1))?; + let this_batch = LastBlockWitness::load(ctx, number, pool, tree) + .await + .with_wrap(|| format!("LastBlockWitness::make({number})"))?; + let mut conn = pool.connection(ctx).await.wrap("connection()")?; + let this = Self { + blocks: conn + .payloads( + ctx, + std::ops::Range { + start: prev_batch.last_block() + 1, + end: this_batch.last_block() + 1, + }, + ) + .await + .wrap("payloads()")?, + prev_batch, + this_batch, + }; + Ok(this) + } + + /// Verifies the L1Batch and witness against the commitment. + /// WARNING: the following fields of the payload are not currently verified: + /// * `l1_gas_price` + /// * `l2_fair_gas_price` + /// * `fair_pubdata_price` + /// * `virtual_blocks` + /// * `operator_address` + /// * `protocol_version` (present both in payload and witness, but neither has a commitment) + pub(crate) fn verify(&self, comm: &L1BatchCommit) -> anyhow::Result<()> { + let (last_number, last_hash) = self.this_batch.verify(&comm.this_batch)?; + let (mut prev_number, mut prev_hash) = self.prev_batch.verify(&comm.prev_batch)?; + anyhow::ensure!( + self.prev_batch + .info + .batch_number + .checked_add(1) + .context("batch_number overflow")? + == u64::from(comm.number.0) + ); + anyhow::ensure!(self.this_batch.info.batch_number == u64::from(comm.number.0)); + for (i, b) in self.blocks.iter().enumerate() { + anyhow::ensure!(b.l1_batch_number == comm.number); + anyhow::ensure!(b.protocol_version == self.this_batch.protocol_version); + anyhow::ensure!(b.last_in_batch == (i + 1 == self.blocks.len())); + prev_number += 1; + let mut hasher = L2BlockHasher::new(prev_number, b.timestamp, prev_hash); + for t in &b.transactions { + // Reconstruct transaction by converting it back and forth to `abi::Transaction`. + // This allows us to verify that the transaction actually matches the transaction + // hash. + // TODO: make consensus payload contain `abi::Transaction` instead. + // TODO: currently the payload doesn't contain the block number, which is + // annoying. Consider adding it to payload. + let t2: Transaction = abi::Transaction::try_from(t.clone())?.try_into()?; + anyhow::ensure!(t == &t2); + hasher.push_tx_hash(t.hash()); + } + prev_hash = hasher.finalize(self.this_batch.protocol_version); + anyhow::ensure!(prev_hash == b.hash); + } + anyhow::ensure!(prev_hash == last_hash); + anyhow::ensure!(prev_number == last_number); + Ok(()) + } +} diff --git a/core/node/consensus/src/lib.rs b/core/node/consensus/src/lib.rs index b076b26e274..bc9776c42df 100644 --- a/core/node/consensus/src/lib.rs +++ b/core/node/consensus/src/lib.rs @@ -11,6 +11,10 @@ use zksync_consensus_storage::BlockStore; use crate::storage::{ConnectionPool, Store}; +// Currently `batch` module is only used in tests, +// but will be used in production once batch syncing is implemented in consensus. +#[allow(unused)] +mod batch; mod config; mod en; pub mod era; diff --git a/core/node/consensus/src/storage/mod.rs b/core/node/consensus/src/storage/mod.rs index 658c7a887d5..cf45f89ad11 100644 --- a/core/node/consensus/src/storage/mod.rs +++ b/core/node/consensus/src/storage/mod.rs @@ -13,7 +13,7 @@ use zksync_node_sync::{ SyncState, }; use zksync_state_keeper::io::common::IoCursor; -use zksync_types::L2BlockNumber; +use zksync_types::{commitment::L1BatchWithMetadata, L1BatchNumber, L2BlockNumber}; use super::config; @@ -101,6 +101,18 @@ impl<'a> Connection<'a> { .map_err(DalError::generalize)?) } + /// Wrapper for `consensus_dal().block_payloads()`. + pub async fn payloads( + &mut self, + ctx: &ctx::Ctx, + numbers: std::ops::Range, + ) -> ctx::Result> { + Ok(ctx + .wait(self.0.consensus_dal().block_payloads(numbers)) + .await? + .map_err(DalError::generalize)?) + } + /// Wrapper for `consensus_dal().first_certificate()`. pub async fn first_certificate( &mut self, @@ -166,6 +178,18 @@ impl<'a> Connection<'a> { .context("sqlx")?) } + /// Wrapper for `consensus_dal().get_l1_batch_metadata()`. + pub async fn batch( + &mut self, + ctx: &ctx::Ctx, + number: L1BatchNumber, + ) -> ctx::Result> { + Ok(ctx + .wait(self.0.blocks_dal().get_l1_batch_metadata(number)) + .await? + .context("get_l1_batch_metadata()")?) + } + /// Wrapper for `FetcherCursor::new()`. pub async fn new_payload_queue( &mut self, diff --git a/core/node/consensus/src/storage/testonly.rs b/core/node/consensus/src/storage/testonly.rs index 48feba61e15..ccac1f7e45a 100644 --- a/core/node/consensus/src/storage/testonly.rs +++ b/core/node/consensus/src/storage/testonly.rs @@ -5,6 +5,7 @@ use zksync_concurrency::{ctx, error::Wrap as _, time}; use zksync_consensus_roles::validator; use zksync_node_genesis::{insert_genesis_batch, GenesisParams}; use zksync_node_test_utils::{recover, snapshot, Snapshot}; +use zksync_types::{commitment::L1BatchWithMetadata, L1BatchNumber}; use super::ConnectionPool; @@ -30,6 +31,28 @@ impl ConnectionPool { Ok(()) } + /// Waits for the `number` L1 batch. + pub async fn wait_for_batch( + &self, + ctx: &ctx::Ctx, + number: L1BatchNumber, + ) -> ctx::Result { + const POLL_INTERVAL: time::Duration = time::Duration::milliseconds(50); + loop { + if let Some(payload) = self + .connection(ctx) + .await + .wrap("connection()")? + .batch(ctx, number) + .await + .wrap("batch()")? + { + return Ok(payload); + } + ctx.sleep(POLL_INTERVAL).await?; + } + } + /// Takes a storage snapshot at the last sealed L1 batch. pub(crate) async fn snapshot(&self, ctx: &ctx::Ctx) -> ctx::Result { let mut conn = self.connection(ctx).await.wrap("connection()")?; diff --git a/core/node/consensus/src/testonly.rs b/core/node/consensus/src/testonly.rs index 3b990bf088f..5baa1c7b1ee 100644 --- a/core/node/consensus/src/testonly.rs +++ b/core/node/consensus/src/testonly.rs @@ -1,15 +1,25 @@ //! Utilities for testing the consensus module. - use std::sync::Arc; use anyhow::Context as _; use rand::Rng; use zksync_concurrency::{ctx, error::Wrap as _, scope, sync, time}; -use zksync_config::{configs, configs::consensus as config}; +use zksync_config::{ + configs, + configs::{ + chain::OperationsManagerConfig, + consensus as config, + database::{MerkleTreeConfig, MerkleTreeMode}, + }, +}; use zksync_consensus_crypto::TextFmt as _; use zksync_consensus_network as network; use zksync_consensus_roles::validator; use zksync_dal::{CoreDal, DalError}; +use zksync_l1_contract_interface::i_executor::structures::StoredBatchInfo; +use zksync_metadata_calculator::{ + LazyAsyncTreeReader, MetadataCalculator, MetadataCalculatorConfig, +}; use zksync_node_api_server::web3::{state::InternalApiConfig, testonly::spawn_http_server}; use zksync_node_genesis::GenesisParams; use zksync_node_sync::{ @@ -18,17 +28,29 @@ use zksync_node_sync::{ testonly::MockMainNodeClient, ExternalIO, MainNodeClient, SyncState, }; -use zksync_node_test_utils::{create_l1_batch_metadata, create_l2_transaction}; +use zksync_node_test_utils::{create_l1_batch_metadata, l1_batch_metadata_to_commitment_artifacts}; +use zksync_state::RocksdbStorageOptions; use zksync_state_keeper::{ io::{IoCursor, L1BatchParams, L2BlockParams}, seal_criteria::NoopSealer, - testonly::{test_batch_executor::MockReadStorageFactory, MockBatchExecutor}, - OutputHandler, StateKeeperPersistence, TreeWritesPersistence, ZkSyncStateKeeper, + testonly::{ + fund, l1_transaction, l2_transaction, test_batch_executor::MockReadStorageFactory, + MockBatchExecutor, + }, + AsyncRocksdbCache, MainBatchExecutor, OutputHandler, StateKeeperPersistence, + TreeWritesPersistence, ZkSyncStateKeeper, +}; +use zksync_test_account::Account; +use zksync_types::{ + fee_model::{BatchFeeInput, L1PeggedBatchFeeModelInput}, + Address, L1BatchNumber, L2BlockNumber, L2ChainId, PriorityOpId, ProtocolVersionId, }; -use zksync_types::{Address, L1BatchNumber, L2BlockNumber, L2ChainId, ProtocolVersionId}; use zksync_web3_decl::client::{Client, DynClient, L2}; -use crate::{en, ConnectionPool}; +use crate::{ + batch::{L1BatchCommit, L1BatchWithWitness, LastBlockCommit}, + en, ConnectionPool, +}; /// Fake StateKeeper for tests. pub(super) struct StateKeeper { @@ -38,14 +60,15 @@ pub(super) struct StateKeeper { // timestamp of the last block. last_timestamp: u64, batch_sealed: bool, - - fee_per_gas: u64, - gas_per_pubdata: u64, + // test L2 account + account: Account, + next_priority_op: PriorityOpId, actions_sender: ActionQueueSender, sync_state: SyncState, addr: sync::watch::Receiver>, pool: ConnectionPool, + tree_reader: LazyAsyncTreeReader, } pub(super) fn config(cfg: &network::Config) -> (config::ConsensusConfig, config::ConsensusSecrets) { @@ -92,7 +115,11 @@ pub(super) struct StateKeeperRunner { actions_queue: ActionQueue, sync_state: SyncState, pool: ConnectionPool, + addr: sync::watch::Sender>, + rocksdb_dir: tempfile::TempDir, + metadata_calculator: MetadataCalculator, + account: Account, } impl StateKeeper { @@ -114,24 +141,49 @@ impl StateKeeper { let (actions_sender, actions_queue) = ActionQueue::new(); let addr = sync::watch::channel(None).0; let sync_state = SyncState::default(); + + let rocksdb_dir = tempfile::tempdir().context("tempdir()")?; + let merkle_tree_config = MerkleTreeConfig { + path: rocksdb_dir + .path() + .join("merkle_tree") + .to_string_lossy() + .into(), + mode: MerkleTreeMode::Lightweight, + ..Default::default() + }; + let operation_manager_config = OperationsManagerConfig { + delay_interval: 100, //`100ms` + }; + let config = + MetadataCalculatorConfig::for_main_node(&merkle_tree_config, &operation_manager_config); + let metadata_calculator = MetadataCalculator::new(config, None, pool.0.clone()) + .await + .context("MetadataCalculator::new()")?; + let tree_reader = metadata_calculator.tree_reader(); + let account = Account::random(); Ok(( Self { last_batch: cursor.l1_batch, last_block: cursor.next_l2_block - 1, last_timestamp: cursor.prev_l2_block_timestamp, batch_sealed: !pending_batch, - fee_per_gas: 10, - gas_per_pubdata: 100, + next_priority_op: PriorityOpId(1), actions_sender, sync_state: sync_state.clone(), addr: addr.subscribe(), pool: pool.clone(), + tree_reader, + account: account.clone(), }, StateKeeperRunner { actions_queue, sync_state, pool: pool.clone(), addr, + rocksdb_dir, + metadata_calculator, + account, }, )) } @@ -147,7 +199,10 @@ impl StateKeeper { protocol_version: ProtocolVersionId::latest(), validation_computational_gas_limit: u32::MAX, operator_address: GenesisParams::mock().config().fee_account, - fee_input: Default::default(), + fee_input: BatchFeeInput::L1Pegged(L1PeggedBatchFeeModelInput { + fair_l2_gas_price: 10, + l1_gas_price: 100, + }), first_l2_block: L2BlockParams { timestamp: self.last_timestamp, virtual_blocks: 1, @@ -170,12 +225,18 @@ impl StateKeeper { } /// Pushes a new L2 block with `transactions` transactions to the `StateKeeper`. - pub async fn push_block(&mut self, transactions: usize) { - assert!(transactions > 0); + pub async fn push_random_block(&mut self, rng: &mut impl Rng) { let mut actions = vec![self.open_block()]; - for _ in 0..transactions { - let tx = create_l2_transaction(self.fee_per_gas, self.gas_per_pubdata); - actions.push(FetchedTransaction::new(tx.into()).into()); + for _ in 0..rng.gen_range(3..8) { + let tx = match rng.gen() { + true => l2_transaction(&mut self.account, 1_000_000), + false => { + let tx = l1_transaction(&mut self.account, self.next_priority_op); + self.next_priority_op += 1; + tx + } + }; + actions.push(FetchedTransaction::new(tx).into()); } actions.push(SyncAction::SealL2Block); self.actions_sender.push_actions(actions).await; @@ -198,7 +259,7 @@ impl StateKeeper { if rng.gen_range(0..100) < 20 { self.seal_batch().await; } else { - self.push_block(rng.gen_range(3..8)).await; + self.push_random_block(rng).await; } } } @@ -209,6 +270,49 @@ impl StateKeeper { validator::BlockNumber(self.last_block.0.into()) } + /// Last L1 batch that has been sealed and will have + /// metadata computed eventually. + pub fn last_sealed_batch(&self) -> L1BatchNumber { + self.last_batch - (!self.batch_sealed) as u32 + } + + /// Loads a commitment to L1 batch directly from the database. + // TODO: ideally, we should rather fake fetching it from Ethereum. + // We can use `zksync_eth_client::clients::MockEthereum` for that, + // which implements `EthInterface`. It should be enough to use + // `MockEthereum.with_call_handler()`. + pub async fn load_batch_commit( + &self, + ctx: &ctx::Ctx, + number: L1BatchNumber, + ) -> ctx::Result { + // TODO: we should mock the `eth_sender` as well. + let mut conn = self.pool.connection(ctx).await?; + let this = conn.batch(ctx, number).await?.context("missing batch")?; + let prev = conn + .batch(ctx, number - 1) + .await? + .context("missing batch")?; + Ok(L1BatchCommit { + number, + this_batch: LastBlockCommit { + info: StoredBatchInfo::from(&this).hash(), + }, + prev_batch: LastBlockCommit { + info: StoredBatchInfo::from(&prev).hash(), + }, + }) + } + + /// Loads an `L1BatchWithWitness`. + pub async fn load_batch_with_witness( + &self, + ctx: &ctx::Ctx, + n: L1BatchNumber, + ) -> ctx::Result { + L1BatchWithWitness::load(ctx, n, &self.pool, &self.tree_reader).await + } + /// Connects to the json RPC endpoint exposed by the state keeper. pub async fn connect(&self, ctx: &ctx::Ctx) -> ctx::Result>> { let addr = sync::wait_for(ctx, &mut self.addr.clone(), Option::is_some) @@ -266,7 +370,43 @@ impl StateKeeper { } } -async fn calculate_mock_metadata(ctx: &ctx::Ctx, pool: &ConnectionPool) -> ctx::Result<()> { +async fn mock_commitment_generator_step(ctx: &ctx::Ctx, pool: &ConnectionPool) -> ctx::Result<()> { + let mut conn = pool.connection(ctx).await.wrap("connection()")?; + let Some(first) = ctx + .wait( + conn.0 + .blocks_dal() + .get_next_l1_batch_ready_for_commitment_generation(), + ) + .await? + .map_err(|e| e.generalize())? + else { + return Ok(()); + }; + let last = ctx + .wait( + conn.0 + .blocks_dal() + .get_last_l1_batch_ready_for_commitment_generation(), + ) + .await? + .map_err(|e| e.generalize())? + .context("batch disappeared")?; + // Create artificial `L1BatchCommitmentArtifacts`. + for i in (first.0..=last.0).map(L1BatchNumber) { + let metadata = create_l1_batch_metadata(i.0); + let artifacts = l1_batch_metadata_to_commitment_artifacts(&metadata); + ctx.wait( + conn.0 + .blocks_dal() + .save_l1_batch_commitment_artifacts(i, &artifacts), + ) + .await??; + } + Ok(()) +} + +async fn mock_metadata_calculator_step(ctx: &ctx::Ctx, pool: &ConnectionPool) -> ctx::Result<()> { let mut conn = pool.connection(ctx).await.wrap("connection()")?; let Some(last) = ctx .wait(conn.0.blocks_dal().get_sealed_l1_batch_number()) @@ -306,6 +446,122 @@ async fn calculate_mock_metadata(ctx: &ctx::Ctx, pool: &ConnectionPool) -> ctx:: } impl StateKeeperRunner { + // Executes the state keeper task with real metadata calculator task + // and fake commitment generator (because real one is too slow). + pub async fn run_real(self, ctx: &ctx::Ctx) -> anyhow::Result<()> { + let res = scope::run!(ctx, |ctx, s| async { + // Fund the test account. Required for L2 transactions to succeed. + fund(&self.pool.0, &[self.account.address]).await; + + let (stop_send, stop_recv) = sync::watch::channel(false); + let (persistence, l2_block_sealer) = + StateKeeperPersistence::new(self.pool.0.clone(), Address::repeat_byte(11), 5); + + let io = ExternalIO::new( + self.pool.0.clone(), + self.actions_queue, + Box::::default(), + L2ChainId::default(), + ) + .await?; + + s.spawn_bg(async { + Ok(l2_block_sealer + .run() + .await + .context("l2_block_sealer.run()")?) + }); + + s.spawn_bg({ + let stop_recv = stop_recv.clone(); + async { + self.metadata_calculator.run(stop_recv).await?; + Ok(()) + } + }); + + // TODO: should be replaceable with `PostgresFactory`. + // Caching shouldn't be needed for tests. + let (async_cache, async_catchup_task) = AsyncRocksdbCache::new( + self.pool.0.clone(), + self.rocksdb_dir + .path() + .join("cache") + .to_string_lossy() + .into(), + RocksdbStorageOptions { + block_cache_capacity: (1 << 20), // `1MB` + max_open_files: None, + }, + ); + s.spawn_bg({ + let stop_recv = stop_recv.clone(); + async { + async_catchup_task.run(stop_recv).await?; + Ok(()) + } + }); + s.spawn_bg::<()>(async { + loop { + mock_commitment_generator_step(ctx, &self.pool).await?; + // Sleep real time. + ctx.wait(tokio::time::sleep(tokio::time::Duration::from_millis(100))) + .await?; + } + }); + + s.spawn_bg({ + let stop_recv = stop_recv.clone(); + async { + ZkSyncStateKeeper::new( + stop_recv, + Box::new(io), + Box::new(MainBatchExecutor::new(false, false)), + OutputHandler::new(Box::new(persistence.with_tx_insertion())) + .with_handler(Box::new(self.sync_state.clone())), + Arc::new(NoopSealer), + Arc::new(async_cache), + ) + .run() + .await + .context("ZkSyncStateKeeper::run()")?; + Ok(()) + } + }); + s.spawn_bg(async { + // Spawn HTTP server. + let cfg = InternalApiConfig::new( + &configs::api::Web3JsonRpcConfig::for_tests(), + &configs::contracts::ContractsConfig::for_tests(), + &configs::GenesisConfig::for_tests(), + ); + let mut server = spawn_http_server( + cfg, + self.pool.0.clone(), + Default::default(), + Arc::default(), + stop_recv, + ) + .await; + if let Ok(addr) = ctx.wait(server.wait_until_ready()).await { + self.addr.send_replace(Some(addr)); + tracing::info!("API server ready!"); + } + ctx.canceled().await; + server.shutdown().await; + Ok(()) + }); + ctx.canceled().await; + stop_send.send_replace(true); + Ok(()) + }) + .await; + match res { + Ok(()) | Err(ctx::Error::Canceled(_)) => Ok(()), + Err(ctx::Error::Internal(err)) => Err(err), + } + } + /// Executes the StateKeeper task. pub async fn run(self, ctx: &ctx::Ctx) -> anyhow::Result<()> { let res = scope::run!(ctx, |ctx, s| async { @@ -329,7 +585,8 @@ impl StateKeeperRunner { }); s.spawn_bg::<()>(async { loop { - calculate_mock_metadata(ctx, &self.pool).await?; + mock_metadata_calculator_step(ctx, &self.pool).await?; + mock_commitment_generator_step(ctx, &self.pool).await?; // Sleep real time. ctx.wait(tokio::time::sleep(tokio::time::Duration::from_millis(100))) .await?; diff --git a/core/node/consensus/src/tests.rs b/core/node/consensus/src/tests.rs index 6ed65161362..79784f0fbb5 100644 --- a/core/node/consensus/src/tests.rs +++ b/core/node/consensus/src/tests.rs @@ -1,3 +1,4 @@ +#![allow(unused)] use anyhow::Context as _; use test_casing::test_casing; use tracing::Instrument as _; @@ -9,6 +10,7 @@ use zksync_consensus_roles::{ validator, validator::testonly::{Setup, SetupSpec}, }; +use zksync_dal::CoreDal; use zksync_node_test_utils::Snapshot; use zksync_types::{L1BatchNumber, L2BlockNumber}; @@ -515,3 +517,45 @@ async fn test_centralized_fetcher(from_snapshot: bool) { .await .unwrap(); } + +/// Tests that generated L1 batch witnesses can be verified successfully. +/// TODO: add tests for verification failures. +#[tokio::test] +async fn test_batch_witness() { + zksync_concurrency::testonly::abort_on_panic(); + let ctx = &ctx::test_root(&ctx::RealClock); + let rng = &mut ctx.rng(); + + scope::run!(ctx, |ctx, s| async { + let pool = ConnectionPool::from_genesis().await; + let (mut node, runner) = testonly::StateKeeper::new(ctx, pool.clone()).await?; + s.spawn_bg(runner.run_real(ctx)); + + tracing::info!("analyzing storage"); + { + let mut conn = pool.connection(ctx).await.unwrap(); + let mut n = validator::BlockNumber(0); + while let Some(p) = conn.payload(ctx, n).await? { + tracing::info!("block[{n}] = {p:?}"); + n = n + 1; + } + } + + // Seal a bunch of batches. + node.push_random_blocks(rng, 10).await; + node.seal_batch().await; + pool.wait_for_batch(ctx, node.last_sealed_batch()).await?; + // We can verify only 2nd batch onward, because + // batch witness verifies parent of the last block of the + // previous batch (and 0th batch contains only 1 block). + for n in 2..=node.last_sealed_batch().0 { + let n = L1BatchNumber(n); + let batch_with_witness = node.load_batch_with_witness(ctx, n).await?; + let commit = node.load_batch_commit(ctx, n).await?; + batch_with_witness.verify(&commit)?; + } + Ok(()) + }) + .await + .unwrap(); +} diff --git a/core/node/eth_watch/src/tests.rs b/core/node/eth_watch/src/tests.rs index 71d33f5c973..6b15c71bd14 100644 --- a/core/node/eth_watch/src/tests.rs +++ b/core/node/eth_watch/src/tests.rs @@ -142,7 +142,7 @@ fn build_l1_tx(serial_id: u64, eth_block: u64) -> L1Tx { execute: Execute { contract_address: Address::repeat_byte(0x11), calldata: vec![1, 2, 3], - factory_deps: Some(vec![]), + factory_deps: vec![], value: U256::zero(), }, common_data: L1TxCommonData { @@ -173,7 +173,7 @@ fn build_upgrade_tx(id: ProtocolVersionId, eth_block: u64) -> ProtocolUpgradeTx execute: Execute { contract_address: Address::repeat_byte(0x11), calldata: vec![1, 2, 3], - factory_deps: None, + factory_deps: vec![], value: U256::zero(), }, common_data: ProtocolUpgradeTxCommonData { @@ -562,7 +562,6 @@ fn upgrade_into_diamond_cut(upgrade: ProtocolUpgrade) -> Token { tx: Default::default(), factory_deps: vec![], eth_block: 0, - received_timestamp_ms: 0, }) else { unreachable!() diff --git a/core/node/metadata_calculator/Cargo.toml b/core/node/metadata_calculator/Cargo.toml index 5f336bb11d4..b694c1d198c 100644 --- a/core/node/metadata_calculator/Cargo.toml +++ b/core/node/metadata_calculator/Cargo.toml @@ -10,6 +10,7 @@ keywords.workspace = true categories.workspace = true [dependencies] +zksync_crypto.workspace = true zksync_dal.workspace = true zksync_health_check.workspace = true zksync_merkle_tree.workspace = true diff --git a/core/node/metadata_calculator/src/api_server/mod.rs b/core/node/metadata_calculator/src/api_server/mod.rs index 77773ffa37c..c90b889df91 100644 --- a/core/node/metadata_calculator/src/api_server/mod.rs +++ b/core/node/metadata_calculator/src/api_server/mod.rs @@ -12,6 +12,7 @@ use axum::{ }; use serde::{Deserialize, Serialize}; use tokio::sync::watch; +use zksync_crypto::hasher::blake2::Blake2Hasher; use zksync_health_check::{CheckHealth, Health, HealthStatus}; use zksync_merkle_tree::NoVersionError; use zksync_types::{L1BatchNumber, H256, U256}; @@ -34,7 +35,7 @@ struct TreeProofsResponse { entries: Vec, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct TreeEntryWithProof { #[serde(default, skip_serializing_if = "H256::is_zero")] pub value: H256, @@ -59,6 +60,21 @@ impl TreeEntryWithProof { merkle_path, } } + + /// Verifies the entry. + pub fn verify(&self, key: U256, trusted_root_hash: H256) -> anyhow::Result<()> { + let mut merkle_path = self.merkle_path.clone(); + merkle_path.reverse(); + zksync_merkle_tree::TreeEntryWithProof { + base: zksync_merkle_tree::TreeEntry { + value: self.value, + leaf_index: self.index, + key, + }, + merkle_path, + } + .verify(&Blake2Hasher, trusted_root_hash) + } } /// Server-side tree API error. diff --git a/core/node/state_keeper/Cargo.toml b/core/node/state_keeper/Cargo.toml index afc2d6ed826..c2ac940eef3 100644 --- a/core/node/state_keeper/Cargo.toml +++ b/core/node/state_keeper/Cargo.toml @@ -24,6 +24,8 @@ zksync_node_fee_model.workspace = true zksync_utils.workspace = true zksync_contracts.workspace = true zksync_protobuf.workspace = true +zksync_test_account.workspace = true +zksync_node_genesis.workspace = true zksync_node_test_utils.workspace = true vm_utils.workspace = true @@ -40,10 +42,8 @@ hex.workspace = true [dev-dependencies] assert_matches.workspace = true test-casing.workspace = true -tempfile.workspace = true futures.workspace = true +tempfile.workspace = true -zksync_test_account.workspace = true -zksync_node_genesis.workspace = true zksync_eth_client.workspace = true zksync_system_constants.workspace = true diff --git a/core/node/state_keeper/src/batch_executor/mod.rs b/core/node/state_keeper/src/batch_executor/mod.rs index eb6292ee1da..8703831f395 100644 --- a/core/node/state_keeper/src/batch_executor/mod.rs +++ b/core/node/state_keeper/src/batch_executor/mod.rs @@ -18,11 +18,10 @@ use crate::{ types::ExecutionMetricsForCriteria, }; +pub mod main_executor; #[cfg(test)] mod tests; -pub mod main_executor; - /// Representation of a transaction executed in the virtual machine. #[derive(Debug, Clone)] pub enum TxExecutionResult { diff --git a/core/node/state_keeper/src/batch_executor/tests/tester.rs b/core/node/state_keeper/src/batch_executor/tests/tester.rs index d091520e652..39f860b752e 100644 --- a/core/node/state_keeper/src/batch_executor/tests/tester.rs +++ b/core/node/state_keeper/src/batch_executor/tests/tester.rs @@ -17,12 +17,11 @@ use zksync_node_test_utils::prepare_recovery_snapshot; use zksync_state::{ReadStorageFactory, RocksdbStorageOptions}; use zksync_test_account::{Account, DeployContractsTx, TxType}; use zksync_types::{ - block::L2BlockHasher, ethabi::Token, fee::Fee, protocol_version::ProtocolSemanticVersion, + block::L2BlockHasher, ethabi::Token, protocol_version::ProtocolSemanticVersion, snapshots::SnapshotRecoveryStatus, storage_writes_deduplicator::StorageWritesDeduplicator, system_contracts::get_system_smart_contracts, utils::storage_key_for_standard_token_balance, AccountTreeId, Address, Execute, L1BatchNumber, L2BlockNumber, PriorityOpId, ProtocolVersionId, - StorageKey, StorageLog, Transaction, H256, L2_BASE_TOKEN_ADDRESS, - SYSTEM_CONTEXT_MINIMAL_BASE_FEE, U256, + StorageKey, StorageLog, Transaction, H256, L2_BASE_TOKEN_ADDRESS, U256, }; use zksync_utils::u256_to_h256; @@ -32,13 +31,12 @@ use super::{ }; use crate::{ batch_executor::{BatchExecutorHandle, TxExecutionResult}, + testonly, testonly::BASE_SYSTEM_CONTRACTS, tests::{default_l1_batch_env, default_system_env}, AsyncRocksdbCache, BatchExecutor, MainBatchExecutor, }; -const DEFAULT_GAS_PER_PUBDATA: u32 = 10000; - /// Representation of configuration parameters used by the state keeper. /// Has sensible defaults for most tests, each of which can be overridden. #[derive(Debug)] @@ -346,15 +344,7 @@ impl AccountLoadNextExecutable for Account { ) } fn l1_execute(&mut self, serial_id: PriorityOpId) -> Transaction { - self.get_l1_tx( - Execute { - contract_address: Address::random(), - value: Default::default(), - calldata: vec![], - factory_deps: None, - }, - serial_id.0, - ) + testonly::l1_transaction(self, serial_id) } /// Returns a valid `execute` transaction. @@ -373,10 +363,12 @@ impl AccountLoadNextExecutable for Account { ) -> Transaction { // For each iteration of the expensive contract, there are two slots that are updated: // the length of the vector and the new slot with the element itself. - let minimal_fee = - 2 * DEFAULT_GAS_PER_PUBDATA * writes * INITIAL_STORAGE_WRITE_PUBDATA_BYTES as u32; + let minimal_fee = 2 + * testonly::DEFAULT_GAS_PER_PUBDATA + * writes + * INITIAL_STORAGE_WRITE_PUBDATA_BYTES as u32; - let fee = fee(minimal_fee + gas_limit); + let fee = testonly::fee(minimal_fee + gas_limit); self.get_l2_tx_for_execute( Execute { @@ -391,7 +383,7 @@ impl AccountLoadNextExecutable for Account { } .to_bytes(), value: Default::default(), - factory_deps: None, + factory_deps: vec![], }, Some(fee), ) @@ -400,16 +392,7 @@ impl AccountLoadNextExecutable for Account { /// Returns a valid `execute` transaction. /// Automatically increments nonce of the account. fn execute_with_gas_limit(&mut self, gas_limit: u32) -> Transaction { - let fee = fee(gas_limit); - self.get_l2_tx_for_execute( - Execute { - contract_address: Address::random(), - calldata: vec![], - value: Default::default(), - factory_deps: None, - }, - Some(fee), - ) + testonly::l2_transaction(self, gas_limit) } /// Returns a transaction to the loadnext contract with custom gas limit and expected burned gas amount. @@ -420,7 +403,7 @@ impl AccountLoadNextExecutable for Account { gas_to_burn: u32, gas_limit: u32, ) -> Transaction { - let fee = fee(gas_limit); + let fee = testonly::fee(gas_limit); let calldata = mock_loadnext_gas_burn_calldata(gas_to_burn); self.get_l2_tx_for_execute( @@ -428,22 +411,13 @@ impl AccountLoadNextExecutable for Account { contract_address: address, calldata, value: Default::default(), - factory_deps: None, + factory_deps: vec![], }, Some(fee), ) } } -fn fee(gas_limit: u32) -> Fee { - Fee { - gas_limit: U256::from(gas_limit), - max_fee_per_gas: SYSTEM_CONTEXT_MINIMAL_BASE_FEE.into(), - max_priority_fee_per_gas: U256::zero(), - gas_per_pubdata_limit: U256::from(DEFAULT_GAS_PER_PUBDATA), - } -} - pub fn mock_loadnext_gas_burn_calldata(gas: u32) -> Vec { let loadnext_contract = get_loadnext_contract(); let contract_function = loadnext_contract.contract.function("burnGas").unwrap(); diff --git a/core/node/state_keeper/src/testonly/mod.rs b/core/node/state_keeper/src/testonly/mod.rs index b50cd483fc5..3ba61949516 100644 --- a/core/node/state_keeper/src/testonly/mod.rs +++ b/core/node/state_keeper/src/testonly/mod.rs @@ -14,7 +14,15 @@ use multivm::{ use once_cell::sync::Lazy; use tokio::sync::{mpsc, watch}; use zksync_contracts::BaseSystemContracts; +use zksync_dal::{ConnectionPool, Core, CoreDal as _}; use zksync_state::ReadStorageFactory; +use zksync_test_account::Account; +use zksync_types::{ + fee::Fee, utils::storage_key_for_standard_token_balance, AccountTreeId, Address, Execute, + L1BatchNumber, L2BlockNumber, PriorityOpId, StorageLog, Transaction, H256, + L2_BASE_TOKEN_ADDRESS, SYSTEM_CONTEXT_MINIMAL_BASE_FEE, U256, +}; +use zksync_utils::u256_to_h256; use crate::{ batch_executor::{BatchExecutor, BatchExecutorHandle, Command, TxExecutionResult}, @@ -104,3 +112,76 @@ impl BatchExecutor for MockBatchExecutor { Some(BatchExecutorHandle::from_raw(handle, send)) } } + +/// Adds funds for specified account list. +/// Expects genesis to be performed (i.e. `setup_storage` called beforehand). +pub async fn fund(pool: &ConnectionPool, addresses: &[Address]) { + let mut storage = pool.connection().await.unwrap(); + + let eth_amount = U256::from(10u32).pow(U256::from(32)); //10^32 wei + + for address in addresses { + let key = storage_key_for_standard_token_balance( + AccountTreeId::new(L2_BASE_TOKEN_ADDRESS), + address, + ); + let value = u256_to_h256(eth_amount); + let storage_log = StorageLog::new_write_log(key, value); + + storage + .storage_logs_dal() + .append_storage_logs(L2BlockNumber(0), &[(H256::zero(), vec![storage_log])]) + .await + .unwrap(); + if storage + .storage_logs_dedup_dal() + .filter_written_slots(&[storage_log.key.hashed_key()]) + .await + .unwrap() + .is_empty() + { + storage + .storage_logs_dedup_dal() + .insert_initial_writes(L1BatchNumber(0), &[storage_log.key]) + .await + .unwrap(); + } + } +} + +pub(crate) const DEFAULT_GAS_PER_PUBDATA: u32 = 10000; + +pub(crate) fn fee(gas_limit: u32) -> Fee { + Fee { + gas_limit: U256::from(gas_limit), + max_fee_per_gas: SYSTEM_CONTEXT_MINIMAL_BASE_FEE.into(), + max_priority_fee_per_gas: U256::zero(), + gas_per_pubdata_limit: U256::from(DEFAULT_GAS_PER_PUBDATA), + } +} + +/// Returns a valid L2 transaction. +/// Automatically increments nonce of the account. +pub fn l2_transaction(account: &mut Account, gas_limit: u32) -> Transaction { + account.get_l2_tx_for_execute( + Execute { + contract_address: Address::random(), + calldata: vec![], + value: Default::default(), + factory_deps: vec![], + }, + Some(fee(gas_limit)), + ) +} + +pub fn l1_transaction(account: &mut Account, serial_id: PriorityOpId) -> Transaction { + account.get_l1_tx( + Execute { + contract_address: Address::random(), + value: Default::default(), + calldata: vec![], + factory_deps: vec![], + }, + serial_id.0, + ) +} diff --git a/core/node/state_keeper/src/updates/l2_block_updates.rs b/core/node/state_keeper/src/updates/l2_block_updates.rs index efc09472fb0..34cfad44f93 100644 --- a/core/node/state_keeper/src/updates/l2_block_updates.rs +++ b/core/node/state_keeper/src/updates/l2_block_updates.rs @@ -120,7 +120,7 @@ impl L2BlockUpdates { }; // Get transaction factory deps - let factory_deps = tx.execute.factory_deps.as_deref().unwrap_or_default(); + let factory_deps = &tx.execute.factory_deps; let tx_factory_deps: HashMap<_, _> = factory_deps .iter() .map(|bytecode| (hash_bytecode(bytecode), bytecode)) diff --git a/core/node/test_utils/src/lib.rs b/core/node/test_utils/src/lib.rs index 9abd968acb1..566eab9c3d2 100644 --- a/core/node/test_utils/src/lib.rs +++ b/core/node/test_utils/src/lib.rs @@ -123,7 +123,7 @@ pub fn create_l2_transaction(fee_per_gas: u64, gas_per_pubdata: u64) -> L2Tx { U256::zero(), L2ChainId::from(271), &K256PrivateKey::random(), - None, + vec![], PaymasterParams::default(), ) .unwrap(); diff --git a/core/node/vm_runner/src/tests/mod.rs b/core/node/vm_runner/src/tests/mod.rs index d0374e0d5fa..0d106235f71 100644 --- a/core/node/vm_runner/src/tests/mod.rs +++ b/core/node/vm_runner/src/tests/mod.rs @@ -189,7 +189,7 @@ pub fn create_l2_transaction( contract_address: Address::random(), calldata: vec![], value: Default::default(), - factory_deps: None, + factory_deps: vec![], }, Some(fee), ); diff --git a/core/tests/loadnext/src/sdk/operations/deploy_contract.rs b/core/tests/loadnext/src/sdk/operations/deploy_contract.rs index adf1fe09ee7..af621249ed8 100644 --- a/core/tests/loadnext/src/sdk/operations/deploy_contract.rs +++ b/core/tests/loadnext/src/sdk/operations/deploy_contract.rs @@ -73,7 +73,7 @@ where execute_calldata, fee, nonce, - Some(vec![bytecode.clone()]), + vec![bytecode.clone()], paymaster_params, ) .await @@ -150,7 +150,7 @@ where Default::default(), self.wallet.address(), self.value.unwrap_or_default(), - Some(factory_deps), + factory_deps, paymaster_params, ); self.wallet diff --git a/core/tests/loadnext/src/sdk/operations/execute_contract.rs b/core/tests/loadnext/src/sdk/operations/execute_contract.rs index 3572d24a8b5..18b93008a73 100644 --- a/core/tests/loadnext/src/sdk/operations/execute_contract.rs +++ b/core/tests/loadnext/src/sdk/operations/execute_contract.rs @@ -67,7 +67,7 @@ where calldata, fee, nonce, - self.factory_deps, + self.factory_deps.unwrap_or_default(), paymaster_params, ) .await @@ -150,7 +150,7 @@ where Default::default(), self.wallet.address(), self.value.unwrap_or_default(), - self.factory_deps.clone(), + self.factory_deps.clone().unwrap_or_default(), paymaster_params, ); self.wallet diff --git a/core/tests/loadnext/src/sdk/operations/transfer.rs b/core/tests/loadnext/src/sdk/operations/transfer.rs index 8fe35fae92e..34bab615c7c 100644 --- a/core/tests/loadnext/src/sdk/operations/transfer.rs +++ b/core/tests/loadnext/src/sdk/operations/transfer.rs @@ -155,7 +155,7 @@ where Execute { contract_address: to, calldata: Default::default(), - factory_deps: None, + factory_deps: vec![], value: amount, } } else { @@ -163,7 +163,7 @@ where Execute { contract_address: token, calldata: create_transfer_calldata(to, amount), - factory_deps: None, + factory_deps: vec![], value: Default::default(), } }; diff --git a/core/tests/loadnext/src/sdk/signer.rs b/core/tests/loadnext/src/sdk/signer.rs index a992772909b..0f4b1cf2971 100644 --- a/core/tests/loadnext/src/sdk/signer.rs +++ b/core/tests/loadnext/src/sdk/signer.rs @@ -57,7 +57,7 @@ impl Signer { fee, self.eth_signer.get_address().await?, amount, - None, + vec![], Default::default(), ); @@ -79,7 +79,7 @@ impl Signer { fee, self.eth_signer.get_address().await?, U256::zero(), - None, + vec![], paymaster_params, ); @@ -98,7 +98,7 @@ impl Signer { calldata: Vec, fee: Fee, nonce: Nonce, - factory_deps: Option>>, + factory_deps: Vec>, paymaster_params: PaymasterParams, ) -> Result { self.sign_execute_contract_for_deploy( @@ -118,7 +118,7 @@ impl Signer { calldata: Vec, fee: Fee, nonce: Nonce, - factory_deps: Option>>, + factory_deps: Vec>, paymaster_params: PaymasterParams, ) -> Result { let mut execute_contract = L2Tx::new( diff --git a/core/tests/test_account/src/lib.rs b/core/tests/test_account/src/lib.rs index 9574c47b9ab..619caeb1ebd 100644 --- a/core/tests/test_account/src/lib.rs +++ b/core/tests/test_account/src/lib.rs @@ -8,15 +8,10 @@ use zksync_system_constants::{ REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_BYTE, }; use zksync_types::{ - api, - fee::Fee, - l1::{OpProcessingType, PriorityQueueType}, - l2::L2Tx, - utils::deployed_address_create, - Address, Execute, ExecuteTransactionCommon, K256PrivateKey, L1TxCommonData, L2ChainId, Nonce, - PriorityOpId, Transaction, H256, U256, + abi, fee::Fee, l2::L2Tx, utils::deployed_address_create, Address, Execute, K256PrivateKey, + L2ChainId, Nonce, Transaction, H256, PRIORITY_OPERATION_L2_TX_TYPE, U256, }; -use zksync_utils::bytecode::hash_bytecode; +use zksync_utils::{address_to_u256, bytecode::hash_bytecode, h256_to_u256}; pub const L1_TEST_GAS_PER_PUBDATA_BYTE: u32 = 800; const BASE_FEE: u64 = 2_000_000_000; @@ -73,28 +68,22 @@ impl Account { value, factory_deps, } = execute; - let mut tx = L2Tx::new_signed( + L2Tx::new_signed( contract_address, calldata, nonce, - fee.unwrap_or_else(|| self.default_fee()), + fee.unwrap_or_else(Self::default_fee), value, L2ChainId::default(), &self.private_key, factory_deps, Default::default(), ) - .expect("should create a signed execute transaction"); - - // Set the real transaction hash, which is necessary for transaction execution in VM to function properly. - let mut tx_request = api::TransactionRequest::from(tx.clone()); - tx_request.chain_id = Some(L2ChainId::default().as_u64()); - let tx_hash = tx_request.get_tx_hash().unwrap(); - tx.set_input(H256::random().0.to_vec(), tx_hash); - tx.into() + .expect("should create a signed execute transaction") + .into() } - fn default_fee(&self) -> Fee { + pub fn default_fee() -> Fee { Fee { gas_limit: U256::from(2000000000u32), max_fee_per_gas: U256::from(BASE_FEE), @@ -138,7 +127,7 @@ impl Account { let execute = Execute { contract_address: CONTRACT_DEPLOYER_ADDRESS, calldata, - factory_deps: Some(factory_deps), + factory_deps, value: U256::zero(), }; @@ -160,27 +149,42 @@ impl Account { pub fn get_l1_tx(&self, execute: Execute, serial_id: u64) -> Transaction { let max_fee_per_gas = U256::from(0u32); let gas_limit = U256::from(20_000_000); - - Transaction { - common_data: ExecuteTransactionCommon::L1(L1TxCommonData { - sender: self.address, + let factory_deps = execute.factory_deps; + abi::Transaction::L1 { + tx: abi::L2CanonicalTransaction { + tx_type: PRIORITY_OPERATION_L2_TX_TYPE.into(), + from: address_to_u256(&self.address), + to: address_to_u256(&execute.contract_address), gas_limit, - gas_per_pubdata_limit: REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_BYTE.into(), - to_mint: gas_limit * max_fee_per_gas + execute.value, - serial_id: PriorityOpId(serial_id), + gas_per_pubdata_byte_limit: REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_BYTE.into(), max_fee_per_gas, - canonical_tx_hash: H256::from_low_u64_be(serial_id), - layer_2_tip_fee: Default::default(), - op_processing_type: OpProcessingType::Common, - priority_queue_type: PriorityQueueType::Deque, - eth_block: 0, - refund_recipient: self.address, - full_fee: Default::default(), - }), - execute, - received_timestamp_ms: 0, - raw_bytes: None, + max_priority_fee_per_gas: 0.into(), + paymaster: 0.into(), + nonce: serial_id.into(), + value: execute.value, + reserved: [ + // `to_mint` + gas_limit * max_fee_per_gas + execute.value, + // `refund_recipient` + address_to_u256(&self.address), + 0.into(), + 0.into(), + ], + data: execute.calldata, + signature: vec![], + factory_deps: factory_deps + .iter() + .map(|b| h256_to_u256(hash_bytecode(b))) + .collect(), + paymaster_input: vec![], + reserved_dynamic: vec![], + } + .into(), + factory_deps, + eth_block: 0, } + .try_into() + .unwrap() } pub fn get_test_contract_transaction( @@ -211,7 +215,7 @@ impl Account { contract_address: address, calldata, value: value.unwrap_or_default(), - factory_deps: None, + factory_deps: vec![], }; match tx_type { TxType::L2 => self.get_l2_tx_for_execute(execute, None), @@ -230,7 +234,7 @@ impl Account { contract_address: address, calldata, value: U256::zero(), - factory_deps: None, + factory_deps: vec![], }; match tx_type { diff --git a/core/tests/vm-benchmark/harness/src/lib.rs b/core/tests/vm-benchmark/harness/src/lib.rs index 83750d2e2a2..137a3b654cb 100644 --- a/core/tests/vm-benchmark/harness/src/lib.rs +++ b/core/tests/vm-benchmark/harness/src/lib.rs @@ -147,7 +147,7 @@ pub fn get_deploy_tx(code: &[u8]) -> Transaction { U256::zero(), L2ChainId::from(270), &PRIVATE_KEY, - Some(vec![code.to_vec()]), // maybe not needed? + vec![code.to_vec()], // maybe not needed? Default::default(), ) .expect("should create a signed execute transaction"); diff --git a/prover/Cargo.lock b/prover/Cargo.lock index 7f30f6be590..44c2a8b8395 100644 --- a/prover/Cargo.lock +++ b/prover/Cargo.lock @@ -8974,6 +8974,7 @@ dependencies = [ "tracing", "vise", "zksync_config", + "zksync_crypto", "zksync_dal", "zksync_health_check", "zksync_merkle_tree", @@ -9045,6 +9046,7 @@ dependencies = [ "anyhow", "async-trait", "secrecy", + "tempfile", "tracing", "zksync_concurrency", "zksync_config", @@ -9056,10 +9058,16 @@ dependencies = [ "zksync_consensus_storage", "zksync_consensus_utils", "zksync_dal", + "zksync_l1_contract_interface", + "zksync_merkle_tree", + "zksync_metadata_calculator", "zksync_node_sync", "zksync_protobuf", + "zksync_state", "zksync_state_keeper", + "zksync_system_constants", "zksync_types", + "zksync_utils", "zksync_web3_decl", ] @@ -9447,11 +9455,13 @@ dependencies = [ "zksync_dal", "zksync_mempool", "zksync_node_fee_model", + "zksync_node_genesis", "zksync_node_test_utils", "zksync_protobuf", "zksync_shared_metrics", "zksync_state", "zksync_storage", + "zksync_test_account", "zksync_types", "zksync_utils", ] @@ -9520,6 +9530,19 @@ dependencies = [ "zksync_utils", ] +[[package]] +name = "zksync_test_account" +version = "0.1.0" +dependencies = [ + "ethabi", + "hex", + "zksync_contracts", + "zksync_eth_signer", + "zksync_system_constants", + "zksync_types", + "zksync_utils", +] + [[package]] name = "zksync_types" version = "0.1.0"