From dbcf3c6d02a6bfb9197bf4278f296632b0fd7d66 Mon Sep 17 00:00:00 2001 From: pompon0 Date: Fri, 14 Jun 2024 09:36:11 +0200 Subject: [PATCH] feat: verification of L1Batch witness (BFT-471) - attempt 2 (#2232) Rollforward matter-labs/zksync-era#2019 which was reverted by matter-labs/zksync-era#2230 Made Execute json encoding super backward compatible. The only change from the previous attempt is in core/lib/types/src/execute.rs --- 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_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 | 67 ++-- .../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 ++ 83 files changed, 1230 insertions(+), 445 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_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..03762040a6b 100644 --- a/core/lib/types/src/tx/execute.rs +++ b/core/lib/types/src/tx/execute.rs @@ -4,30 +4,61 @@ use zksync_utils::ZeroPrefixHexSerde; use crate::{ethabi, Address, EIP712TypedStructure, StructBuilder, H256, U256}; -/// `Execute` transaction executes a previously deployed smart contract in the L2 rollup. -#[derive(Clone, Default, Serialize, Deserialize, PartialEq)] +/// This struct is the `serde` schema for the `Execute` struct. +/// It allows us to modify `Execute` struct without worrying +/// about encoding compatibility. +/// +/// For example, changing type of `factory_deps` from `Option>` +/// to `Vec>` (even with `#[serde(default)]` annotation) +/// would be incompatible for `serde` json encoding, +/// because `null` is a valid value for the former but not for the latter. +#[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] +struct ExecuteSerde { + contract_address: Address, + #[serde(with = "ZeroPrefixHexSerde")] + calldata: Vec, + value: U256, + factory_deps: Option>>, +} + +/// `Execute` transaction executes a previously deployed smart contract in the L2 rollup. +#[derive(Clone, Default, PartialEq)] pub struct Execute { pub contract_address: Address, - - #[serde(with = "ZeroPrefixHexSerde")] pub calldata: Vec, - 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>>, + pub factory_deps: Vec>, +} + +impl serde::Serialize for Execute { + fn serialize(&self, s: S) -> Result { + ExecuteSerde { + contract_address: self.contract_address, + calldata: self.calldata.clone(), + value: self.value, + factory_deps: Some(self.factory_deps.clone()), + } + .serialize(s) + } +} + +impl<'de> serde::Deserialize<'de> for Execute { + fn deserialize>(d: D) -> Result { + let x = ExecuteSerde::deserialize(d)?; + Ok(Self { + contract_address: x.contract_address, + calldata: x.calldata, + value: x.value, + factory_deps: x.factory_deps.unwrap_or_default(), + }) + } } 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 +114,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"