diff --git a/Cargo.lock b/Cargo.lock index 55bbb4b5582..bd9f2d5ef28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3303,6 +3303,12 @@ dependencies = [ "url", ] +[[package]] +name = "human-repr" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f58b778a5761513caf593693f8951c97a5b610841e754788400f32102eefdff1" + [[package]] name = "hyper" version = "0.14.30" @@ -9640,9 +9646,9 @@ dependencies = [ [[package]] name = "zksync_concurrency" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4724d51934e475c846ba9e6ed169e25587385188b928a9ecfbbf616092a1c17" +checksum = "035269d811b3770debca372141ab64cad067dce8e58cb39a48cb7617d30c626b" dependencies = [ "anyhow", "once_cell", @@ -9681,9 +9687,9 @@ dependencies = [ [[package]] name = "zksync_consensus_bft" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e7199c07aa14d9c3319839b98ad0496aac6e72327e70ded77ddb66329766db" +checksum = "a8001633dee671134cf572175a6c4f817904ce5f8d92e9b51f49891c5184a831" dependencies = [ "anyhow", "async-trait", @@ -9703,9 +9709,9 @@ dependencies = [ [[package]] name = "zksync_consensus_crypto" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7760e7a140f16f0435fbf2ad9a4b09feaad74568d05b553751d222f4803a42e" +checksum = "49e38d1b5ed28c66e785caff53ea4863375555d818aafa03290397192dd3e665" dependencies = [ "anyhow", "blst", @@ -9724,9 +9730,9 @@ dependencies = [ [[package]] name = "zksync_consensus_executor" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db07f7329b29737d8fd6860b350c809ae1b56ad53e26a7d0eddf3664ccb9dacb" +checksum = "061546668dd779ecb08302d2c84a6419e0093ad42aaa279bf20a8fa2ffda1be4" dependencies = [ "anyhow", "async-trait", @@ -9746,9 +9752,9 @@ dependencies = [ [[package]] name = "zksync_consensus_network" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89a2d60db1ccd41438d29724a8d0d57fcf9506eb4443ea4b9205fd78c9c8e59" +checksum = "4e9789b5be26d20511bd7930bd9916d91122ff6cb09a28898563152a52f9f5eb" dependencies = [ "anyhow", "async-trait", @@ -9756,6 +9762,7 @@ dependencies = [ "build_html", "bytesize", "http-body-util", + "human-repr", "hyper 1.4.1", "hyper-util", "im", @@ -9782,9 +9789,9 @@ dependencies = [ [[package]] name = "zksync_consensus_roles" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96f903187836210602beba27655e111e22efb229ef90bd2a95a3d6799b31685c" +checksum = "e49fbd4e69b276058f3dfc06cf6ada0e8caa6ed826e81289e4d596da95a0f17a" dependencies = [ "anyhow", "bit-vec", @@ -9804,9 +9811,9 @@ dependencies = [ [[package]] name = "zksync_consensus_storage" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff43cfd03ea205c763e74362dc6ec5a4d74b6b1baef0fb134dde92a8880397f7" +checksum = "b2b2aab4ed18b13cd584f4edcc2546c8da82f89ac62e525063e12935ff28c9be" dependencies = [ "anyhow", "async-trait", @@ -9824,9 +9831,9 @@ dependencies = [ [[package]] name = "zksync_consensus_utils" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1020308512c01ab80327fb874b5b61c6fd513a6b26c8a5fce3e077600da04e4b" +checksum = "10bac8f471b182d4fa3d40cf158aac3624fe636a1ff0b4cf3fe26a0e20c68a42" dependencies = [ "anyhow", "rand 0.8.5", @@ -10054,10 +10061,13 @@ dependencies = [ "tracing", "vise", "zksync_concurrency", + "zksync_consensus_crypto", "zksync_consensus_roles", "zksync_consensus_storage", + "zksync_consensus_utils", "zksync_contracts", "zksync_db_connection", + "zksync_l1_contract_interface", "zksync_protobuf", "zksync_protobuf_build", "zksync_system_constants", @@ -10352,8 +10362,10 @@ dependencies = [ name = "zksync_l1_contract_interface" version = "0.1.0" dependencies = [ + "anyhow", "hex", "once_cell", + "rand 0.8.5", "serde", "serde_json", "serde_with", @@ -10856,9 +10868,9 @@ dependencies = [ [[package]] name = "zksync_protobuf" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2d9ce9b9697daae6023c8da5cfe8764690a9d9c91ff32b8e1e54a7c8301fb3" +checksum = "abd55c64f54cb10967a435422f66ff5880ae14a232b245517c7ce38da32e0cab" dependencies = [ "anyhow", "bit-vec", @@ -10877,9 +10889,9 @@ dependencies = [ [[package]] name = "zksync_protobuf_build" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "903c23a12e160a703f9b68d0dd961daa24156af912ca1bc9efb74969f3acc645" +checksum = "4121952bcaf711005dd554612fc6e2de9b30cb58088508df87f1d38046ce8ac8" dependencies = [ "anyhow", "heck 0.5.0", diff --git a/Cargo.toml b/Cargo.toml index 5d516e97aba..d597f4af754 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -232,16 +232,16 @@ zk_evm_1_5_0 = { package = "zk_evm", version = "=0.150.5" } zksync_vm2 = { git = "https://github.com/matter-labs/vm2.git", rev = "a233d44bbe61dc6a758a754c3b78fe4f83e56699" } # Consensus dependencies. -zksync_concurrency = "=0.3.0" -zksync_consensus_bft = "=0.3.0" -zksync_consensus_crypto = "=0.3.0" -zksync_consensus_executor = "=0.3.0" -zksync_consensus_network = "=0.3.0" -zksync_consensus_roles = "=0.3.0" -zksync_consensus_storage = "=0.3.0" -zksync_consensus_utils = "=0.3.0" -zksync_protobuf = "=0.3.0" -zksync_protobuf_build = "=0.3.0" +zksync_concurrency = "=0.5.0" +zksync_consensus_bft = "=0.5.0" +zksync_consensus_crypto = "=0.5.0" +zksync_consensus_executor = "=0.5.0" +zksync_consensus_network = "=0.5.0" +zksync_consensus_roles = "=0.5.0" +zksync_consensus_storage = "=0.5.0" +zksync_consensus_utils = "=0.5.0" +zksync_protobuf = "=0.5.0" +zksync_protobuf_build = "=0.5.0" # "Local" dependencies zksync_multivm = { version = "0.1.0", path = "core/lib/multivm" } diff --git a/core/lib/basic_types/src/lib.rs b/core/lib/basic_types/src/lib.rs index 8b6a7f949dd..197bd8eb7aa 100644 --- a/core/lib/basic_types/src/lib.rs +++ b/core/lib/basic_types/src/lib.rs @@ -13,6 +13,7 @@ use std::{ str::FromStr, }; +use anyhow::Context as _; pub use ethabi::{ self, ethereum_types::{ @@ -35,6 +36,21 @@ pub mod url; pub mod vm; pub mod web3; +/// Parses H256 from a slice of bytes. +pub fn parse_h256(bytes: &[u8]) -> anyhow::Result { + Ok(<[u8; 32]>::try_from(bytes).context("invalid size")?.into()) +} + +/// Parses H256 from an optional slice of bytes. +pub fn parse_h256_opt(bytes: Option<&[u8]>) -> anyhow::Result { + parse_h256(bytes.context("missing data")?) +} + +/// Parses H160 from a slice of bytes. +pub fn parse_h160(bytes: &[u8]) -> anyhow::Result { + Ok(<[u8; 20]>::try_from(bytes).context("invalid size")?.into()) +} + /// Account place in the global state tree is uniquely identified by its address. /// Binary this type is represented by 160 bit big-endian representation of account address. #[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Hash, Ord, PartialOrd)] diff --git a/core/lib/dal/.sqlx/query-d3d472436f1f3a6cc61bc9d47de5731b755cf2e09d877dd4eb70d58a1d11a977.json b/core/lib/dal/.sqlx/query-fec7b791e371a4c58350b6537065223f4599d4128db588d8645f3d106de5f50b.json similarity index 58% rename from core/lib/dal/.sqlx/query-d3d472436f1f3a6cc61bc9d47de5731b755cf2e09d877dd4eb70d58a1d11a977.json rename to core/lib/dal/.sqlx/query-fec7b791e371a4c58350b6537065223f4599d4128db588d8645f3d106de5f50b.json index 61497cdb169..c34d38ac2d0 100644 --- a/core/lib/dal/.sqlx/query-d3d472436f1f3a6cc61bc9d47de5731b755cf2e09d877dd4eb70d58a1d11a977.json +++ b/core/lib/dal/.sqlx/query-fec7b791e371a4c58350b6537065223f4599d4128db588d8645f3d106de5f50b.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n certificate\n FROM\n miniblocks_consensus\n WHERE\n number >= $1\n ORDER BY\n number DESC\n LIMIT\n 1\n ", + "query": "\n SELECT\n certificate\n FROM\n miniblocks_consensus\n ORDER BY\n number DESC\n LIMIT\n 1\n ", "describe": { "columns": [ { @@ -10,13 +10,11 @@ } ], "parameters": { - "Left": [ - "Int8" - ] + "Left": [] }, "nullable": [ false ] }, - "hash": "d3d472436f1f3a6cc61bc9d47de5731b755cf2e09d877dd4eb70d58a1d11a977" + "hash": "fec7b791e371a4c58350b6537065223f4599d4128db588d8645f3d106de5f50b" } diff --git a/core/lib/dal/Cargo.toml b/core/lib/dal/Cargo.toml index ccca49525e4..db03b8de982 100644 --- a/core/lib/dal/Cargo.toml +++ b/core/lib/dal/Cargo.toml @@ -22,8 +22,11 @@ zksync_types.workspace = true zksync_concurrency.workspace = true zksync_consensus_roles.workspace = true zksync_consensus_storage.workspace = true +zksync_consensus_crypto.workspace = true +zksync_consensus_utils.workspace = true zksync_protobuf.workspace = true zksync_db_connection.workspace = true +zksync_l1_contract_interface.workspace = true itertools.workspace = true thiserror.workspace = true diff --git a/core/lib/dal/src/consensus/conv.rs b/core/lib/dal/src/consensus/conv.rs new file mode 100644 index 00000000000..269c47fa2dd --- /dev/null +++ b/core/lib/dal/src/consensus/conv.rs @@ -0,0 +1,519 @@ +//! Protobuf conversion functions. +use anyhow::{anyhow, Context as _}; +use zksync_concurrency::net; +use zksync_consensus_roles::{attester, node}; +use zksync_protobuf::{read_required, required, ProtoFmt, ProtoRepr}; +use zksync_types::{ + abi, ethabi, + fee::Fee, + l1::{OpProcessingType, PriorityQueueType}, + l2::TransactionType, + parse_h160, parse_h256, + protocol_upgrade::ProtocolUpgradeTxCommonData, + transaction_request::PaymasterParams, + Execute, ExecuteTransactionCommon, InputData, L1BatchNumber, L1TxCommonData, L2TxCommonData, + Nonce, PriorityOpId, ProtocolVersionId, Transaction, H256, +}; +use zksync_utils::{h256_to_u256, u256_to_h256}; + +use super::*; + +impl ProtoFmt for BlockMetadata { + type Proto = proto::BlockMetadata; + fn read(r: &Self::Proto) -> anyhow::Result { + Ok(Self { + payload_hash: read_required(&r.payload_hash).context("payload_hash")?, + }) + } + fn build(&self) -> Self::Proto { + Self::Proto { + payload_hash: Some(self.payload_hash.build()), + } + } +} + +impl ProtoRepr for proto::NodeAddr { + type Type = (node::PublicKey, net::Host); + fn read(&self) -> anyhow::Result { + Ok(( + read_required(&self.key).context("key")?, + net::Host(required(&self.addr).context("addr")?.clone()), + )) + } + fn build(this: &Self::Type) -> Self { + Self { + key: Some(this.0.build()), + addr: Some(this.1 .0.clone()), + } + } +} + +impl ProtoFmt for GlobalConfig { + type Proto = proto::GlobalConfig; + + fn read(r: &Self::Proto) -> anyhow::Result { + Ok(Self { + genesis: read_required(&r.genesis).context("genesis")?, + registry_address: r + .registry_address + .as_ref() + .map(|a| parse_h160(a)) + .transpose() + .context("registry_address")?, + seed_peers: r + .seed_peers + .iter() + .enumerate() + .map(|(i, e)| e.read().context(i)) + .collect::>() + .context("seed_peers")?, + }) + } + + fn build(&self) -> Self::Proto { + Self::Proto { + genesis: Some(self.genesis.build()), + registry_address: self.registry_address.map(|a| a.as_bytes().to_vec()), + seed_peers: self + .seed_peers + .iter() + .map(|(k, v)| ProtoRepr::build(&(k.clone(), v.clone()))) + .collect(), + } + } +} +impl ProtoFmt for AttestationStatus { + type Proto = proto::AttestationStatus; + + fn read(r: &Self::Proto) -> anyhow::Result { + Ok(Self { + genesis: read_required(&r.genesis).context("genesis")?, + next_batch_to_attest: attester::BatchNumber( + *required(&r.next_batch_to_attest).context("next_batch_to_attest")?, + ), + }) + } + + fn build(&self) -> Self::Proto { + Self::Proto { + genesis: Some(self.genesis.build()), + next_batch_to_attest: Some(self.next_batch_to_attest.0), + } + } +} + +impl ProtoFmt for Payload { + type Proto = proto::Payload; + + fn read(r: &Self::Proto) -> anyhow::Result { + let protocol_version = required(&r.protocol_version) + .and_then(|x| Ok(ProtocolVersionId::try_from(u16::try_from(*x)?)?)) + .context("protocol_version")?; + let mut transactions = vec![]; + + match protocol_version { + v if v >= ProtocolVersionId::Version25 => { + anyhow::ensure!( + r.transactions.is_empty(), + "transactions should be empty in protocol_version {v}" + ); + for (i, tx) in r.transactions_v25.iter().enumerate() { + transactions.push( + tx.read() + .with_context(|| format!("transactions_v25[{i}]"))?, + ); + } + } + v => { + anyhow::ensure!( + r.transactions_v25.is_empty(), + "transactions_v25 should be empty in protocol_version {v}" + ); + for (i, tx) in r.transactions.iter().enumerate() { + transactions.push(tx.read().with_context(|| format!("transactions[{i}]"))?) + } + } + } + + Ok(Self { + protocol_version, + hash: required(&r.hash) + .and_then(|h| parse_h256(h)) + .context("hash")?, + l1_batch_number: L1BatchNumber( + *required(&r.l1_batch_number).context("l1_batch_number")?, + ), + timestamp: *required(&r.timestamp).context("timestamp")?, + l1_gas_price: *required(&r.l1_gas_price).context("l1_gas_price")?, + l2_fair_gas_price: *required(&r.l2_fair_gas_price).context("l2_fair_gas_price")?, + fair_pubdata_price: r.fair_pubdata_price, + virtual_blocks: *required(&r.virtual_blocks).context("virtual_blocks")?, + operator_address: required(&r.operator_address) + .and_then(|a| parse_h160(a)) + .context("operator_address")?, + transactions, + last_in_batch: *required(&r.last_in_batch).context("last_in_batch")?, + }) + } + + fn build(&self) -> Self::Proto { + let mut x = Self::Proto { + protocol_version: Some((self.protocol_version as u16).into()), + hash: Some(self.hash.as_bytes().into()), + l1_batch_number: Some(self.l1_batch_number.0), + timestamp: Some(self.timestamp), + l1_gas_price: Some(self.l1_gas_price), + l2_fair_gas_price: Some(self.l2_fair_gas_price), + fair_pubdata_price: self.fair_pubdata_price, + virtual_blocks: Some(self.virtual_blocks), + operator_address: Some(self.operator_address.as_bytes().into()), + // Transactions are stored in execution order, therefore order is deterministic. + transactions: vec![], + transactions_v25: vec![], + last_in_batch: Some(self.last_in_batch), + }; + match self.protocol_version { + v if v >= ProtocolVersionId::Version25 => { + x.transactions_v25 = self.transactions.iter().map(ProtoRepr::build).collect(); + } + _ => { + x.transactions = self.transactions.iter().map(ProtoRepr::build).collect(); + } + } + x + } +} + +impl ProtoRepr for proto::TransactionV25 { + type Type = Transaction; + + fn read(&self) -> anyhow::Result { + use proto::transaction_v25::T; + let tx = match required(&self.t)? { + T::L1(l1) => abi::Transaction::L1 { + tx: required(&l1.rlp) + .and_then(|x| { + let tokens = ethabi::decode(&[abi::L2CanonicalTransaction::schema()], x) + .context("ethabi::decode()")?; + // Unwrap is safe because `ethabi::decode` does the verification. + let tx = + abi::L2CanonicalTransaction::decode(tokens.into_iter().next().unwrap()) + .context("L2CanonicalTransaction::decode()")?; + Ok(tx) + }) + .context("rlp")? + .into(), + factory_deps: l1.factory_deps.clone(), + eth_block: 0, + }, + T::L2(l2) => abi::Transaction::L2(required(&l2.rlp).context("rlp")?.clone()), + }; + Transaction::from_abi(tx, true) + } + + fn build(tx: &Self::Type) -> Self { + let tx = abi::Transaction::try_from(tx.clone()).unwrap(); + use proto::transaction_v25::T; + Self { + t: Some(match tx { + abi::Transaction::L1 { + tx, factory_deps, .. + } => T::L1(proto::L1Transaction { + rlp: Some(ethabi::encode(&[tx.encode()])), + factory_deps, + }), + abi::Transaction::L2(tx) => T::L2(proto::L2Transaction { rlp: Some(tx) }), + }), + } + } +} + +impl ProtoRepr for proto::Transaction { + type Type = Transaction; + + fn read(&self) -> anyhow::Result { + let common_data = required(&self.common_data).context("common_data")?; + let execute = required(&self.execute).context("execute")?; + Ok(Self::Type { + common_data: match common_data { + proto::transaction::CommonData::L1(common_data) => { + anyhow::ensure!( + *required(&common_data.deadline_block) + .context("common_data.deadline_block")? + == 0 + ); + anyhow::ensure!( + required(&common_data.eth_hash) + .and_then(|x| parse_h256(x)) + .context("common_data.eth_hash")? + == H256::default() + ); + ExecuteTransactionCommon::L1(L1TxCommonData { + sender: required(&common_data.sender_address) + .and_then(|x| parse_h160(x)) + .context("common_data.sender_address")?, + serial_id: required(&common_data.serial_id) + .map(|x| PriorityOpId(*x)) + .context("common_data.serial_id")?, + layer_2_tip_fee: required(&common_data.layer_2_tip_fee) + .and_then(|x| parse_h256(x)) + .map(h256_to_u256) + .context("common_data.layer_2_tip_fee")?, + full_fee: required(&common_data.full_fee) + .and_then(|x| parse_h256(x)) + .map(h256_to_u256) + .context("common_data.full_fee")?, + max_fee_per_gas: required(&common_data.max_fee_per_gas) + .and_then(|x| parse_h256(x)) + .map(h256_to_u256) + .context("common_data.max_fee_per_gas")?, + gas_limit: required(&common_data.gas_limit) + .and_then(|x| parse_h256(x)) + .map(h256_to_u256) + .context("common_data.gas_limit")?, + gas_per_pubdata_limit: required(&common_data.gas_per_pubdata_limit) + .and_then(|x| parse_h256(x)) + .map(h256_to_u256) + .context("common_data.gas_per_pubdata_limit")?, + op_processing_type: required(&common_data.op_processing_type) + .and_then(|x| { + OpProcessingType::try_from(u8::try_from(*x)?) + .map_err(|_| anyhow!("u8::try_from")) + }) + .context("common_data.op_processing_type")?, + priority_queue_type: required(&common_data.priority_queue_type) + .and_then(|x| { + PriorityQueueType::try_from(u8::try_from(*x)?) + .map_err(|_| anyhow!("u8::try_from")) + }) + .context("common_data.priority_queue_type")?, + eth_block: *required(&common_data.eth_block) + .context("common_data.eth_block")?, + canonical_tx_hash: required(&common_data.canonical_tx_hash) + .and_then(|x| parse_h256(x)) + .context("common_data.canonical_tx_hash")?, + to_mint: required(&common_data.to_mint) + .and_then(|x| parse_h256(x)) + .map(h256_to_u256) + .context("common_data.to_mint")?, + refund_recipient: required(&common_data.refund_recipient_address) + .and_then(|x| parse_h160(x)) + .context("common_data.refund_recipient_address")?, + }) + } + proto::transaction::CommonData::L2(common_data) => { + ExecuteTransactionCommon::L2(L2TxCommonData { + nonce: required(&common_data.nonce) + .map(|x| Nonce(*x)) + .context("common_data.nonce")?, + fee: Fee { + gas_limit: required(&common_data.gas_limit) + .and_then(|x| parse_h256(x)) + .map(h256_to_u256) + .context("common_data.gas_limit")?, + max_fee_per_gas: required(&common_data.max_fee_per_gas) + .and_then(|x| parse_h256(x)) + .map(h256_to_u256) + .context("common_data.max_fee_per_gas")?, + max_priority_fee_per_gas: required( + &common_data.max_priority_fee_per_gas, + ) + .and_then(|x| parse_h256(x)) + .map(h256_to_u256) + .context("common_data.max_priority_fee_per_gas")?, + gas_per_pubdata_limit: required(&common_data.gas_per_pubdata_limit) + .and_then(|x| parse_h256(x)) + .map(h256_to_u256) + .context("common_data.gas_per_pubdata_limit")?, + }, + initiator_address: required(&common_data.initiator_address) + .and_then(|x| parse_h160(x)) + .context("common_data.initiator_address")?, + signature: required(&common_data.signature) + .context("common_data.signature")? + .clone(), + transaction_type: required(&common_data.transaction_type) + .and_then(|x| Ok(TransactionType::try_from(*x)?)) + .context("common_data.transaction_type")?, + input: { + match &common_data.input { + None => None, + Some(input) => Some(InputData { + hash: required(&input.hash) + .and_then(|x| parse_h256(x)) + .context("common_data.input.hash")?, + data: required(&input.data) + .context("common_data.input.data")? + .clone(), + }), + } + }, + paymaster_params: { + let params = required(&common_data.paymaster_params)?; + PaymasterParams { + paymaster: required(¶ms.paymaster_address) + .and_then(|x| parse_h160(x)) + .context("common_data.paymaster_params.paymaster_address")?, + paymaster_input: required(¶ms.paymaster_input) + .context("common_data.paymaster_params.paymaster_input")? + .clone(), + } + }, + }) + } + proto::transaction::CommonData::ProtocolUpgrade(common_data) => { + ExecuteTransactionCommon::ProtocolUpgrade(ProtocolUpgradeTxCommonData { + sender: required(&common_data.sender_address) + .and_then(|x| parse_h160(x)) + .context("common_data.sender_address")?, + upgrade_id: required(&common_data.upgrade_id) + .and_then(|x| Ok(ProtocolVersionId::try_from(u16::try_from(*x)?)?)) + .context("common_data.upgrade_id")?, + max_fee_per_gas: required(&common_data.max_fee_per_gas) + .and_then(|x| parse_h256(x)) + .map(h256_to_u256) + .context("common_data.max_fee_per_gas")?, + gas_limit: required(&common_data.gas_limit) + .and_then(|x| parse_h256(x)) + .map(h256_to_u256) + .context("common_data.gas_limit")?, + gas_per_pubdata_limit: required(&common_data.gas_per_pubdata_limit) + .and_then(|x| parse_h256(x)) + .map(h256_to_u256) + .context("common_data.gas_per_pubdata_limit")?, + eth_block: *required(&common_data.eth_block) + .context("common_data.eth_block")?, + canonical_tx_hash: required(&common_data.canonical_tx_hash) + .and_then(|x| parse_h256(x)) + .context("common_data.canonical_tx_hash")?, + to_mint: required(&common_data.to_mint) + .and_then(|x| parse_h256(x)) + .map(h256_to_u256) + .context("common_data.to_mint")?, + refund_recipient: required(&common_data.refund_recipient_address) + .and_then(|x| parse_h160(x)) + .context("common_data.refund_recipient_address")?, + }) + } + }, + execute: Execute { + contract_address: execute + .contract_address + .as_ref() + .and_then(|x| parse_h160(x).ok()), + calldata: required(&execute.calldata).context("calldata")?.clone(), + value: required(&execute.value) + .and_then(|x| parse_h256(x)) + .map(h256_to_u256) + .context("execute.value")?, + 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()), + }) + } + + fn build(this: &Self::Type) -> Self { + let common_data = match &this.common_data { + ExecuteTransactionCommon::L1(data) => { + proto::transaction::CommonData::L1(proto::L1TxCommonData { + sender_address: Some(data.sender.as_bytes().into()), + serial_id: Some(data.serial_id.0), + deadline_block: Some(0), + layer_2_tip_fee: Some(u256_to_h256(data.layer_2_tip_fee).as_bytes().into()), + full_fee: Some(u256_to_h256(data.full_fee).as_bytes().into()), + max_fee_per_gas: Some(u256_to_h256(data.max_fee_per_gas).as_bytes().into()), + gas_limit: Some(u256_to_h256(data.gas_limit).as_bytes().into()), + gas_per_pubdata_limit: Some( + u256_to_h256(data.gas_per_pubdata_limit).as_bytes().into(), + ), + op_processing_type: Some(data.op_processing_type as u32), + priority_queue_type: Some(data.priority_queue_type as u32), + eth_hash: Some(H256::default().as_bytes().into()), + eth_block: Some(data.eth_block), + canonical_tx_hash: Some(data.canonical_tx_hash.as_bytes().into()), + to_mint: Some(u256_to_h256(data.to_mint).as_bytes().into()), + refund_recipient_address: Some(data.refund_recipient.as_bytes().into()), + }) + } + ExecuteTransactionCommon::L2(data) => { + proto::transaction::CommonData::L2(proto::L2TxCommonData { + nonce: Some(data.nonce.0), + gas_limit: Some(u256_to_h256(data.fee.gas_limit).as_bytes().into()), + max_fee_per_gas: Some(u256_to_h256(data.fee.max_fee_per_gas).as_bytes().into()), + max_priority_fee_per_gas: Some( + u256_to_h256(data.fee.max_priority_fee_per_gas) + .as_bytes() + .into(), + ), + gas_per_pubdata_limit: Some( + u256_to_h256(data.fee.gas_per_pubdata_limit) + .as_bytes() + .into(), + ), + initiator_address: Some(data.initiator_address.as_bytes().into()), + signature: Some(data.signature.clone()), + transaction_type: Some(data.transaction_type as u32), + input: data.input.as_ref().map(|input_data| proto::InputData { + data: Some(input_data.data.clone()), + hash: Some(input_data.hash.as_bytes().into()), + }), + paymaster_params: Some(proto::PaymasterParams { + paymaster_input: Some(data.paymaster_params.paymaster_input.clone()), + paymaster_address: Some(data.paymaster_params.paymaster.as_bytes().into()), + }), + }) + } + ExecuteTransactionCommon::ProtocolUpgrade(data) => { + proto::transaction::CommonData::ProtocolUpgrade( + proto::ProtocolUpgradeTxCommonData { + sender_address: Some(data.sender.as_bytes().into()), + upgrade_id: Some(data.upgrade_id as u32), + max_fee_per_gas: Some(u256_to_h256(data.max_fee_per_gas).as_bytes().into()), + gas_limit: Some(u256_to_h256(data.gas_limit).as_bytes().into()), + gas_per_pubdata_limit: Some( + u256_to_h256(data.gas_per_pubdata_limit).as_bytes().into(), + ), + eth_hash: Some(H256::default().as_bytes().into()), + eth_block: Some(data.eth_block), + canonical_tx_hash: Some(data.canonical_tx_hash.as_bytes().into()), + to_mint: Some(u256_to_h256(data.to_mint).as_bytes().into()), + refund_recipient_address: Some(data.refund_recipient.as_bytes().into()), + }, + ) + } + }; + let execute = proto::Execute { + contract_address: this.execute.contract_address.map(|x| x.as_bytes().into()), + calldata: Some(this.execute.calldata.clone()), + value: Some(u256_to_h256(this.execute.value).as_bytes().into()), + factory_deps: this.execute.factory_deps.clone(), + }; + Self { + common_data: Some(common_data), + execute: Some(execute), + raw_bytes: this.raw_bytes.as_ref().map(|inner| inner.0.clone()), + } + } +} + +impl ProtoRepr for proto::AttesterCommittee { + type Type = attester::Committee; + + fn read(&self) -> anyhow::Result { + let members: Vec<_> = self + .members + .iter() + .enumerate() + .map(|(i, m)| attester::WeightedAttester::read(m).context(i)) + .collect::>() + .context("members")?; + Self::Type::new(members) + } + + fn build(this: &Self::Type) -> Self { + Self { + members: this.iter().map(|x| x.build()).collect(), + } + } +} diff --git a/core/lib/dal/src/consensus/mod.rs b/core/lib/dal/src/consensus/mod.rs index 876dfe14bed..8e88265730e 100644 --- a/core/lib/dal/src/consensus/mod.rs +++ b/core/lib/dal/src/consensus/mod.rs @@ -1,29 +1,20 @@ -pub mod proto; +use std::collections::BTreeMap; +use zksync_concurrency::net; +use zksync_consensus_roles::{attester, node, validator}; +use zksync_types::{ethabi, Address, L1BatchNumber, ProtocolVersionId, Transaction, H256}; + +mod conv; +pub mod proto; #[cfg(test)] mod testonly; #[cfg(test)] mod tests; -use std::collections::BTreeMap; - -use anyhow::{anyhow, Context as _}; -use zksync_concurrency::net; -use zksync_consensus_roles::{attester, node, validator}; -use zksync_protobuf::{read_required, required, ProtoFmt, ProtoRepr}; -use zksync_types::{ - abi, ethabi, - fee::Fee, - l1::{OpProcessingType, PriorityQueueType}, - l2::TransactionType, - protocol_upgrade::ProtocolUpgradeTxCommonData, - transaction_request::PaymasterParams, - Address, Execute, ExecuteTransactionCommon, InputData, L1BatchNumber, L1TxCommonData, - L2TxCommonData, Nonce, PriorityOpId, ProtocolVersionId, Transaction, H256, -}; -use zksync_utils::{h256_to_u256, u256_to_h256}; - -use crate::models::{parse_h160, parse_h256}; +#[derive(Debug, PartialEq, Clone)] +pub struct BlockMetadata { + pub payload_hash: validator::PayloadHash, +} /// Global config of the consensus. #[derive(Debug, PartialEq, Clone)] @@ -33,57 +24,6 @@ pub struct GlobalConfig { pub seed_peers: BTreeMap, } -impl ProtoRepr for proto::NodeAddr { - type Type = (node::PublicKey, net::Host); - fn read(&self) -> anyhow::Result { - Ok(( - read_required(&self.key).context("key")?, - net::Host(required(&self.addr).context("addr")?.clone()), - )) - } - fn build(this: &Self::Type) -> Self { - Self { - key: Some(this.0.build()), - addr: Some(this.1 .0.clone()), - } - } -} - -impl ProtoFmt for GlobalConfig { - type Proto = proto::GlobalConfig; - - fn read(r: &Self::Proto) -> anyhow::Result { - Ok(Self { - genesis: read_required(&r.genesis).context("genesis")?, - registry_address: r - .registry_address - .as_ref() - .map(|a| parse_h160(a)) - .transpose() - .context("registry_address")?, - seed_peers: r - .seed_peers - .iter() - .enumerate() - .map(|(i, e)| e.read().context(i)) - .collect::>() - .context("seed_peers")?, - }) - } - - fn build(&self) -> Self::Proto { - Self::Proto { - genesis: Some(self.genesis.build()), - registry_address: self.registry_address.map(|a| a.as_bytes().to_vec()), - seed_peers: self - .seed_peers - .iter() - .map(|(k, v)| ProtoRepr::build(&(k.clone(), v.clone()))) - .collect(), - } - } -} - /// Global attestation status served by /// `attestationStatus` RPC. #[derive(Debug, PartialEq, Clone)] @@ -92,26 +32,6 @@ pub struct AttestationStatus { pub next_batch_to_attest: attester::BatchNumber, } -impl ProtoFmt for AttestationStatus { - type Proto = proto::AttestationStatus; - - fn read(r: &Self::Proto) -> anyhow::Result { - Ok(Self { - genesis: read_required(&r.genesis).context("genesis")?, - next_batch_to_attest: attester::BatchNumber( - *required(&r.next_batch_to_attest).context("next_batch_to_attest")?, - ), - }) - } - - fn build(&self) -> Self::Proto { - Self::Proto { - genesis: Some(self.genesis.build()), - next_batch_to_attest: Some(self.next_batch_to_attest.0), - } - } -} - /// L2 block (= miniblock) payload. #[derive(Debug, PartialEq)] pub struct Payload { @@ -128,88 +48,6 @@ pub struct Payload { pub last_in_batch: bool, } -impl ProtoFmt for Payload { - type Proto = proto::Payload; - - fn read(r: &Self::Proto) -> anyhow::Result { - let protocol_version = required(&r.protocol_version) - .and_then(|x| Ok(ProtocolVersionId::try_from(u16::try_from(*x)?)?)) - .context("protocol_version")?; - let mut transactions = vec![]; - - match protocol_version { - v if v >= ProtocolVersionId::Version25 => { - anyhow::ensure!( - r.transactions.is_empty(), - "transactions should be empty in protocol_version {v}" - ); - for (i, tx) in r.transactions_v25.iter().enumerate() { - transactions.push( - tx.read() - .with_context(|| format!("transactions_v25[{i}]"))?, - ); - } - } - v => { - anyhow::ensure!( - r.transactions_v25.is_empty(), - "transactions_v25 should be empty in protocol_version {v}" - ); - for (i, tx) in r.transactions.iter().enumerate() { - transactions.push(tx.read().with_context(|| format!("transactions[{i}]"))?) - } - } - } - - Ok(Self { - protocol_version, - hash: required(&r.hash) - .and_then(|h| parse_h256(h)) - .context("hash")?, - l1_batch_number: L1BatchNumber( - *required(&r.l1_batch_number).context("l1_batch_number")?, - ), - timestamp: *required(&r.timestamp).context("timestamp")?, - l1_gas_price: *required(&r.l1_gas_price).context("l1_gas_price")?, - l2_fair_gas_price: *required(&r.l2_fair_gas_price).context("l2_fair_gas_price")?, - fair_pubdata_price: r.fair_pubdata_price, - virtual_blocks: *required(&r.virtual_blocks).context("virtual_blocks")?, - operator_address: required(&r.operator_address) - .and_then(|a| parse_h160(a)) - .context("operator_address")?, - transactions, - last_in_batch: *required(&r.last_in_batch).context("last_in_batch")?, - }) - } - - fn build(&self) -> Self::Proto { - let mut x = Self::Proto { - protocol_version: Some((self.protocol_version as u16).into()), - hash: Some(self.hash.as_bytes().into()), - l1_batch_number: Some(self.l1_batch_number.0), - timestamp: Some(self.timestamp), - l1_gas_price: Some(self.l1_gas_price), - l2_fair_gas_price: Some(self.l2_fair_gas_price), - fair_pubdata_price: self.fair_pubdata_price, - virtual_blocks: Some(self.virtual_blocks), - operator_address: Some(self.operator_address.as_bytes().into()), - // Transactions are stored in execution order, therefore order is deterministic. - transactions: vec![], - transactions_v25: vec![], - last_in_batch: Some(self.last_in_batch), - }; - match self.protocol_version { - v if v >= ProtocolVersionId::Version25 => { - x.transactions_v25 = self.transactions.iter().map(ProtoRepr::build).collect(); - } - _ => { - x.transactions = self.transactions.iter().map(ProtoRepr::build).collect(); - } - } - x - } -} - impl Payload { pub fn decode(payload: &validator::Payload) -> anyhow::Result { zksync_protobuf::decode(&payload.0) @@ -219,337 +57,3 @@ impl Payload { validator::Payload(zksync_protobuf::encode(self)) } } - -impl ProtoRepr for proto::TransactionV25 { - type Type = Transaction; - - fn read(&self) -> anyhow::Result { - use proto::transaction_v25::T; - let tx = match required(&self.t)? { - T::L1(l1) => abi::Transaction::L1 { - tx: required(&l1.rlp) - .and_then(|x| { - let tokens = ethabi::decode(&[abi::L2CanonicalTransaction::schema()], x) - .context("ethabi::decode()")?; - // Unwrap is safe because `ethabi::decode` does the verification. - let tx = - abi::L2CanonicalTransaction::decode(tokens.into_iter().next().unwrap()) - .context("L2CanonicalTransaction::decode()")?; - Ok(tx) - }) - .context("rlp")? - .into(), - factory_deps: l1.factory_deps.clone(), - eth_block: 0, - }, - T::L2(l2) => abi::Transaction::L2(required(&l2.rlp).context("rlp")?.clone()), - }; - Transaction::from_abi(tx, true) - } - - fn build(tx: &Self::Type) -> Self { - let tx = abi::Transaction::try_from(tx.clone()).unwrap(); - use proto::transaction_v25::T; - Self { - t: Some(match tx { - abi::Transaction::L1 { - tx, factory_deps, .. - } => T::L1(proto::L1Transaction { - rlp: Some(ethabi::encode(&[tx.encode()])), - factory_deps, - }), - abi::Transaction::L2(tx) => T::L2(proto::L2Transaction { rlp: Some(tx) }), - }), - } - } -} - -impl ProtoRepr for proto::Transaction { - type Type = Transaction; - - fn read(&self) -> anyhow::Result { - let common_data = required(&self.common_data).context("common_data")?; - let execute = required(&self.execute).context("execute")?; - Ok(Self::Type { - common_data: match common_data { - proto::transaction::CommonData::L1(common_data) => { - anyhow::ensure!( - *required(&common_data.deadline_block) - .context("common_data.deadline_block")? - == 0 - ); - anyhow::ensure!( - required(&common_data.eth_hash) - .and_then(|x| parse_h256(x)) - .context("common_data.eth_hash")? - == H256::default() - ); - ExecuteTransactionCommon::L1(L1TxCommonData { - sender: required(&common_data.sender_address) - .and_then(|x| parse_h160(x)) - .context("common_data.sender_address")?, - serial_id: required(&common_data.serial_id) - .map(|x| PriorityOpId(*x)) - .context("common_data.serial_id")?, - layer_2_tip_fee: required(&common_data.layer_2_tip_fee) - .and_then(|x| parse_h256(x)) - .map(h256_to_u256) - .context("common_data.layer_2_tip_fee")?, - full_fee: required(&common_data.full_fee) - .and_then(|x| parse_h256(x)) - .map(h256_to_u256) - .context("common_data.full_fee")?, - max_fee_per_gas: required(&common_data.max_fee_per_gas) - .and_then(|x| parse_h256(x)) - .map(h256_to_u256) - .context("common_data.max_fee_per_gas")?, - gas_limit: required(&common_data.gas_limit) - .and_then(|x| parse_h256(x)) - .map(h256_to_u256) - .context("common_data.gas_limit")?, - gas_per_pubdata_limit: required(&common_data.gas_per_pubdata_limit) - .and_then(|x| parse_h256(x)) - .map(h256_to_u256) - .context("common_data.gas_per_pubdata_limit")?, - op_processing_type: required(&common_data.op_processing_type) - .and_then(|x| { - OpProcessingType::try_from(u8::try_from(*x)?) - .map_err(|_| anyhow!("u8::try_from")) - }) - .context("common_data.op_processing_type")?, - priority_queue_type: required(&common_data.priority_queue_type) - .and_then(|x| { - PriorityQueueType::try_from(u8::try_from(*x)?) - .map_err(|_| anyhow!("u8::try_from")) - }) - .context("common_data.priority_queue_type")?, - eth_block: *required(&common_data.eth_block) - .context("common_data.eth_block")?, - canonical_tx_hash: required(&common_data.canonical_tx_hash) - .and_then(|x| parse_h256(x)) - .context("common_data.canonical_tx_hash")?, - to_mint: required(&common_data.to_mint) - .and_then(|x| parse_h256(x)) - .map(h256_to_u256) - .context("common_data.to_mint")?, - refund_recipient: required(&common_data.refund_recipient_address) - .and_then(|x| parse_h160(x)) - .context("common_data.refund_recipient_address")?, - }) - } - proto::transaction::CommonData::L2(common_data) => { - ExecuteTransactionCommon::L2(L2TxCommonData { - nonce: required(&common_data.nonce) - .map(|x| Nonce(*x)) - .context("common_data.nonce")?, - fee: Fee { - gas_limit: required(&common_data.gas_limit) - .and_then(|x| parse_h256(x)) - .map(h256_to_u256) - .context("common_data.gas_limit")?, - max_fee_per_gas: required(&common_data.max_fee_per_gas) - .and_then(|x| parse_h256(x)) - .map(h256_to_u256) - .context("common_data.max_fee_per_gas")?, - max_priority_fee_per_gas: required( - &common_data.max_priority_fee_per_gas, - ) - .and_then(|x| parse_h256(x)) - .map(h256_to_u256) - .context("common_data.max_priority_fee_per_gas")?, - gas_per_pubdata_limit: required(&common_data.gas_per_pubdata_limit) - .and_then(|x| parse_h256(x)) - .map(h256_to_u256) - .context("common_data.gas_per_pubdata_limit")?, - }, - initiator_address: required(&common_data.initiator_address) - .and_then(|x| parse_h160(x)) - .context("common_data.initiator_address")?, - signature: required(&common_data.signature) - .context("common_data.signature")? - .clone(), - transaction_type: required(&common_data.transaction_type) - .and_then(|x| Ok(TransactionType::try_from(*x)?)) - .context("common_data.transaction_type")?, - input: { - match &common_data.input { - None => None, - Some(input) => Some(InputData { - hash: required(&input.hash) - .and_then(|x| parse_h256(x)) - .context("common_data.input.hash")?, - data: required(&input.data) - .context("common_data.input.data")? - .clone(), - }), - } - }, - paymaster_params: { - let params = required(&common_data.paymaster_params)?; - PaymasterParams { - paymaster: required(¶ms.paymaster_address) - .and_then(|x| parse_h160(x)) - .context("common_data.paymaster_params.paymaster_address")?, - paymaster_input: required(¶ms.paymaster_input) - .context("common_data.paymaster_params.paymaster_input")? - .clone(), - } - }, - }) - } - proto::transaction::CommonData::ProtocolUpgrade(common_data) => { - ExecuteTransactionCommon::ProtocolUpgrade(ProtocolUpgradeTxCommonData { - sender: required(&common_data.sender_address) - .and_then(|x| parse_h160(x)) - .context("common_data.sender_address")?, - upgrade_id: required(&common_data.upgrade_id) - .and_then(|x| Ok(ProtocolVersionId::try_from(u16::try_from(*x)?)?)) - .context("common_data.upgrade_id")?, - max_fee_per_gas: required(&common_data.max_fee_per_gas) - .and_then(|x| parse_h256(x)) - .map(h256_to_u256) - .context("common_data.max_fee_per_gas")?, - gas_limit: required(&common_data.gas_limit) - .and_then(|x| parse_h256(x)) - .map(h256_to_u256) - .context("common_data.gas_limit")?, - gas_per_pubdata_limit: required(&common_data.gas_per_pubdata_limit) - .and_then(|x| parse_h256(x)) - .map(h256_to_u256) - .context("common_data.gas_per_pubdata_limit")?, - eth_block: *required(&common_data.eth_block) - .context("common_data.eth_block")?, - canonical_tx_hash: required(&common_data.canonical_tx_hash) - .and_then(|x| parse_h256(x)) - .context("common_data.canonical_tx_hash")?, - to_mint: required(&common_data.to_mint) - .and_then(|x| parse_h256(x)) - .map(h256_to_u256) - .context("common_data.to_mint")?, - refund_recipient: required(&common_data.refund_recipient_address) - .and_then(|x| parse_h160(x)) - .context("common_data.refund_recipient_address")?, - }) - } - }, - execute: Execute { - contract_address: execute - .contract_address - .as_ref() - .and_then(|x| parse_h160(x).ok()), - calldata: required(&execute.calldata).context("calldata")?.clone(), - value: required(&execute.value) - .and_then(|x| parse_h256(x)) - .map(h256_to_u256) - .context("execute.value")?, - 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()), - }) - } - - fn build(this: &Self::Type) -> Self { - let common_data = match &this.common_data { - ExecuteTransactionCommon::L1(data) => { - proto::transaction::CommonData::L1(proto::L1TxCommonData { - sender_address: Some(data.sender.as_bytes().into()), - serial_id: Some(data.serial_id.0), - deadline_block: Some(0), - layer_2_tip_fee: Some(u256_to_h256(data.layer_2_tip_fee).as_bytes().into()), - full_fee: Some(u256_to_h256(data.full_fee).as_bytes().into()), - max_fee_per_gas: Some(u256_to_h256(data.max_fee_per_gas).as_bytes().into()), - gas_limit: Some(u256_to_h256(data.gas_limit).as_bytes().into()), - gas_per_pubdata_limit: Some( - u256_to_h256(data.gas_per_pubdata_limit).as_bytes().into(), - ), - op_processing_type: Some(data.op_processing_type as u32), - priority_queue_type: Some(data.priority_queue_type as u32), - eth_hash: Some(H256::default().as_bytes().into()), - eth_block: Some(data.eth_block), - canonical_tx_hash: Some(data.canonical_tx_hash.as_bytes().into()), - to_mint: Some(u256_to_h256(data.to_mint).as_bytes().into()), - refund_recipient_address: Some(data.refund_recipient.as_bytes().into()), - }) - } - ExecuteTransactionCommon::L2(data) => { - proto::transaction::CommonData::L2(proto::L2TxCommonData { - nonce: Some(data.nonce.0), - gas_limit: Some(u256_to_h256(data.fee.gas_limit).as_bytes().into()), - max_fee_per_gas: Some(u256_to_h256(data.fee.max_fee_per_gas).as_bytes().into()), - max_priority_fee_per_gas: Some( - u256_to_h256(data.fee.max_priority_fee_per_gas) - .as_bytes() - .into(), - ), - gas_per_pubdata_limit: Some( - u256_to_h256(data.fee.gas_per_pubdata_limit) - .as_bytes() - .into(), - ), - initiator_address: Some(data.initiator_address.as_bytes().into()), - signature: Some(data.signature.clone()), - transaction_type: Some(data.transaction_type as u32), - input: data.input.as_ref().map(|input_data| proto::InputData { - data: Some(input_data.data.clone()), - hash: Some(input_data.hash.as_bytes().into()), - }), - paymaster_params: Some(proto::PaymasterParams { - paymaster_input: Some(data.paymaster_params.paymaster_input.clone()), - paymaster_address: Some(data.paymaster_params.paymaster.as_bytes().into()), - }), - }) - } - ExecuteTransactionCommon::ProtocolUpgrade(data) => { - proto::transaction::CommonData::ProtocolUpgrade( - proto::ProtocolUpgradeTxCommonData { - sender_address: Some(data.sender.as_bytes().into()), - upgrade_id: Some(data.upgrade_id as u32), - max_fee_per_gas: Some(u256_to_h256(data.max_fee_per_gas).as_bytes().into()), - gas_limit: Some(u256_to_h256(data.gas_limit).as_bytes().into()), - gas_per_pubdata_limit: Some( - u256_to_h256(data.gas_per_pubdata_limit).as_bytes().into(), - ), - eth_hash: Some(H256::default().as_bytes().into()), - eth_block: Some(data.eth_block), - canonical_tx_hash: Some(data.canonical_tx_hash.as_bytes().into()), - to_mint: Some(u256_to_h256(data.to_mint).as_bytes().into()), - refund_recipient_address: Some(data.refund_recipient.as_bytes().into()), - }, - ) - } - }; - let execute = proto::Execute { - contract_address: this.execute.contract_address.map(|x| x.as_bytes().into()), - calldata: Some(this.execute.calldata.clone()), - value: Some(u256_to_h256(this.execute.value).as_bytes().into()), - factory_deps: this.execute.factory_deps.clone(), - }; - Self { - common_data: Some(common_data), - execute: Some(execute), - raw_bytes: this.raw_bytes.as_ref().map(|inner| inner.0.clone()), - } - } -} - -impl ProtoRepr for proto::AttesterCommittee { - type Type = attester::Committee; - - fn read(&self) -> anyhow::Result { - let members: Vec<_> = self - .members - .iter() - .enumerate() - .map(|(i, m)| attester::WeightedAttester::read(m).context(i)) - .collect::>() - .context("members")?; - Self::Type::new(members) - } - - fn build(this: &Self::Type) -> Self { - Self { - members: this.iter().map(|x| x.build()).collect(), - } - } -} diff --git a/core/lib/dal/src/consensus/proto/mod.proto b/core/lib/dal/src/consensus/proto/mod.proto index ab1245f3ef6..421904bf966 100644 --- a/core/lib/dal/src/consensus/proto/mod.proto +++ b/core/lib/dal/src/consensus/proto/mod.proto @@ -6,6 +6,10 @@ import "zksync/roles/validator.proto"; import "zksync/roles/attester.proto"; import "zksync/roles/node.proto"; +message BlockMetadata { + optional roles.validator.PayloadHash payload_hash = 1; // required +} + message Payload { // zksync-era ProtocolVersionId optional uint32 protocol_version = 9; // required; u16 diff --git a/core/lib/dal/src/consensus/testonly.rs b/core/lib/dal/src/consensus/testonly.rs index 904a4c563d2..13086323b17 100644 --- a/core/lib/dal/src/consensus/testonly.rs +++ b/core/lib/dal/src/consensus/testonly.rs @@ -1,11 +1,17 @@ -use rand::{ - distributions::{Distribution, Standard}, - Rng, -}; +use rand::{distributions::Distribution, Rng}; +use zksync_consensus_utils::EncodeDist; -use super::AttestationStatus; +use super::*; -impl Distribution for Standard { +impl Distribution for EncodeDist { + fn sample(&self, rng: &mut R) -> BlockMetadata { + BlockMetadata { + payload_hash: rng.gen(), + } + } +} + +impl Distribution for EncodeDist { fn sample(&self, rng: &mut R) -> AttestationStatus { AttestationStatus { genesis: rng.gen(), @@ -13,3 +19,16 @@ impl Distribution for Standard { } } } + +impl Distribution for EncodeDist { + fn sample(&self, rng: &mut R) -> GlobalConfig { + GlobalConfig { + genesis: rng.gen(), + registry_address: Some(rng.gen()), + seed_peers: self + .sample_range(rng) + .map(|_| (rng.gen(), self.sample(rng))) + .collect(), + } + } +} diff --git a/core/lib/dal/src/consensus/tests.rs b/core/lib/dal/src/consensus/tests.rs index 7059f1a74ea..e8342b7446c 100644 --- a/core/lib/dal/src/consensus/tests.rs +++ b/core/lib/dal/src/consensus/tests.rs @@ -4,7 +4,7 @@ use rand::Rng; use zksync_concurrency::ctx; use zksync_protobuf::{ repr::{decode, encode}, - testonly::{test_encode, test_encode_random}, + testonly::{test_encode, test_encode_all_formats, FmtConv}, ProtoRepr, }; use zksync_test_account::Account; @@ -12,7 +12,7 @@ use zksync_types::{ web3::Bytes, Execute, ExecuteTransactionCommon, L1BatchNumber, ProtocolVersionId, Transaction, }; -use super::{proto, AttestationStatus, Payload}; +use super::*; use crate::tests::mock_protocol_upgrade_transaction; fn execute(rng: &mut impl Rng) -> Execute { @@ -59,7 +59,9 @@ fn payload(rng: &mut impl Rng, protocol_version: ProtocolVersionId) -> Payload { fn test_encoding() { let ctx = &ctx::test_root(&ctx::RealClock); let rng = &mut ctx.rng(); - test_encode_random::(rng); + test_encode_all_formats::>(rng); + test_encode_all_formats::>(rng); + test_encode_all_formats::>(rng); encode_decode::(l1_transaction(rng)); encode_decode::(l2_transaction(rng)); encode_decode::(l1_transaction(rng)); diff --git a/core/lib/dal/src/consensus_dal.rs b/core/lib/dal/src/consensus_dal/mod.rs similarity index 79% rename from core/lib/dal/src/consensus_dal.rs rename to core/lib/dal/src/consensus_dal/mod.rs index dd976f22086..eb3385a992a 100644 --- a/core/lib/dal/src/consensus_dal.rs +++ b/core/lib/dal/src/consensus_dal/mod.rs @@ -1,16 +1,25 @@ use anyhow::Context as _; +use zksync_consensus_crypto::keccak256::Keccak256; use zksync_consensus_roles::{attester, validator}; -use zksync_consensus_storage::{BlockStoreState, ReplicaState}; +use zksync_consensus_storage::{BlockStoreState, Last, ReplicaState}; use zksync_db_connection::{ connection::Connection, error::{DalError, DalResult, SqlxContext}, instrument::{InstrumentExt, Instrumented}, }; -use zksync_types::L2BlockNumber; +use zksync_l1_contract_interface::i_executor::structures::StoredBatchInfo; +use zksync_types::{L1BatchNumber, L2BlockNumber}; -pub use crate::consensus::{proto, AttestationStatus, GlobalConfig, Payload}; +pub use crate::consensus::{proto, AttestationStatus, BlockMetadata, GlobalConfig, Payload}; use crate::{Core, CoreDal}; +#[cfg(test)] +mod tests; + +pub fn batch_hash(info: &StoredBatchInfo) -> attester::BatchHash { + attester::BatchHash(Keccak256::from_bytes(info.hash().0)) +} + /// Storage access methods for `zksync_core::consensus` module. #[derive(Debug)] pub struct ConsensusDal<'a, 'c> { @@ -305,47 +314,63 @@ impl ConsensusDal<'_, '_> { Ok(next) } - /// Fetches the last consensus certificate. + /// Fetches the block store state. + /// The blocks that are available to consensus are either pre-genesis or + /// have a consensus certificate. /// Currently, certificates are NOT generated synchronously with L2 blocks, - /// so it might NOT be the certificate for the last L2 block. - pub async fn block_certificates_range(&mut self) -> anyhow::Result { - // It cannot be older than genesis first block. - let mut start = self + /// so the `BlockStoreState.last` might be different than the last block in storage. + pub async fn block_store_state(&mut self) -> anyhow::Result { + let first = self.first_block().await.context("first_block()")?; + let cfg = self .global_config() - .await? - .context("genesis()")? - .genesis - .first_block; - start = start.max(self.first_block().await.context("first_block()")?); - let row = sqlx::query!( + .await + .context("global_config()")? + .context("global config is missing")?; + + // If there is a cert in storage, then the block range visible to consensus + // is [first block, block of last cert]. + if let Some(row) = sqlx::query!( r#" SELECT certificate FROM miniblocks_consensus - WHERE - number >= $1 ORDER BY number DESC LIMIT 1 "#, - i64::try_from(start.0)?, ) .instrument("block_certificate_range") .report_latency() .fetch_optional(self.storage) - .await?; - Ok(BlockStoreState { - first: start, - last: row - .map(|row| { + .await? + { + return Ok(BlockStoreState { + first, + last: Some(Last::Final( zksync_protobuf::serde::Deserialize { deny_unknown_fields: true, } - .proto_fmt(row.certificate) - }) - .transpose()?, + .proto_fmt(row.certificate)?, + )), + }); + } + + // Otherwise it is [first block, min(genesis.first_block-1,last block)]. + let next = self + .next_block() + .await + .context("next_block()")? + .min(cfg.genesis.first_block); + Ok(BlockStoreState { + first, + // unwrap is ok, because `next > first >= 0`. + last: if next > first { + Some(Last::PreGenesis(next.prev().unwrap())) + } else { + None + }, }) } @@ -461,6 +486,19 @@ impl ConsensusDal<'_, '_> { .next()) } + /// Fetches L2 block metadata for the given block number. + pub async fn block_metadata( + &mut self, + n: validator::BlockNumber, + ) -> anyhow::Result> { + let Some(b) = self.block_payload(n).await.context("block_payload()")? else { + return Ok(None); + }; + Ok(Some(BlockMetadata { + payload_hash: b.encode().hash(), + })) + } + /// Inserts a certificate for the L2 block `cert.header().number`. /// Fails if certificate doesn't match the stored block. pub async fn insert_block_certificate( @@ -558,15 +596,29 @@ impl ConsensusDal<'_, '_> { )) } + /// Fetches the L1 batch info for the given number. + pub async fn batch_info( + &mut self, + number: attester::BatchNumber, + ) -> anyhow::Result> { + let n = L1BatchNumber(number.0.try_into().context("overflow")?); + Ok(self + .storage + .blocks_dal() + .get_l1_batch_metadata(n) + .await + .context("get_l1_batch_metadata()")? + .map(|x| StoredBatchInfo::from(&x))) + } + /// Inserts a certificate for the L1 batch. /// Noop if a certificate for the same L1 batch is already present. /// Verification against previously stored attester committee is performed. - /// Batch hash is not verified - it cannot be performed due to circular dependency on - /// `zksync_l1_contract_interface`. + /// Batch hash verification is performed. pub async fn insert_batch_certificate( &mut self, cert: &attester::BatchQC, - ) -> anyhow::Result<()> { + ) -> Result<(), InsertCertificateError> { let cfg = self .global_config() .await @@ -577,6 +629,16 @@ impl ConsensusDal<'_, '_> { .await .context("attester_committee()")? .context("attester committee is missing")?; + let hash = batch_hash( + &self + .batch_info(cert.message.number) + .await + .context("batch()")? + .context("batch is missing")?, + ); + if cert.message.hash != hash { + return Err(InsertCertificateError::PayloadMismatch); + } cert.verify(cfg.genesis.hash(), &committee) .context("cert.verify()")?; sqlx::query!( @@ -711,158 +773,3 @@ impl ConsensusDal<'_, '_> { })) } } - -#[cfg(test)] -mod tests { - use rand::Rng as _; - use zksync_consensus_roles::{attester, validator}; - use zksync_consensus_storage::ReplicaState; - use zksync_types::ProtocolVersion; - - use super::GlobalConfig; - use crate::{ - tests::{create_l1_batch_header, create_l2_block_header}, - ConnectionPool, Core, CoreDal, - }; - - #[tokio::test] - async fn replica_state_read_write() { - let rng = &mut rand::thread_rng(); - let pool = ConnectionPool::::test_pool().await; - let mut conn = pool.connection().await.unwrap(); - assert_eq!(None, conn.consensus_dal().global_config().await.unwrap()); - for n in 0..3 { - let setup = validator::testonly::Setup::new(rng, 3); - let mut genesis = (*setup.genesis).clone(); - genesis.fork_number = validator::ForkNumber(n); - let cfg = GlobalConfig { - genesis: genesis.with_hash(), - registry_address: Some(rng.gen()), - seed_peers: [].into(), // TODO: rng.gen() for Host - }; - conn.consensus_dal() - .try_update_global_config(&cfg) - .await - .unwrap(); - assert_eq!( - cfg, - conn.consensus_dal().global_config().await.unwrap().unwrap() - ); - assert_eq!( - ReplicaState::default(), - conn.consensus_dal().replica_state().await.unwrap() - ); - for _ in 0..5 { - let want: ReplicaState = rng.gen(); - conn.consensus_dal().set_replica_state(&want).await.unwrap(); - assert_eq!( - cfg, - conn.consensus_dal().global_config().await.unwrap().unwrap() - ); - assert_eq!(want, conn.consensus_dal().replica_state().await.unwrap()); - } - } - } - - #[tokio::test] - async fn test_batch_certificate() { - let rng = &mut rand::thread_rng(); - let setup = validator::testonly::Setup::new(rng, 3); - let pool = ConnectionPool::::test_pool().await; - let mut conn = pool.connection().await.unwrap(); - let cfg = GlobalConfig { - genesis: setup.genesis.clone(), - registry_address: Some(rng.gen()), - seed_peers: [].into(), - }; - conn.consensus_dal() - .try_update_global_config(&cfg) - .await - .unwrap(); - - let mut make_cert = |number: attester::BatchNumber| { - let m = attester::Batch { - genesis: setup.genesis.hash(), - hash: rng.gen(), - number, - }; - let mut sigs = attester::MultiSig::default(); - for k in &setup.attester_keys { - sigs.add(k.public(), k.sign_msg(m.clone()).sig); - } - attester::BatchQC { - message: m, - signatures: sigs, - } - }; - - // Required for inserting l2 blocks - conn.protocol_versions_dal() - .save_protocol_version_with_tx(&ProtocolVersion::default()) - .await - .unwrap(); - - // Insert some mock L2 blocks and L1 batches - let mut block_number = 0; - let mut batch_number = 0; - for _ in 0..3 { - for _ in 0..3 { - block_number += 1; - let l2_block = create_l2_block_header(block_number); - conn.blocks_dal().insert_l2_block(&l2_block).await.unwrap(); - } - batch_number += 1; - let l1_batch = create_l1_batch_header(batch_number); - conn.blocks_dal() - .insert_mock_l1_batch(&l1_batch) - .await - .unwrap(); - conn.blocks_dal() - .mark_l2_blocks_as_executed_in_l1_batch(l1_batch.number) - .await - .unwrap(); - } - - let n = attester::BatchNumber(batch_number.into()); - - // Insert a batch certificate for the last L1 batch. - let want = make_cert(n); - conn.consensus_dal() - .upsert_attester_committee(n, setup.genesis.attesters.as_ref().unwrap()) - .await - .unwrap(); - conn.consensus_dal() - .insert_batch_certificate(&want) - .await - .unwrap(); - - // Reinserting a cert should fail. - assert!(conn - .consensus_dal() - .insert_batch_certificate(&make_cert(n)) - .await - .is_err()); - - // Retrieve the latest certificate. - let got_n = conn - .consensus_dal() - .last_batch_certificate_number() - .await - .unwrap() - .unwrap(); - let got = conn - .consensus_dal() - .batch_certificate(got_n) - .await - .unwrap() - .unwrap(); - assert_eq!(got, want); - - // Try insert batch certificate for non-existing batch - assert!(conn - .consensus_dal() - .insert_batch_certificate(&make_cert(n.next())) - .await - .is_err()); - } -} diff --git a/core/lib/dal/src/consensus_dal/tests.rs b/core/lib/dal/src/consensus_dal/tests.rs new file mode 100644 index 00000000000..772e7b2bf5e --- /dev/null +++ b/core/lib/dal/src/consensus_dal/tests.rs @@ -0,0 +1,186 @@ +use rand::Rng as _; +use zksync_consensus_roles::{attester, validator}; +use zksync_consensus_storage::ReplicaState; +use zksync_types::{ + block::L1BatchTreeData, + commitment::{L1BatchCommitmentArtifacts, L1BatchCommitmentHash}, + ProtocolVersion, +}; + +use super::*; +use crate::{ + tests::{create_l1_batch_header, create_l2_block_header}, + ConnectionPool, Core, CoreDal, +}; + +#[tokio::test] +async fn replica_state_read_write() { + let rng = &mut rand::thread_rng(); + let pool = ConnectionPool::::test_pool().await; + let mut conn = pool.connection().await.unwrap(); + assert_eq!(None, conn.consensus_dal().global_config().await.unwrap()); + for n in 0..3 { + let setup = validator::testonly::Setup::new(rng, 3); + let mut genesis = (*setup.genesis).clone(); + genesis.fork_number = validator::ForkNumber(n); + let cfg = GlobalConfig { + genesis: genesis.with_hash(), + registry_address: Some(rng.gen()), + seed_peers: [].into(), // TODO: rng.gen() for Host + }; + conn.consensus_dal() + .try_update_global_config(&cfg) + .await + .unwrap(); + assert_eq!( + cfg, + conn.consensus_dal().global_config().await.unwrap().unwrap() + ); + assert_eq!( + ReplicaState::default(), + conn.consensus_dal().replica_state().await.unwrap() + ); + for _ in 0..5 { + let want: ReplicaState = rng.gen(); + conn.consensus_dal().set_replica_state(&want).await.unwrap(); + assert_eq!( + cfg, + conn.consensus_dal().global_config().await.unwrap().unwrap() + ); + assert_eq!(want, conn.consensus_dal().replica_state().await.unwrap()); + } + } +} + +#[tokio::test] +async fn test_batch_certificate() { + let rng = &mut rand::thread_rng(); + let setup = validator::testonly::Setup::new(rng, 3); + let pool = ConnectionPool::::test_pool().await; + let mut conn = pool.connection().await.unwrap(); + let cfg = GlobalConfig { + genesis: setup.genesis.clone(), + registry_address: Some(rng.gen()), + seed_peers: [].into(), + }; + conn.consensus_dal() + .try_update_global_config(&cfg) + .await + .unwrap(); + + let make_cert = |number: attester::BatchNumber, hash: attester::BatchHash| { + let m = attester::Batch { + genesis: setup.genesis.hash(), + hash, + number, + }; + let mut sigs = attester::MultiSig::default(); + for k in &setup.attester_keys { + sigs.add(k.public(), k.sign_msg(m.clone()).sig); + } + attester::BatchQC { + message: m, + signatures: sigs, + } + }; + + // Required for inserting l2 blocks + conn.protocol_versions_dal() + .save_protocol_version_with_tx(&ProtocolVersion::default()) + .await + .unwrap(); + + // Insert some mock L2 blocks and L1 batches + let mut block_number = 0; + let mut batch_number = 0; + for _ in 0..3 { + for _ in 0..3 { + block_number += 1; + let l2_block = create_l2_block_header(block_number); + conn.blocks_dal().insert_l2_block(&l2_block).await.unwrap(); + } + batch_number += 1; + let l1_batch = create_l1_batch_header(batch_number); + conn.blocks_dal() + .insert_mock_l1_batch(&l1_batch) + .await + .unwrap(); + conn.blocks_dal() + .save_l1_batch_tree_data( + l1_batch.number, + &L1BatchTreeData { + hash: rng.gen(), + rollup_last_leaf_index: rng.gen(), + }, + ) + .await + .unwrap(); + conn.blocks_dal() + .save_l1_batch_commitment_artifacts( + l1_batch.number, + &L1BatchCommitmentArtifacts { + commitment_hash: L1BatchCommitmentHash { + pass_through_data: rng.gen(), + aux_output: rng.gen(), + meta_parameters: rng.gen(), + commitment: rng.gen(), + }, + l2_l1_merkle_root: rng.gen(), + compressed_state_diffs: None, + compressed_initial_writes: None, + compressed_repeated_writes: None, + zkporter_is_available: false, + aux_commitments: None, + }, + ) + .await + .unwrap(); + conn.blocks_dal() + .mark_l2_blocks_as_executed_in_l1_batch(l1_batch.number) + .await + .unwrap(); + } + + let n = attester::BatchNumber(batch_number.into()); + + // Insert a batch certificate for the last L1 batch. + let hash = batch_hash(&conn.consensus_dal().batch_info(n).await.unwrap().unwrap()); + let want = make_cert(n, hash); + conn.consensus_dal() + .upsert_attester_committee(n, setup.genesis.attesters.as_ref().unwrap()) + .await + .unwrap(); + conn.consensus_dal() + .insert_batch_certificate(&want) + .await + .unwrap(); + + // Reinserting a cert should fail. + assert!(conn + .consensus_dal() + .insert_batch_certificate(&make_cert(n, hash)) + .await + .is_err()); + + // Retrieve the latest certificate. + let got_n = conn + .consensus_dal() + .last_batch_certificate_number() + .await + .unwrap() + .unwrap(); + let got = conn + .consensus_dal() + .batch_certificate(got_n) + .await + .unwrap() + .unwrap(); + assert_eq!(got, want); + + // Try insert batch certificate for non-existing batch + assert!(conn + .consensus_dal() + .insert_batch_certificate(&make_cert(n.next(), rng.gen())) + .await + .is_err()); +} diff --git a/core/lib/dal/src/models/mod.rs b/core/lib/dal/src/models/mod.rs index 479649f8509..12e41ac780a 100644 --- a/core/lib/dal/src/models/mod.rs +++ b/core/lib/dal/src/models/mod.rs @@ -1,7 +1,6 @@ pub mod storage_block; -use anyhow::Context as _; use zksync_db_connection::error::SqlxContext; -use zksync_types::{ProtocolVersionId, H160, H256}; +use zksync_types::ProtocolVersionId; mod call; pub mod storage_base_token_ratio; @@ -19,18 +18,6 @@ pub mod storage_verification_request; #[cfg(test)] mod tests; -pub(crate) fn parse_h256(bytes: &[u8]) -> anyhow::Result { - Ok(<[u8; 32]>::try_from(bytes).context("invalid size")?.into()) -} - -fn parse_h256_opt(bytes: Option<&[u8]>) -> anyhow::Result { - parse_h256(bytes.context("missing data")?) -} - -pub(crate) fn parse_h160(bytes: &[u8]) -> anyhow::Result { - Ok(<[u8; 20]>::try_from(bytes).context("invalid size")?.into()) -} - pub(crate) fn parse_protocol_version(raw: i32) -> sqlx::Result { u16::try_from(raw) .decode_column("protocol_version")? diff --git a/core/lib/dal/src/models/storage_sync.rs b/core/lib/dal/src/models/storage_sync.rs index cf7b76d8163..7a4ebe074fe 100644 --- a/core/lib/dal/src/models/storage_sync.rs +++ b/core/lib/dal/src/models/storage_sync.rs @@ -1,13 +1,11 @@ use zksync_contracts::BaseSystemContractsHashes; use zksync_db_connection::error::SqlxContext; use zksync_types::{ - api::en, Address, L1BatchNumber, L2BlockNumber, ProtocolVersionId, Transaction, H256, + api::en, parse_h160, parse_h256, parse_h256_opt, Address, L1BatchNumber, L2BlockNumber, + ProtocolVersionId, Transaction, H256, }; -use crate::{ - consensus_dal::Payload, - models::{parse_h160, parse_h256, parse_h256_opt, parse_protocol_version}, -}; +use crate::{consensus_dal::Payload, models::parse_protocol_version}; #[derive(Debug, Clone, sqlx::FromRow)] pub(crate) struct StorageSyncBlock { diff --git a/core/lib/l1_contract_interface/Cargo.toml b/core/lib/l1_contract_interface/Cargo.toml index 8b68df854e7..1aa4c256e0f 100644 --- a/core/lib/l1_contract_interface/Cargo.toml +++ b/core/lib/l1_contract_interface/Cargo.toml @@ -19,12 +19,14 @@ crypto_codegen.workspace = true # Used to calculate the kzg commitment and proofs kzg.workspace = true +anyhow.workspace = true sha2.workspace = true sha3.workspace = true hex.workspace = true once_cell.workspace = true [dev-dependencies] +rand.workspace = true serde.workspace = true serde_json.workspace = true serde_with = { workspace = true, features = ["base64", "hex"] } diff --git a/core/lib/l1_contract_interface/src/i_executor/structures/mod.rs b/core/lib/l1_contract_interface/src/i_executor/structures/mod.rs index d1ed57e41f2..aa987204901 100644 --- a/core/lib/l1_contract_interface/src/i_executor/structures/mod.rs +++ b/core/lib/l1_contract_interface/src/i_executor/structures/mod.rs @@ -3,4 +3,7 @@ mod commit_batch_info; mod stored_batch_info; +#[cfg(test)] +mod tests; + pub use self::{commit_batch_info::CommitBatchInfo, stored_batch_info::StoredBatchInfo}; diff --git a/core/lib/l1_contract_interface/src/i_executor/structures/stored_batch_info.rs b/core/lib/l1_contract_interface/src/i_executor/structures/stored_batch_info.rs index 8373c46e36b..26f9b30392e 100644 --- a/core/lib/l1_contract_interface/src/i_executor/structures/stored_batch_info.rs +++ b/core/lib/l1_contract_interface/src/i_executor/structures/stored_batch_info.rs @@ -1,7 +1,8 @@ +use anyhow::Context as _; use zksync_types::{ commitment::L1BatchWithMetadata, - ethabi::{self, Token}, - web3, + ethabi::{self, ParamType, Token}, + parse_h256, web3, web3::contract::Error as ContractError, H256, U256, }; @@ -9,7 +10,7 @@ use zksync_types::{ use crate::Tokenizable; /// `StoredBatchInfo` from `IExecutor.sol`. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct StoredBatchInfo { pub batch_number: u64, pub batch_hash: H256, @@ -22,11 +23,35 @@ pub struct StoredBatchInfo { } impl StoredBatchInfo { + fn schema() -> Vec { + vec![ParamType::Tuple(vec![ + ParamType::Uint(64), + ParamType::FixedBytes(32), + ParamType::Uint(64), + ParamType::Uint(256), + ParamType::FixedBytes(32), + ParamType::FixedBytes(32), + ParamType::Uint(256), + ParamType::FixedBytes(32), + ])] + } + + /// Encodes the struct into RLP. + pub fn encode(&self) -> Vec { + ethabi::encode(&[self.clone().into_token()]) + } + + /// Decodes the struct from RLP. + pub fn decode(rlp: &[u8]) -> anyhow::Result { + let [token] = ethabi::decode_whole(&Self::schema(), rlp)? + .try_into() + .unwrap(); + Ok(Self::from_token(token)?) + } + /// `_hashStoredBatchInfo` from `Executor.sol`. pub fn hash(&self) -> H256 { - H256(web3::keccak256(ðabi::encode(&[self - .clone() - .into_token()]))) + H256(web3::keccak256(&self.encode())) } } @@ -46,11 +71,42 @@ impl From<&L1BatchWithMetadata> for StoredBatchInfo { } impl Tokenizable for StoredBatchInfo { - fn from_token(_token: Token) -> Result { - // Currently there is no need to decode this struct. - // We still want to implement `Tokenizable` trait for it, so that *once* it's needed - // the implementation is provided here and not in some other inconsistent way. - Err(ContractError::Other("Not implemented".into())) + fn from_token(token: Token) -> Result { + (|| { + let [ + Token::Uint(batch_number), + Token::FixedBytes(batch_hash), + Token::Uint(index_repeated_storage_changes), + Token::Uint(number_of_layer1_txs), + Token::FixedBytes(priority_operations_hash), + Token::FixedBytes(l2_logs_tree_root), + Token::Uint(timestamp), + Token::FixedBytes(commitment), + ] : [Token;8] = token + .into_tuple().context("not a tuple")? + .try_into().ok().context("bad length")? + else { anyhow::bail!("bad format") }; + Ok(Self { + batch_number: batch_number + .try_into() + .ok() + .context("overflow") + .context("batch_number")?, + batch_hash: parse_h256(&batch_hash).context("batch_hash")?, + index_repeated_storage_changes: index_repeated_storage_changes + .try_into() + .ok() + .context("overflow") + .context("index_repeated_storage_changes")?, + number_of_layer1_txs, + priority_operations_hash: parse_h256(&priority_operations_hash) + .context("priority_operations_hash")?, + l2_logs_tree_root: parse_h256(&l2_logs_tree_root).context("l2_logs_tree_root")?, + timestamp, + commitment: parse_h256(&commitment).context("commitment")?, + }) + })() + .map_err(|err| ContractError::InvalidOutputType(format!("{err:#}"))) } fn into_token(self) -> Token { diff --git a/core/lib/l1_contract_interface/src/i_executor/structures/tests.rs b/core/lib/l1_contract_interface/src/i_executor/structures/tests.rs new file mode 100644 index 00000000000..0cb8caffb34 --- /dev/null +++ b/core/lib/l1_contract_interface/src/i_executor/structures/tests.rs @@ -0,0 +1,32 @@ +use rand::{ + distributions::{Distribution, Standard}, + Rng, +}; + +use super::*; + +impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> StoredBatchInfo { + StoredBatchInfo { + batch_number: rng.gen(), + batch_hash: rng.gen(), + index_repeated_storage_changes: rng.gen(), + number_of_layer1_txs: rng.gen::().into(), + priority_operations_hash: rng.gen(), + l2_logs_tree_root: rng.gen(), + timestamp: rng.gen::().into(), + commitment: rng.gen(), + } + } +} + +/// Test checking encoding and decoding of `StoredBatchInfo`. +#[test] +fn test_encoding() { + let rng = &mut rand::thread_rng(); + for _ in 0..10 { + let want: StoredBatchInfo = rng.gen(); + let got = StoredBatchInfo::decode(&want.encode()).unwrap(); + assert_eq!(want, got); + } +} diff --git a/core/lib/types/src/api/en.rs b/core/lib/types/src/api/en.rs index 9391c862757..209ab7c24f9 100644 --- a/core/lib/types/src/api/en.rs +++ b/core/lib/types/src/api/en.rs @@ -64,3 +64,9 @@ pub struct ConsensusGenesis(pub serde_json::Value); /// The wrapped JSON value corresponds to `zksync_dal::consensus::AttestationStatus`. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AttestationStatus(pub serde_json::Value); + +/// Block metadata that should have been committed to on L1, but it is not. +/// +/// The wrapped JSON value corresponds to `zksync_dal::consensus::BlockMetadata`. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BlockMetadata(pub serde_json::Value); diff --git a/core/lib/web3_decl/src/namespaces/en.rs b/core/lib/web3_decl/src/namespaces/en.rs index 8a4d2db8c6f..0f1fd9d34b8 100644 --- a/core/lib/web3_decl/src/namespaces/en.rs +++ b/core/lib/web3_decl/src/namespaces/en.rs @@ -28,6 +28,12 @@ pub trait EnNamespace { #[method(name = "consensusGlobalConfig")] async fn consensus_global_config(&self) -> RpcResult>; + #[method(name = "blockMetadata")] + async fn block_metadata( + &self, + block_number: L2BlockNumber, + ) -> RpcResult>; + /// Lists all tokens created at or before the specified `block_number`. /// /// This method is used by EN after snapshot recovery in order to recover token records. diff --git a/core/node/api_server/src/web3/backend_jsonrpsee/namespaces/en.rs b/core/node/api_server/src/web3/backend_jsonrpsee/namespaces/en.rs index de763526373..9f5e54a5f4f 100644 --- a/core/node/api_server/src/web3/backend_jsonrpsee/namespaces/en.rs +++ b/core/node/api_server/src/web3/backend_jsonrpsee/namespaces/en.rs @@ -37,6 +37,15 @@ impl EnNamespaceServer for EnNamespace { .map_err(|err| self.current_method().map_err(err)) } + async fn block_metadata( + &self, + block_number: L2BlockNumber, + ) -> RpcResult> { + self.block_metadata_impl(block_number) + .await + .map_err(|err| self.current_method().map_err(err)) + } + async fn sync_tokens(&self, block_number: Option) -> RpcResult> { self.sync_tokens_impl(block_number) .await diff --git a/core/node/api_server/src/web3/namespaces/en.rs b/core/node/api_server/src/web3/namespaces/en.rs index 721ca985ceb..a09a0cb92fc 100644 --- a/core/node/api_server/src/web3/namespaces/en.rs +++ b/core/node/api_server/src/web3/namespaces/en.rs @@ -1,5 +1,6 @@ use anyhow::Context as _; use zksync_config::{configs::EcosystemContracts, GenesisConfig}; +use zksync_consensus_roles::validator; use zksync_dal::{CoreDal, DalError}; use zksync_types::{ api::en, protocol_version::ProtocolSemanticVersion, tokens::TokenInfo, Address, L1BatchNumber, @@ -86,6 +87,36 @@ impl EnNamespace { ))) } + #[tracing::instrument(skip(self))] + pub async fn block_metadata_impl( + &self, + block_number: L2BlockNumber, + ) -> Result, Web3Error> { + let Some(meta) = self + .state + .acquire_connection() + .await? + // unwrap is ok, because we start outermost transaction. + .transaction_builder() + .unwrap() + // run readonly transaction to perform consistent reads. + .set_readonly() + .build() + .await + .context("TransactionBuilder::build()")? + .consensus_dal() + .block_metadata(validator::BlockNumber(block_number.0.into())) + .await? + else { + return Ok(None); + }; + Ok(Some(en::BlockMetadata( + zksync_protobuf::serde::Serialize + .proto_fmt(&meta, serde_json::value::Serializer) + .unwrap(), + ))) + } + pub(crate) fn current_method(&self) -> &MethodTracer { &self.state.current_method } diff --git a/core/node/consensus/src/batch.rs b/core/node/consensus/src/batch.rs deleted file mode 100644 index af38f446c1b..00000000000 --- a/core/node/consensus/src/batch.rs +++ /dev/null @@ -1,275 +0,0 @@ -//! 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::storage::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::from_abi(abi::Transaction::try_from(t.clone())?, true)?; - 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/config.rs b/core/node/consensus/src/config.rs index 3584d533f66..4ad7a551ab4 100644 --- a/core/node/consensus/src/config.rs +++ b/core/node/consensus/src/config.rs @@ -169,7 +169,6 @@ pub(super) fn executor( server_addr: cfg.server_addr, public_addr: net::Host(cfg.public_addr.0.clone()), max_payload_size: cfg.max_payload_size, - max_batch_size: cfg.max_batch_size, node_key: node_key(secrets) .context("node_key")? .context("missing node_key")?, @@ -184,6 +183,5 @@ pub(super) fn executor( gossip_static_outbound, rpc, debug_page, - batch_poll_interval: time::Duration::seconds(1), }) } diff --git a/core/node/consensus/src/en.rs b/core/node/consensus/src/en.rs index e4be8d9d687..c358974fb0c 100644 --- a/core/node/consensus/src/en.rs +++ b/core/node/consensus/src/en.rs @@ -4,7 +4,7 @@ use anyhow::Context as _; use zksync_concurrency::{ctx, error::Wrap as _, scope, time}; use zksync_consensus_executor::{self as executor, attestation}; use zksync_consensus_roles::{attester, validator}; -use zksync_consensus_storage::{BatchStore, BlockStore}; +use zksync_consensus_storage::BlockStore; use zksync_dal::consensus_dal; use zksync_node_sync::{fetcher::FetchedBlock, sync_action::ActionQueueSender, SyncState}; use zksync_types::L2BlockNumber; @@ -32,8 +32,13 @@ impl EN { /// Task running a consensus node for the external node. /// It may be a validator, but it cannot be a leader (cannot propose blocks). /// - /// NOTE: Before starting the consensus node it fetches all the blocks + /// If `enable_pregenesis` is false, + /// before starting the consensus node it fetches all the blocks /// older than consensus genesis from the main node using json RPC. + /// NOTE: currently `enable_pregenesis` is hardcoded to `false` in `era.rs`. + /// True is used only in tests. Once the `block_metadata` RPC is enabled everywhere + /// this flag should be removed and fetching pregenesis blocks will always be done + /// over the gossip network. pub async fn run( self, ctx: &ctx::Ctx, @@ -41,6 +46,7 @@ impl EN { cfg: ConsensusConfig, secrets: ConsensusSecrets, build_version: Option, + enable_pregenesis: bool, ) -> anyhow::Result<()> { let attester = config::attester_key(&secrets).context("attester_key")?; @@ -72,13 +78,15 @@ impl EN { drop(conn); // Fetch blocks before the genesis. - self.fetch_blocks( - ctx, - &mut payload_queue, - Some(global_config.genesis.first_block), - ) - .await - .wrap("fetch_blocks()")?; + if !enable_pregenesis { + self.fetch_blocks( + ctx, + &mut payload_queue, + Some(global_config.genesis.first_block), + ) + .await + .wrap("fetch_blocks()")?; + } // Monitor the genesis of the main node. // If it changes, it means that a hard fork occurred and we need to reset the consensus state. @@ -102,9 +110,14 @@ impl EN { // Run consensus component. // External nodes have a payload queue which they use to fetch data from the main node. - let (store, runner) = Store::new(ctx, self.pool.clone(), Some(payload_queue)) - .await - .wrap("Store::new()")?; + let (store, runner) = Store::new( + ctx, + self.pool.clone(), + Some(payload_queue), + Some(self.client.clone()), + ) + .await + .wrap("Store::new()")?; s.spawn_bg(async { Ok(runner.run(ctx).await?) }); let (block_store, runner) = BlockStore::new(ctx, Box::new(store.clone())) @@ -112,11 +125,6 @@ impl EN { .wrap("BlockStore::new()")?; s.spawn_bg(async { Ok(runner.run(ctx).await?) }); - let (batch_store, runner) = BatchStore::new(ctx, Box::new(store.clone())) - .await - .wrap("BatchStore::new()")?; - s.spawn_bg(async { Ok(runner.run(ctx).await?) }); - let attestation = Arc::new(attestation::Controller::new(attester)); s.spawn_bg(self.run_attestation_controller( ctx, @@ -127,7 +135,6 @@ impl EN { let executor = executor::Executor { config: config::executor(&cfg, &secrets, &global_config, build_version)?, block_store, - batch_store, validator: config::validator_key(&secrets) .context("validator_key")? .map(|key| executor::Validator { @@ -210,10 +217,13 @@ impl EN { "waiting for hash of batch {:?}", status.next_batch_to_attest ); - let hash = self - .pool - .wait_for_batch_hash(ctx, status.next_batch_to_attest) - .await?; + let hash = consensus_dal::batch_hash( + &self + .pool + .wait_for_batch_info(ctx, status.next_batch_to_attest, POLL_INTERVAL) + .await + .wrap("wait_for_batch_info()")?, + ); let Some(committee) = registry .attester_committee_for( ctx, diff --git a/core/node/consensus/src/era.rs b/core/node/consensus/src/era.rs index 3150f839680..916b7cdd89a 100644 --- a/core/node/consensus/src/era.rs +++ b/core/node/consensus/src/era.rs @@ -59,8 +59,18 @@ pub async fn run_external_node( is_validator = secrets.validator_key.is_some(), "running external node" ); - en.run(ctx, actions, cfg, secrets, Some(build_version)) - .await + // We will enable it once the main node on all envs supports + // `block_metadata()` JSON RPC method. + let enable_pregenesis = false; + en.run( + ctx, + actions, + cfg, + secrets, + Some(build_version), + enable_pregenesis, + ) + .await } None => { tracing::info!("running fetcher"); diff --git a/core/node/consensus/src/lib.rs b/core/node/consensus/src/lib.rs index ff9cdf86528..8bf078120aa 100644 --- a/core/node/consensus/src/lib.rs +++ b/core/node/consensus/src/lib.rs @@ -6,10 +6,6 @@ use zksync_config::configs::consensus::{ConsensusConfig, ConsensusSecrets}; mod abi; -// 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/mn.rs b/core/node/consensus/src/mn.rs index f80bfe58954..5abbdc3503b 100644 --- a/core/node/consensus/src/mn.rs +++ b/core/node/consensus/src/mn.rs @@ -5,7 +5,7 @@ use zksync_concurrency::{ctx, error::Wrap as _, scope, time}; use zksync_config::configs::consensus::{ConsensusConfig, ConsensusSecrets}; use zksync_consensus_executor::{self as executor, attestation}; use zksync_consensus_roles::{attester, validator}; -use zksync_consensus_storage::{BatchStore, BlockStore}; +use zksync_consensus_storage::BlockStore; use zksync_dal::consensus_dal; use crate::{ @@ -43,7 +43,7 @@ pub async fn run_main_node( } // The main node doesn't have a payload queue as it produces all the L2 blocks itself. - let (store, runner) = Store::new(ctx, pool.clone(), None) + let (store, runner) = Store::new(ctx, pool.clone(), None, None) .await .wrap("Store::new()")?; s.spawn_bg(runner.run(ctx)); @@ -67,11 +67,6 @@ pub async fn run_main_node( .wrap("BlockStore::new()")?; s.spawn_bg(runner.run(ctx)); - let (batch_store, runner) = BatchStore::new(ctx, Box::new(store.clone())) - .await - .wrap("BatchStore::new()")?; - s.spawn_bg(runner.run(ctx)); - let attestation = Arc::new(attestation::Controller::new(attester)); s.spawn_bg(run_attestation_controller( ctx, @@ -83,7 +78,6 @@ pub async fn run_main_node( let executor = executor::Executor { config: config::executor(&cfg, &secrets, &global_config, None)?, block_store, - batch_store, validator: Some(executor::Validator { key: validator_key, replica_store: Box::new(store.clone()), @@ -135,9 +129,10 @@ async fn run_attestation_controller( "waiting for hash of batch {:?}", status.next_batch_to_attest ); - let hash = pool - .wait_for_batch_hash(ctx, status.next_batch_to_attest) + let info = pool + .wait_for_batch_info(ctx, status.next_batch_to_attest, POLL_INTERVAL) .await?; + let hash = consensus_dal::batch_hash(&info); let Some(committee) = registry .attester_committee_for(ctx, registry_addr, status.next_batch_to_attest) .await diff --git a/core/node/consensus/src/registry/tests.rs b/core/node/consensus/src/registry/tests.rs index 935cd673891..33392a7f206 100644 --- a/core/node/consensus/src/registry/tests.rs +++ b/core/node/consensus/src/registry/tests.rs @@ -1,5 +1,5 @@ use rand::Rng as _; -use zksync_concurrency::{ctx, scope}; +use zksync_concurrency::{ctx, scope, time}; use zksync_consensus_roles::{attester, validator::testonly::Setup}; use zksync_test_account::Account; use zksync_types::ProtocolVersionId; @@ -7,6 +7,8 @@ use zksync_types::ProtocolVersionId; use super::*; use crate::storage::ConnectionPool; +const POLL_INTERVAL: time::Duration = time::Duration::milliseconds(500); + /// Test checking that parsing logic matches the abi specified in the json file. #[test] fn test_consensus_registry_abi() { @@ -73,7 +75,9 @@ async fn test_attester_committee() { node.push_block(&txs).await; node.seal_batch().await; - pool.wait_for_batch(ctx, node.last_batch()).await?; + pool.wait_for_batch_info(ctx, node.last_batch(), POLL_INTERVAL) + .await + .wrap("wait_for_batch_info()")?; // Read the attester committee using the vm. let batch = attester::BatchNumber(node.last_batch().0.into()); diff --git a/core/node/consensus/src/storage/connection.rs b/core/node/consensus/src/storage/connection.rs index 0f9d7c8527f..c30398498a9 100644 --- a/core/node/consensus/src/storage/connection.rs +++ b/core/node/consensus/src/storage/connection.rs @@ -1,18 +1,18 @@ use anyhow::Context as _; use zksync_concurrency::{ctx, error::Wrap as _, time}; -use zksync_consensus_crypto::keccak256::Keccak256; use zksync_consensus_roles::{attester, attester::BatchNumber, validator}; -use zksync_consensus_storage::{self as storage, BatchStoreState}; -use zksync_dal::{consensus_dal, consensus_dal::Payload, Core, CoreDal, DalError}; +use zksync_consensus_storage as storage; +use zksync_dal::{ + consensus_dal::{AttestationStatus, BlockMetadata, GlobalConfig, Payload}, + Core, CoreDal, DalError, +}; use zksync_l1_contract_interface::i_executor::structures::StoredBatchInfo; use zksync_node_sync::{fetcher::IoCursorExt as _, ActionQueueSender, SyncState}; use zksync_state_keeper::io::common::IoCursor; -use zksync_types::{ - commitment::L1BatchWithMetadata, fee_model::BatchFeeInput, L1BatchNumber, L2BlockNumber, -}; +use zksync_types::{fee_model::BatchFeeInput, L1BatchNumber, L2BlockNumber}; use zksync_vm_executor::oneshot::{BlockInfo, ResolvedBlockInfo}; -use super::{InsertCertificateError, PayloadQueue}; +use super::PayloadQueue; use crate::config; /// Context-aware `zksync_dal::ConnectionPool` wrapper. @@ -54,24 +54,24 @@ impl ConnectionPool { /// Waits for the `number` L1 batch hash. #[tracing::instrument(skip_all)] - pub async fn wait_for_batch_hash( + pub async fn wait_for_batch_info( &self, ctx: &ctx::Ctx, number: attester::BatchNumber, - ) -> ctx::Result { - const POLL_INTERVAL: time::Duration = time::Duration::milliseconds(500); + interval: time::Duration, + ) -> ctx::Result { loop { - if let Some(hash) = self + if let Some(info) = self .connection(ctx) .await .wrap("connection()")? - .batch_hash(ctx, number) + .batch_info(ctx, number) .await - .with_wrap(|| format!("batch_hash({number})"))? + .with_wrap(|| format!("batch_info({number})"))? { - return Ok(hash); + return Ok(info); } - ctx.sleep(POLL_INTERVAL).await?; + ctx.sleep(interval).await?; } } } @@ -109,16 +109,23 @@ impl<'a> Connection<'a> { .map_err(DalError::generalize)?) } - /// Wrapper for `consensus_dal().block_payloads()`. - pub async fn payloads( + pub async fn batch_info( &mut self, ctx: &ctx::Ctx, - numbers: std::ops::Range, - ) -> ctx::Result> { + n: attester::BatchNumber, + ) -> ctx::Result> { + Ok(ctx.wait(self.0.consensus_dal().batch_info(n)).await??) + } + + /// Wrapper for `consensus_dal().block_metadata()`. + pub async fn block_metadata( + &mut self, + ctx: &ctx::Ctx, + number: validator::BlockNumber, + ) -> ctx::Result> { Ok(ctx - .wait(self.0.consensus_dal().block_payloads(numbers)) - .await? - .map_err(DalError::generalize)?) + .wait(self.0.consensus_dal().block_metadata(number)) + .await??) } /// Wrapper for `consensus_dal().block_certificate()`. @@ -138,7 +145,7 @@ impl<'a> Connection<'a> { &mut self, ctx: &ctx::Ctx, cert: &validator::CommitQC, - ) -> Result<(), InsertCertificateError> { + ) -> Result<(), super::InsertCertificateError> { Ok(ctx .wait(self.0.consensus_dal().insert_block_certificate(cert)) .await??) @@ -151,20 +158,10 @@ impl<'a> Connection<'a> { &mut self, ctx: &ctx::Ctx, cert: &attester::BatchQC, - ) -> Result<(), InsertCertificateError> { - use consensus_dal::InsertCertificateError as E; - let want_hash = self - .batch_hash(ctx, cert.message.number) - .await - .wrap("batch_hash()")? - .ok_or(E::MissingPayload)?; - if want_hash != cert.message.hash { - return Err(E::PayloadMismatch.into()); - } + ) -> Result<(), super::InsertCertificateError> { Ok(ctx .wait(self.0.consensus_dal().insert_batch_certificate(cert)) - .await? - .map_err(E::Other)?) + .await??) } /// Wrapper for `consensus_dal().upsert_attester_committee()`. @@ -203,37 +200,6 @@ impl<'a> Connection<'a> { .context("sqlx")?) } - /// Wrapper for `consensus_dal().batch_hash()`. - pub async fn batch_hash( - &mut self, - ctx: &ctx::Ctx, - number: attester::BatchNumber, - ) -> ctx::Result> { - let n = L1BatchNumber(number.0.try_into().context("overflow")?); - let Some(meta) = ctx - .wait(self.0.blocks_dal().get_l1_batch_metadata(n)) - .await? - .context("get_l1_batch_metadata()")? - else { - return Ok(None); - }; - Ok(Some(attester::BatchHash(Keccak256::from_bytes( - StoredBatchInfo::from(&meta).hash().0, - )))) - } - - /// Wrapper for `blocks_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, @@ -249,10 +215,7 @@ impl<'a> Connection<'a> { } /// Wrapper for `consensus_dal().global_config()`. - pub async fn global_config( - &mut self, - ctx: &ctx::Ctx, - ) -> ctx::Result> { + pub async fn global_config(&mut self, ctx: &ctx::Ctx) -> ctx::Result> { Ok(ctx.wait(self.0.consensus_dal().global_config()).await??) } @@ -260,7 +223,7 @@ impl<'a> Connection<'a> { pub async fn try_update_global_config( &mut self, ctx: &ctx::Ctx, - cfg: &consensus_dal::GlobalConfig, + cfg: &GlobalConfig, ) -> ctx::Result<()> { Ok(ctx .wait(self.0.consensus_dal().try_update_global_config(cfg)) @@ -273,14 +236,14 @@ impl<'a> Connection<'a> { Ok(ctx.wait(self.0.consensus_dal().next_block()).await??) } - /// Wrapper for `consensus_dal().block_certificates_range()`. + /// Wrapper for `consensus_dal().block_store_state()`. #[tracing::instrument(skip_all)] - pub(crate) async fn block_certificates_range( + pub(crate) async fn block_store_state( &mut self, ctx: &ctx::Ctx, ) -> ctx::Result { Ok(ctx - .wait(self.0.consensus_dal().block_certificates_range()) + .wait(self.0.consensus_dal().block_store_state()) .await??) } @@ -305,7 +268,7 @@ impl<'a> Connection<'a> { } tracing::info!("Performing a hard fork of consensus."); - let new = consensus_dal::GlobalConfig { + let new = GlobalConfig { genesis: validator::GenesisRaw { chain_id: spec.chain_id, fork_number: old.as_ref().map_or(validator::ForkNumber(0), |old| { @@ -334,38 +297,35 @@ impl<'a> Connection<'a> { &mut self, ctx: &ctx::Ctx, number: validator::BlockNumber, - ) -> ctx::Result> { - let Some(justification) = self - .block_certificate(ctx, number) - .await - .wrap("block_certificate()")? - else { + ) -> ctx::Result> { + let Some(payload) = self.payload(ctx, number).await.wrap("payload()")? else { return Ok(None); }; - let payload = self - .payload(ctx, number) + if let Some(justification) = self + .block_certificate(ctx, number) .await - .wrap("payload()")? - .context("L2 block disappeared from storage")?; - - Ok(Some(validator::FinalBlock { - payload: payload.encode(), - justification, - })) - } + .wrap("block_certificate()")? + { + return Ok(Some( + validator::FinalBlock { + payload: payload.encode(), + justification, + } + .into(), + )); + } - /// Wrapper for `blocks_dal().get_sealed_l1_batch_number()`. - #[tracing::instrument(skip_all)] - pub async fn get_last_batch_number( - &mut self, - ctx: &ctx::Ctx, - ) -> ctx::Result> { - Ok(ctx - .wait(self.0.blocks_dal().get_sealed_l1_batch_number()) - .await? - .context("get_sealed_l1_batch_number()")? - .map(|nr| attester::BatchNumber(nr.0 as u64))) + Ok(Some( + validator::PreGenesisBlock { + number, + payload: payload.encode(), + // We won't use justification until it is possible to verify + // payload against the L1 batch commitment. + justification: validator::Justification(vec![]), + } + .into(), + )) } /// Wrapper for `blocks_dal().get_l2_block_range_of_l1_batch()`. @@ -388,83 +348,11 @@ impl<'a> Connection<'a> { })) } - /// Construct the [attester::SyncBatch] for a given batch number. - pub async fn get_batch( - &mut self, - ctx: &ctx::Ctx, - number: attester::BatchNumber, - ) -> ctx::Result> { - let Some((min, max)) = self - .get_l2_block_range_of_l1_batch(ctx, number) - .await - .context("get_l2_block_range_of_l1_batch()")? - else { - return Ok(None); - }; - - let payloads = self.payloads(ctx, min..max).await.wrap("payloads()")?; - let payloads = payloads.into_iter().map(|p| p.encode()).collect(); - - // TODO: Fill out the proof when we have the stateless L1 batch validation story finished. - // It is supposed to be a Merkle proof that the rolling hash of the batch has been included - // in the L1 system contract state tree. It is *not* the Ethereum state root hash, so producing - // it can be done without an L1 client, which is only required for validation. - let batch = attester::SyncBatch { - number, - payloads, - proof: Vec::new(), - }; - - Ok(Some(batch)) - } - - /// Construct the [storage::BatchStoreState] which contains the earliest batch and the last available [attester::SyncBatch]. - #[tracing::instrument(skip_all)] - pub async fn batches_range(&mut self, ctx: &ctx::Ctx) -> ctx::Result { - let first = self - .0 - .blocks_dal() - .get_earliest_l1_batch_number() - .await - .context("get_earliest_l1_batch_number()")?; - - let first = if first.is_some() { - first - } else { - self.0 - .snapshot_recovery_dal() - .get_applied_snapshot_status() - .await - .context("get_earliest_l1_batch_number()")? - .map(|s| s.l1_batch_number) - }; - - // TODO: In the future when we start filling in the `SyncBatch::proof` field, - // we can only run `get_batch` expecting `Some` result on numbers where the - // L1 state root hash is already available, so that we can produce some - // Merkle proof that the rolling hash of the L2 blocks in the batch has - // been included in the L1 state tree. At that point we probably can't - // call `get_last_batch_number` here, but something that indicates that - // the hashes/commitments on the L1 batch are ready and the thing has - // been included in L1; that potentially requires an API client as well. - let last = self - .get_last_batch_number(ctx) - .await - .context("get_last_batch_number()")?; - - Ok(BatchStoreState { - first: first - .map(|n| attester::BatchNumber(n.0 as u64)) - .unwrap_or(attester::BatchNumber(0)), - last, - }) - } - /// Wrapper for `consensus_dal().attestation_status()`. pub async fn attestation_status( &mut self, ctx: &ctx::Ctx, - ) -> ctx::Result> { + ) -> ctx::Result> { Ok(ctx .wait(self.0.consensus_dal().attestation_status()) .await? diff --git a/core/node/consensus/src/storage/store.rs b/core/node/consensus/src/storage/store.rs index cb8e039d7d0..ed83758ba9f 100644 --- a/core/node/consensus/src/storage/store.rs +++ b/core/node/consensus/src/storage/store.rs @@ -1,15 +1,18 @@ use std::sync::Arc; use anyhow::Context as _; -use tokio::sync::watch::Sender; use tracing::Instrument; use zksync_concurrency::{ctx, error::Wrap as _, scope, sync, time}; use zksync_consensus_bft::PayloadManager; -use zksync_consensus_roles::{attester, attester::BatchNumber, validator}; -use zksync_consensus_storage::{self as storage, BatchStoreState}; +use zksync_consensus_roles::validator; +use zksync_consensus_storage::{self as storage}; use zksync_dal::consensus_dal::{self, Payload}; use zksync_node_sync::fetcher::{FetchedBlock, FetchedTransaction}; use zksync_types::L2BlockNumber; +use zksync_web3_decl::{ + client::{DynClient, L2}, + namespaces::EnNamespaceClient as _, +}; use super::{Connection, PayloadQueue}; use crate::storage::{ConnectionPool, InsertCertificateError}; @@ -46,7 +49,7 @@ fn to_fetched_block( } /// Wrapper of `ConnectionPool` implementing `ReplicaStore`, `PayloadManager`, -/// `PersistentBlockStore` and `PersistentBatchStore`. +/// `PersistentBlockStore`. /// /// Contains queues to save Quorum Certificates received over gossip to the store /// as and when the payload they are over becomes available. @@ -59,8 +62,8 @@ pub(crate) struct Store { block_certificates: ctx::channel::UnboundedSender, /// Range of L2 blocks for which we have a QC persisted. blocks_persisted: sync::watch::Receiver, - /// Range of L1 batches we have persisted. - batches_persisted: sync::watch::Receiver, + /// Main node client. None if this node is the main node. + client: Option>>, } struct PersistedBlockState(sync::watch::Sender); @@ -69,7 +72,6 @@ struct PersistedBlockState(sync::watch::Sender); pub struct StoreRunner { pool: ConnectionPool, blocks_persisted: PersistedBlockState, - batches_persisted: sync::watch::Sender, block_certificates: ctx::channel::UnboundedReceiver, } @@ -78,22 +80,15 @@ impl Store { ctx: &ctx::Ctx, pool: ConnectionPool, payload_queue: Option, + client: Option>>, ) -> ctx::Result<(Store, StoreRunner)> { let mut conn = pool.connection(ctx).await.wrap("connection()")?; // Initial state of persisted blocks - let blocks_persisted = conn - .block_certificates_range(ctx) - .await - .wrap("block_certificates_range()")?; - - // Initial state of persisted batches - let batches_persisted = conn.batches_range(ctx).await.wrap("batches_range()")?; - + let blocks_persisted = conn.block_store_state(ctx).await.wrap("blocks_range()")?; drop(conn); let blocks_persisted = sync::watch::channel(blocks_persisted).0; - let batches_persisted = sync::watch::channel(batches_persisted).0; let (block_certs_send, block_certs_recv) = ctx::channel::unbounded(); Ok(( @@ -102,12 +97,11 @@ impl Store { block_certificates: block_certs_send, block_payloads: Arc::new(sync::Mutex::new(payload_queue)), blocks_persisted: blocks_persisted.subscribe(), - batches_persisted: batches_persisted.subscribe(), + client, }, StoreRunner { pool, blocks_persisted: PersistedBlockState(blocks_persisted), - batches_persisted, block_certificates: block_certs_recv, }, )) @@ -125,7 +119,7 @@ impl PersistedBlockState { /// If `persisted.first` is moved forward, it means that blocks have been pruned. /// If `persisted.last` is moved forward, it means that new blocks with certificates have been /// persisted. - #[tracing::instrument(skip_all, fields(first = %new.first, last = ?new.last.as_ref().map(|l| l.message.proposal.number)))] + #[tracing::instrument(skip_all, fields(first = %new.first, next = ?new.next()))] fn update(&self, new: storage::BlockStoreState) { self.0.send_if_modified(|p| { if &new == p { @@ -139,10 +133,11 @@ impl PersistedBlockState { }); } - /// Checks if the given certificate is exactly the next one that should - /// be persisted. + /// Checks if the given certificate should be eventually persisted. + /// Only certificates block store state is a range of blocks for which we already have + /// certificates and we need certs only for the later ones. fn should_be_persisted(&self, cert: &validator::CommitQC) -> bool { - self.0.borrow().next() == cert.header().number + self.0.borrow().next() <= cert.header().number } /// Appends the `cert` to `persisted` range. @@ -152,7 +147,7 @@ impl PersistedBlockState { if p.next() != cert.header().number { return false; } - p.last = Some(cert); + p.last = Some(storage::Last::Final(cert)); true }); } @@ -163,7 +158,6 @@ impl StoreRunner { let StoreRunner { pool, blocks_persisted, - batches_persisted, mut block_certificates, } = self; @@ -176,13 +170,13 @@ impl StoreRunner { ) -> ctx::Result<()> { const POLL_INTERVAL: time::Duration = time::Duration::seconds(1); - let range = pool + let state = pool .connection(ctx) .await? - .block_certificates_range(ctx) + .block_store_state(ctx) .await - .wrap("block_certificates_range()")?; - blocks_persisted.update(range); + .wrap("block_store_state()")?; + blocks_persisted.update(state); ctx.sleep(POLL_INTERVAL).await?; Ok(()) @@ -195,60 +189,6 @@ impl StoreRunner { } }); - #[tracing::instrument(skip_all, fields(l1_batch = %next_batch_number))] - async fn gossip_sync_batches_iteration( - ctx: &ctx::Ctx, - pool: &ConnectionPool, - next_batch_number: &mut BatchNumber, - batches_persisted: &Sender, - ) -> ctx::Result<()> { - const POLL_INTERVAL: time::Duration = time::Duration::seconds(1); - - let mut conn = pool.connection(ctx).await?; - if let Some(last_batch_number) = conn - .get_last_batch_number(ctx) - .await - .wrap("last_batch_number()")? - { - if last_batch_number >= *next_batch_number { - let range = conn.batches_range(ctx).await.wrap("batches_range()")?; - *next_batch_number = last_batch_number.next(); - tracing::info_span!("batches_persisted_send").in_scope(|| { - batches_persisted.send_replace(range); - }); - } - } - ctx.sleep(POLL_INTERVAL).await?; - - Ok(()) - } - - // NOTE: Running this update loop will trigger the gossip of `SyncBatches` which is currently - // pointless as there is no proof and we have to ignore them. We can disable it, but bear in - // mind that any node which gossips the availability will cause pushes and pulls in the consensus. - s.spawn::<()>(async { - // Loop updating `batches_persisted` whenever a new L1 batch is available in the database. - // We have to do this because the L1 batch is produced as L2 blocks are executed, - // which can happen on a different machine or in a different process, so we can't rely on some - // DAL method updating this memory construct. However I'm not sure that `BatchStoreState` - // really has to contain the full blown last batch, or whether it could have for example - // just the number of it. We can't just use the `attester::BatchQC`, which would make it - // analogous to the `BlockStoreState`, because the `SyncBatch` mechanism is for catching - // up with L1 batches from peers _without_ the QC, based on L1 inclusion proofs instead. - // Nevertheless since the `SyncBatch` contains all transactions for all L2 blocks, - // we can try to make it less frequent by querying just the last batch number first. - let mut next_batch_number = { batches_persisted.borrow().next() }; - loop { - gossip_sync_batches_iteration( - ctx, - &pool, - &mut next_batch_number, - &batches_persisted, - ) - .await?; - } - }); - #[tracing::instrument(skip_all)] async fn insert_block_certificates_iteration( ctx: &ctx::Ctx, @@ -339,7 +279,7 @@ impl storage::PersistentBlockStore for Store { &self, ctx: &ctx::Ctx, number: validator::BlockNumber, - ) -> ctx::Result { + ) -> ctx::Result { Ok(self .conn(ctx) .await? @@ -348,6 +288,41 @@ impl storage::PersistentBlockStore for Store { .context("not found")?) } + async fn verify_pregenesis_block( + &self, + ctx: &ctx::Ctx, + block: &validator::PreGenesisBlock, + ) -> ctx::Result<()> { + // We simply ask the main node for the payload hash and compare it against the received + // payload. + let meta = match &self.client { + None => self + .conn(ctx) + .await? + .block_metadata(ctx, block.number) + .await? + .context("metadata not in storage")?, + Some(client) => { + let meta = ctx + .wait(client.block_metadata(L2BlockNumber( + block.number.0.try_into().context("overflow")?, + ))) + .await? + .context("block_metadata()")? + .context("metadata not available")?; + zksync_protobuf::serde::Deserialize { + deny_unknown_fields: false, + } + .proto_fmt(&meta.0) + .context("deserialize()")? + } + }; + if meta.payload_hash != block.payload.hash() { + return Err(anyhow::format_err!("payload hash mismatch").into()); + } + Ok(()) + } + /// If actions queue is set (and the block has not been stored yet), /// the block will be translated into a sequence of actions. /// The received actions should be fed @@ -356,19 +331,21 @@ impl storage::PersistentBlockStore for Store { /// `store_next_block()` call will wait synchronously for the L2 block. /// Once the L2 block is observed in storage, `store_next_block()` will store a cert for this /// L2 block. - async fn queue_next_block( - &self, - ctx: &ctx::Ctx, - block: validator::FinalBlock, - ) -> ctx::Result<()> { + async fn queue_next_block(&self, ctx: &ctx::Ctx, block: validator::Block) -> ctx::Result<()> { let mut payloads = sync::lock(ctx, &self.block_payloads).await?.into_async(); + let (p, j) = match &block { + validator::Block::Final(block) => (&block.payload, Some(&block.justification)), + validator::Block::PreGenesis(block) => (&block.payload, None), + }; if let Some(payloads) = &mut *payloads { payloads - .send(to_fetched_block(block.number(), &block.payload).context("to_fetched_block")?) + .send(to_fetched_block(block.number(), p).context("to_fetched_block")?) .await - .context("payload_queue.send()")?; + .context("payloads.send()")?; + } + if let Some(justification) = j { + self.block_certificates.send(justification.clone()); } - self.block_certificates.send(block.justification); Ok(()) } } @@ -455,43 +432,3 @@ impl PayloadManager for Store { Ok(()) } } - -#[async_trait::async_trait] -impl storage::PersistentBatchStore for Store { - /// Range of batches persisted in storage. - fn persisted(&self) -> sync::watch::Receiver { - self.batches_persisted.clone() - } - - /// Returns the batch with the given number. - async fn get_batch( - &self, - ctx: &ctx::Ctx, - number: attester::BatchNumber, - ) -> ctx::Result> { - self.conn(ctx) - .await? - .get_batch(ctx, number) - .await - .wrap("get_batch") - } - - /// Queue the batch to be persisted in storage. - /// - /// The caller [BatchStore] ensures that this is only called when the batch is the next expected one. - async fn queue_next_batch( - &self, - _ctx: &ctx::Ctx, - _batch: attester::SyncBatch, - ) -> ctx::Result<()> { - // Currently the gossiping of `SyncBatch` and the `BatchStoreState` is unconditionally started by the `Network::run_stream` in consensus, - // and as long as any node reports new batches available by updating the `PersistentBatchStore::persisted` here, the other nodes - // will start pulling the corresponding batches, which will end up being passed to this method. - // If we return an error here or panic, it will stop the whole consensus task tree due to the way scopes work, so instead just return immediately. - // In the future we have to validate the proof agains the L1 state root hash, which IIUC we can't do just yet. - - // Err(anyhow::format_err!("unimplemented: queue_next_batch should not be called until we have the stateless L1 batch story completed.").into()) - - Ok(()) - } -} diff --git a/core/node/consensus/src/storage/testonly.rs b/core/node/consensus/src/storage/testonly.rs index 5817e766c6b..2aed011d23c 100644 --- a/core/node/consensus/src/storage/testonly.rs +++ b/core/node/consensus/src/storage/testonly.rs @@ -7,8 +7,8 @@ use zksync_dal::CoreDal as _; use zksync_node_genesis::{insert_genesis_batch, mock_genesis_config, GenesisParams}; use zksync_node_test_utils::{recover, snapshot, Snapshot}; use zksync_types::{ - commitment::L1BatchWithMetadata, protocol_version::ProtocolSemanticVersion, - system_contracts::get_system_smart_contracts, L1BatchNumber, L2BlockNumber, ProtocolVersionId, + protocol_version::ProtocolSemanticVersion, system_contracts::get_system_smart_contracts, + L1BatchNumber, L2BlockNumber, ProtocolVersionId, }; use super::{Connection, ConnectionPool}; @@ -102,28 +102,6 @@ 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()")?; @@ -152,21 +130,32 @@ impl ConnectionPool { Self(pool) } - /// Waits for `want_last` block to have certificate then fetches all L2 blocks with certificates. - pub async fn wait_for_block_certificates( + /// Waits for `want_last` block then fetches all L2 blocks with certificates. + pub async fn wait_for_blocks( &self, ctx: &ctx::Ctx, want_last: validator::BlockNumber, - ) -> ctx::Result> { - self.wait_for_block_certificate(ctx, want_last).await?; + ) -> ctx::Result> { + const POLL_INTERVAL: time::Duration = time::Duration::milliseconds(100); + let state = loop { + let state = self + .connection(ctx) + .await + .wrap("connection()")? + .block_store_state(ctx) + .await + .wrap("block_store_state()")?; + tracing::info!("state.next() = {}", state.next()); + if state.next() > want_last { + break state; + } + ctx.sleep(POLL_INTERVAL).await?; + }; + + assert_eq!(want_last.next(), state.next()); let mut conn = self.connection(ctx).await.wrap("connection()")?; - let range = conn - .block_certificates_range(ctx) - .await - .wrap("certificates_range()")?; - assert_eq!(want_last.next(), range.next()); - let mut blocks: Vec = vec![]; - for i in range.first.0..range.next().0 { + let mut blocks: Vec = vec![]; + for i in state.first.0..state.next().0 { let i = validator::BlockNumber(i); let block = conn.block(ctx, i).await.context("block()")?.unwrap(); blocks.push(block); @@ -174,13 +163,13 @@ impl ConnectionPool { Ok(blocks) } - /// Same as `wait_for_certificates`, but additionally verifies all the blocks against genesis. - pub async fn wait_for_block_certificates_and_verify( + /// Same as `wait_for_blocks`, but additionally verifies all certificates. + pub async fn wait_for_blocks_and_verify_certs( &self, ctx: &ctx::Ctx, want_last: validator::BlockNumber, - ) -> ctx::Result> { - let blocks = self.wait_for_block_certificates(ctx, want_last).await?; + ) -> ctx::Result> { + let blocks = self.wait_for_blocks(ctx, want_last).await?; let cfg = self .connection(ctx) .await @@ -190,7 +179,9 @@ impl ConnectionPool { .wrap("genesis()")? .context("genesis is missing")?; for block in &blocks { - block.verify(&cfg.genesis).context(block.number())?; + if let validator::Block::Final(block) = block { + block.verify(&cfg.genesis).context(block.number())?; + } } Ok(blocks) } @@ -228,19 +219,11 @@ impl ConnectionPool { let registry = registry::Registry::new(cfg.genesis.clone(), self.clone()).await; for i in first.0..want_last.0 { let i = attester::BatchNumber(i); - let hash = conn - .batch_hash(ctx, i) - .await - .wrap("batch_hash()")? - .context("hash missing")?; let cert = conn .batch_certificate(ctx, i) .await .wrap("batch_certificate")? .context("cert missing")?; - if cert.message.hash != hash { - return Err(anyhow::format_err!("cert[{i:?}]: hash mismatch").into()); - } let committee = registry .attester_committee_for(ctx, registry_addr, i) .await @@ -255,28 +238,30 @@ impl ConnectionPool { pub async fn prune_batches( &self, ctx: &ctx::Ctx, - last_batch: L1BatchNumber, + last_batch: attester::BatchNumber, ) -> ctx::Result<()> { let mut conn = self.connection(ctx).await.context("connection()")?; - let (_, last_block) = ctx - .wait( - conn.0 - .blocks_dal() - .get_l2_block_range_of_l1_batch(last_batch), - ) - .await? - .context("get_l2_block_range_of_l1_batch()")? - .context("batch not found")?; - conn.0 - .pruning_dal() - .soft_prune_batches_range(last_batch, last_block) - .await - .context("soft_prune_batches_range()")?; - conn.0 - .pruning_dal() - .hard_prune_batches_range(last_batch, last_block) + let (_, last_block) = conn + .get_l2_block_range_of_l1_batch(ctx, last_batch) .await - .context("hard_prune_batches_range()")?; + .wrap("get_l2_block_range_of_l1_batch()")? + .context("batch not found")?; + let last_batch = L1BatchNumber(last_batch.0.try_into().context("oveflow")?); + let last_block = L2BlockNumber(last_block.0.try_into().context("oveflow")?); + ctx.wait( + conn.0 + .pruning_dal() + .soft_prune_batches_range(last_batch, last_block), + ) + .await? + .context("soft_prune_batches_range()")?; + ctx.wait( + conn.0 + .pruning_dal() + .hard_prune_batches_range(last_batch, last_block), + ) + .await? + .context("hard_prune_batches_range()")?; Ok(()) } } diff --git a/core/node/consensus/src/testonly.rs b/core/node/consensus/src/testonly.rs index 04a2dfbc083..4538337109a 100644 --- a/core/node/consensus/src/testonly.rs +++ b/core/node/consensus/src/testonly.rs @@ -16,10 +16,7 @@ use zksync_consensus_crypto::TextFmt as _; use zksync_consensus_network as network; use zksync_consensus_roles::{attester, validator, validator::testonly::Setup}; use zksync_dal::{CoreDal, DalError}; -use zksync_l1_contract_interface::i_executor::structures::StoredBatchInfo; -use zksync_metadata_calculator::{ - LazyAsyncTreeReader, MetadataCalculator, MetadataCalculatorConfig, -}; +use zksync_metadata_calculator::{MetadataCalculator, MetadataCalculatorConfig}; use zksync_node_api_server::web3::{state::InternalApiConfig, testonly::TestServerBuilder}; use zksync_node_genesis::GenesisParams; use zksync_node_sync::{ @@ -48,11 +45,7 @@ use zksync_types::{ }; use zksync_web3_decl::client::{Client, DynClient, L2}; -use crate::{ - batch::{L1BatchCommit, L1BatchWithWitness, LastBlockCommit}, - en, - storage::ConnectionPool, -}; +use crate::{en, storage::ConnectionPool}; /// Fake StateKeeper for tests. #[derive(Debug)] @@ -70,7 +63,6 @@ pub(super) struct StateKeeper { sync_state: SyncState, addr: sync::watch::Receiver>, pool: ConnectionPool, - tree_reader: LazyAsyncTreeReader, } #[derive(Clone)] @@ -78,6 +70,7 @@ pub(super) struct ConfigSet { net: network::Config, pub(super) config: config::ConsensusConfig, pub(super) secrets: config::ConsensusSecrets, + pub(super) enable_pregenesis: bool, } impl ConfigSet { @@ -87,11 +80,17 @@ impl ConfigSet { config: make_config(&net, None), secrets: make_secrets(&net, None), net, + enable_pregenesis: self.enable_pregenesis, } } } -pub(super) fn new_configs(rng: &mut impl Rng, setup: &Setup, seed_peers: usize) -> Vec { +pub(super) fn new_configs( + rng: &mut impl Rng, + setup: &Setup, + seed_peers: usize, + pregenesis: bool, +) -> Vec { let net_cfgs = network::testonly::new_configs(rng, setup, 0); let genesis_spec = config::GenesisSpec { chain_id: setup.genesis.chain_id.0.try_into().unwrap(), @@ -131,6 +130,7 @@ pub(super) fn new_configs(rng: &mut impl Rng, setup: &Setup, seed_peers: usize) config: make_config(&net, Some(genesis_spec.clone())), secrets: make_secrets(&net, setup.attester_keys.get(i).cloned()), net, + enable_pregenesis: pregenesis, }) .collect() } @@ -248,7 +248,6 @@ impl StateKeeper { let metadata_calculator = MetadataCalculator::new(config, None, pool.0.clone()) .await .context("MetadataCalculator::new()")?; - let tree_reader = metadata_calculator.tree_reader(); Ok(( Self { protocol_version, @@ -261,7 +260,6 @@ impl StateKeeper { sync_state: sync_state.clone(), addr: addr.subscribe(), pool: pool.clone(), - tree_reader, }, StateKeeperRunner { actions_queue, @@ -369,51 +367,14 @@ impl StateKeeper { } /// Batch of the `last_block`. - pub fn last_batch(&self) -> L1BatchNumber { - self.last_batch + pub fn last_batch(&self) -> attester::BatchNumber { + attester::BatchNumber(self.last_batch.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 + pub fn last_sealed_batch(&self) -> attester::BatchNumber { + attester::BatchNumber((self.last_batch.0 - (!self.batch_sealed) as u32).into()) } /// Connects to the json RPC endpoint exposed by the state keeper. @@ -473,6 +434,7 @@ impl StateKeeper { cfgs.config, cfgs.secrets, cfgs.net.build_version, + cfgs.enable_pregenesis, ) .await } diff --git a/core/node/consensus/src/tests/attestation.rs b/core/node/consensus/src/tests/attestation.rs index 35d849ae616..bd3886bd4c8 100644 --- a/core/node/consensus/src/tests/attestation.rs +++ b/core/node/consensus/src/tests/attestation.rs @@ -1,6 +1,6 @@ use anyhow::Context as _; use rand::Rng as _; -use test_casing::test_casing; +use test_casing::{test_casing, Product}; use tracing::Instrument as _; use zksync_concurrency::{ctx, error::Wrap, scope}; use zksync_consensus_roles::{ @@ -9,10 +9,10 @@ use zksync_consensus_roles::{ }; use zksync_dal::consensus_dal; use zksync_test_account::Account; -use zksync_types::{L1BatchNumber, ProtocolVersionId}; +use zksync_types::ProtocolVersionId; use zksync_web3_decl::namespaces::EnNamespaceClient as _; -use super::VERSIONS; +use super::{POLL_INTERVAL, PREGENESIS, VERSIONS}; use crate::{ mn::run_main_node, registry::{testonly, Registry}, @@ -34,13 +34,13 @@ async fn test_attestation_status_api(version: ProtocolVersionId) { s.spawn_bg(runner.run(ctx).instrument(tracing::info_span!("validator"))); // Setup nontrivial genesis. - while sk.last_sealed_batch() < L1BatchNumber(3) { + while sk.last_sealed_batch() < attester::BatchNumber(3) { sk.push_random_blocks(rng, account, 10).await; } let mut setup = SetupSpec::new(rng, 3); setup.first_block = sk.last_block(); let first_batch = sk.last_batch(); - let setup = Setup::from(setup); + let setup = Setup::from_spec(rng, setup); let mut conn = pool.connection(ctx).await.wrap("connection()")?; conn.try_update_global_config( ctx, @@ -54,7 +54,9 @@ async fn test_attestation_status_api(version: ProtocolVersionId) { .wrap("try_update_global_config()")?; // Make sure that the first_batch is actually sealed. sk.seal_batch().await; - pool.wait_for_batch(ctx, first_batch).await?; + pool.wait_for_batch_info(ctx, first_batch, POLL_INTERVAL) + .await + .wrap("wait_for_batch_info()")?; // Connect to API endpoint. let api = sk.connect(ctx).await?; @@ -84,11 +86,11 @@ async fn test_attestation_status_api(version: ProtocolVersionId) { { let mut conn = pool.connection(ctx).await?; let number = status.next_batch_to_attest; - let hash = conn.batch_hash(ctx, number).await?.unwrap(); + let info = conn.batch_info(ctx, number).await?.unwrap(); let gcfg = conn.global_config(ctx).await?.unwrap(); let m = attester::Batch { number, - hash, + hash: consensus_dal::batch_hash(&info), genesis: gcfg.genesis.hash(), }; let mut sigs = attester::MultiSig::default(); @@ -124,9 +126,9 @@ async fn test_attestation_status_api(version: ProtocolVersionId) { // Test running a couple of attesters (which are also validators). // Main node is expected to collect all certificates. // External nodes are expected to just vote for the batch. -#[test_casing(2, VERSIONS)] +#[test_casing(4, Product((VERSIONS,PREGENESIS)))] #[tokio::test] -async fn test_multiple_attesters(version: ProtocolVersionId) { +async fn test_multiple_attesters(version: ProtocolVersionId, pregenesis: bool) { const NODES: usize = 4; zksync_concurrency::testonly::abort_on_panic(); @@ -135,7 +137,7 @@ async fn test_multiple_attesters(version: ProtocolVersionId) { let account = &mut Account::random(); let to_fund = &[account.address]; let setup = Setup::new(rng, 4); - let mut cfgs = new_configs(rng, &setup, NODES); + let mut cfgs = new_configs(rng, &setup, NODES, pregenesis); scope::run!(ctx, |ctx, s| async { let validator_pool = ConnectionPool::test(false, version).await; let (mut validator, runner) = StateKeeper::new(ctx, validator_pool.clone()).await?; diff --git a/core/node/consensus/src/tests/batch.rs b/core/node/consensus/src/tests/batch.rs deleted file mode 100644 index f0cae7f2c02..00000000000 --- a/core/node/consensus/src/tests/batch.rs +++ /dev/null @@ -1,124 +0,0 @@ -use test_casing::{test_casing, Product}; -use zksync_concurrency::{ctx, scope}; -use zksync_consensus_roles::validator; -use zksync_test_account::Account; -use zksync_types::{L1BatchNumber, ProtocolVersionId}; - -use super::{FROM_SNAPSHOT, VERSIONS}; -use crate::{storage::ConnectionPool, testonly}; - -#[test_casing(4, Product((FROM_SNAPSHOT,VERSIONS)))] -#[tokio::test] -async fn test_connection_get_batch(from_snapshot: bool, version: ProtocolVersionId) { - zksync_concurrency::testonly::abort_on_panic(); - let ctx = &ctx::test_root(&ctx::RealClock); - let rng = &mut ctx.rng(); - let pool = ConnectionPool::test(from_snapshot, version).await; - let account = &mut Account::random(); - - // Fill storage with unsigned L2 blocks and L1 batches in a way that the - // last L1 batch is guaranteed to have some L2 blocks executed in it. - scope::run!(ctx, |ctx, s| async { - // Start state keeper. - let (mut sk, runner) = testonly::StateKeeper::new(ctx, pool.clone()).await?; - s.spawn_bg(runner.run(ctx)); - - for _ in 0..3 { - for _ in 0..2 { - sk.push_random_block(rng, account).await; - } - sk.seal_batch().await; - } - sk.push_random_block(rng, account).await; - - pool.wait_for_payload(ctx, sk.last_block()).await?; - - Ok(()) - }) - .await - .unwrap(); - - // Now we can try to retrieve the batch. - scope::run!(ctx, |ctx, _s| async { - let mut conn = pool.connection(ctx).await?; - let batches = conn.batches_range(ctx).await?; - let last = batches.last.expect("last is set"); - let (min, max) = conn - .get_l2_block_range_of_l1_batch(ctx, last) - .await? - .unwrap(); - - let last_batch = conn - .get_batch(ctx, last) - .await? - .expect("last batch can be retrieved"); - - assert_eq!( - last_batch.payloads.len(), - (max.0 - min.0) as usize, - "all block payloads present" - ); - - let first_payload = last_batch - .payloads - .first() - .expect("last batch has payloads"); - - let want_payload = conn.payload(ctx, min).await?.expect("payload is in the DB"); - let want_payload = want_payload.encode(); - - assert_eq!( - first_payload, &want_payload, - "first payload is the right number" - ); - - anyhow::Ok(()) - }) - .await - .unwrap(); -} - -/// Tests that generated L1 batch witnesses can be verified successfully. -/// TODO: add tests for verification failures. -#[test_casing(2, VERSIONS)] -#[tokio::test] -async fn test_batch_witness(version: ProtocolVersionId) { - zksync_concurrency::testonly::abort_on_panic(); - let ctx = &ctx::test_root(&ctx::RealClock); - let rng = &mut ctx.rng(); - let account = &mut Account::random(); - let to_fund = &[account.address]; - - scope::run!(ctx, |ctx, s| async { - let pool = ConnectionPool::from_genesis(version).await; - let (mut node, runner) = testonly::StateKeeper::new(ctx, pool.clone()).await?; - s.spawn_bg(runner.run_real(ctx, to_fund)); - - 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, account, 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/consensus/src/tests/mod.rs b/core/node/consensus/src/tests/mod.rs index 52abe3c810c..94fbcbb90d8 100644 --- a/core/node/consensus/src/tests/mod.rs +++ b/core/node/consensus/src/tests/mod.rs @@ -2,14 +2,14 @@ use anyhow::Context as _; use rand::Rng as _; use test_casing::{test_casing, Product}; use tracing::Instrument as _; -use zksync_concurrency::{ctx, error::Wrap as _, scope}; +use zksync_concurrency::{ctx, error::Wrap as _, scope, time}; use zksync_config::configs::consensus as config; use zksync_consensus_crypto::TextFmt as _; use zksync_consensus_roles::{ node, validator, validator::testonly::{Setup, SetupSpec}, }; -use zksync_consensus_storage::BlockStore; +use zksync_consensus_storage::{BlockStore, PersistentBlockStore}; use zksync_dal::consensus_dal; use zksync_test_account::Account; use zksync_types::ProtocolVersionId; @@ -21,10 +21,100 @@ use crate::{ }; mod attestation; -mod batch; const VERSIONS: [ProtocolVersionId; 2] = [ProtocolVersionId::latest(), ProtocolVersionId::next()]; const FROM_SNAPSHOT: [bool; 2] = [true, false]; +const PREGENESIS: [bool; 2] = [true, false]; +const POLL_INTERVAL: time::Duration = time::Duration::milliseconds(500); + +#[test_casing(2, VERSIONS)] +#[tokio::test] +async fn test_verify_pregenesis_block(version: ProtocolVersionId) { + zksync_concurrency::testonly::abort_on_panic(); + let ctx = &ctx::test_root(&ctx::AffineClock::new(10.)); + let rng = &mut ctx.rng(); + let account = &mut Account::random(); + let mut setup = SetupSpec::new(rng, 3); + setup.first_block = validator::BlockNumber(1000); + let setup = Setup::from_spec(rng, setup); + let cfg = consensus_dal::GlobalConfig { + genesis: setup.genesis.clone(), + registry_address: None, + seed_peers: [].into(), + }; + + scope::run!(ctx, |ctx, s| async { + tracing::info!("Start state keeper."); + let pool = ConnectionPool::test(/*from_snapshot=*/ false, version).await; + pool.connection(ctx) + .await + .wrap("connection()")? + .try_update_global_config(ctx, &cfg) + .await + .wrap("try_update_global_config()")?; + let (mut sk, runner) = testonly::StateKeeper::new(ctx, pool.clone()).await?; + s.spawn_bg(runner.run(ctx)); + + tracing::info!("Populate storage with a bunch of blocks."); + sk.push_random_blocks(rng, account, 5).await; + sk.seal_batch().await; + let blocks: Vec<_> = pool + .wait_for_blocks(ctx, sk.last_block()) + .await + .context("wait_for_blocks()")? + .into_iter() + .map(|b| match b { + validator::Block::PreGenesis(b) => b, + _ => panic!(), + }) + .collect(); + assert!(!blocks.is_empty()); + + tracing::info!("Create another store"); + let pool = ConnectionPool::test(/*from_snapshot=*/ false, version).await; + pool.connection(ctx) + .await + .wrap("connection()")? + .try_update_global_config(ctx, &cfg) + .await + .wrap("try_update_global_config()")?; + let (store, runner) = Store::new( + ctx, + pool.clone(), + None, + Some(sk.connect(ctx).await.unwrap()), + ) + .await + .unwrap(); + s.spawn_bg(runner.run(ctx)); + + tracing::info!("All the blocks from the main node should be valid."); + for b in &blocks { + store.verify_pregenesis_block(ctx, b).await.unwrap(); + } + tracing::info!("Malformed blocks should not be valid"); + for b in &blocks { + let mut p = consensus_dal::Payload::decode(&b.payload).unwrap(); + // Arbitrary small change. + p.timestamp = rng.gen(); + store + .verify_pregenesis_block( + ctx, + &validator::PreGenesisBlock { + number: b.number, + justification: b.justification.clone(), + payload: p.encode(), + }, + ) + .await + .unwrap_err(); + } + + Ok(()) + }) + .await + .unwrap(); +} #[test_casing(2, VERSIONS)] #[tokio::test] @@ -36,7 +126,7 @@ async fn test_validator_block_store(version: ProtocolVersionId) { let account = &mut Account::random(); // Fill storage with unsigned L2 blocks. - // Fetch a suffix of blocks that we will generate (fake) certs for. + // Fetch a suffix of blocks that we will generate certs for. let want = scope::run!(ctx, |ctx, s| async { // Start state keeper. let (mut sk, runner) = testonly::StateKeeper::new(ctx, pool.clone()).await?; @@ -44,8 +134,9 @@ async fn test_validator_block_store(version: ProtocolVersionId) { sk.push_random_blocks(rng, account, 10).await; pool.wait_for_payload(ctx, sk.last_block()).await?; let mut setup = SetupSpec::new(rng, 3); - setup.first_block = validator::BlockNumber(4); - let mut setup = Setup::from(setup); + setup.first_block = validator::BlockNumber(0); + setup.first_pregenesis_block = setup.first_block; + let mut setup = Setup::from_spec(rng, setup); let mut conn = pool.connection(ctx).await.wrap("connection()")?; conn.try_update_global_config( ctx, @@ -75,7 +166,7 @@ async fn test_validator_block_store(version: ProtocolVersionId) { // Insert blocks one by one and check the storage state. for (i, block) in want.iter().enumerate() { scope::run!(ctx, |ctx, s| async { - let (store, runner) = Store::new(ctx, pool.clone(), None).await.unwrap(); + let (store, runner) = Store::new(ctx, pool.clone(), None, None).await.unwrap(); s.spawn_bg(runner.run(ctx)); let (block_store, runner) = BlockStore::new(ctx, Box::new(store.clone())).await.unwrap(); @@ -85,10 +176,7 @@ async fn test_validator_block_store(version: ProtocolVersionId) { .wait_until_persisted(ctx, block.number()) .await .unwrap(); - let got = pool - .wait_for_block_certificates(ctx, block.number()) - .await - .unwrap(); + let got = pool.wait_for_blocks(ctx, block.number()).await.unwrap(); assert_eq!(want[..=i], got); Ok(()) }) @@ -100,14 +188,14 @@ async fn test_validator_block_store(version: ProtocolVersionId) { // In the current implementation, consensus certificates are created asynchronously // for the L2 blocks constructed by the StateKeeper. This means that consensus actor // is effectively just back filling the consensus certificates for the L2 blocks in storage. -#[test_casing(4, Product((FROM_SNAPSHOT,VERSIONS)))] +#[test_casing(8, Product((FROM_SNAPSHOT,VERSIONS,PREGENESIS)))] #[tokio::test] -async fn test_validator(from_snapshot: bool, version: ProtocolVersionId) { +async fn test_validator(from_snapshot: bool, version: ProtocolVersionId, pregenesis: bool) { zksync_concurrency::testonly::abort_on_panic(); let ctx = &ctx::test_root(&ctx::AffineClock::new(10.)); let rng = &mut ctx.rng(); let setup = Setup::new(rng, 1); - let cfg = testonly::new_configs(rng, &setup, 0)[0].clone(); + let cfg = testonly::new_configs(rng, &setup, 0, pregenesis)[0].clone(); let account = &mut Account::random(); scope::run!(ctx, |ctx, s| async { @@ -149,9 +237,9 @@ async fn test_validator(from_snapshot: bool, version: ProtocolVersionId) { tracing::info!("Verify all certificates"); pool - .wait_for_block_certificates_and_verify(ctx, sk.last_block()) + .wait_for_blocks_and_verify_certs(ctx, sk.last_block()) .await - .context("wait_for_block_certificates_and_verify()")?; + .context("wait_for_blocks_and_verify_certs()")?; Ok(()) }) .await @@ -164,14 +252,14 @@ async fn test_validator(from_snapshot: bool, version: ProtocolVersionId) { } // Test running a validator node and 2 full nodes recovered from different snapshots. -#[test_casing(2, VERSIONS)] +#[test_casing(4, Product((VERSIONS,PREGENESIS)))] #[tokio::test] -async fn test_nodes_from_various_snapshots(version: ProtocolVersionId) { +async fn test_nodes_from_various_snapshots(version: ProtocolVersionId, pregenesis: bool) { zksync_concurrency::testonly::abort_on_panic(); let ctx = &ctx::test_root(&ctx::AffineClock::new(10.)); let rng = &mut ctx.rng(); let setup = Setup::new(rng, 1); - let validator_cfg = testonly::new_configs(rng, &setup, 0)[0].clone(); + let validator_cfg = testonly::new_configs(rng, &setup, 0, pregenesis)[0].clone(); let account = &mut Account::random(); scope::run!(ctx, |ctx, s| async { @@ -226,15 +314,15 @@ async fn test_nodes_from_various_snapshots(version: ProtocolVersionId) { tracing::info!("produce more blocks and compare storages"); validator.push_random_blocks(rng, account, 5).await; let want = validator_pool - .wait_for_block_certificates_and_verify(ctx, validator.last_block()) + .wait_for_blocks_and_verify_certs(ctx, validator.last_block()) .await?; // node stores should be suffixes for validator store. for got in [ node_pool - .wait_for_block_certificates_and_verify(ctx, validator.last_block()) + .wait_for_blocks_and_verify_certs(ctx, validator.last_block()) .await?, node_pool2 - .wait_for_block_certificates_and_verify(ctx, validator.last_block()) + .wait_for_blocks_and_verify_certs(ctx, validator.last_block()) .await?, ] { assert_eq!(want[want.len() - got.len()..], got[..]); @@ -245,14 +333,14 @@ async fn test_nodes_from_various_snapshots(version: ProtocolVersionId) { .unwrap(); } -#[test_casing(4, Product((FROM_SNAPSHOT,VERSIONS)))] +#[test_casing(8, Product((FROM_SNAPSHOT,VERSIONS,PREGENESIS)))] #[tokio::test] -async fn test_config_change(from_snapshot: bool, version: ProtocolVersionId) { +async fn test_config_change(from_snapshot: bool, version: ProtocolVersionId, pregenesis: bool) { zksync_concurrency::testonly::abort_on_panic(); let ctx = &ctx::test_root(&ctx::AffineClock::new(10.)); let rng = &mut ctx.rng(); let setup = Setup::new(rng, 1); - let mut validator_cfg = testonly::new_configs(rng, &setup, 0)[0].clone(); + let mut validator_cfg = testonly::new_configs(rng, &setup, 0, pregenesis)[0].clone(); let node_cfg = validator_cfg.new_fullnode(rng); let account = &mut Account::random(); @@ -304,12 +392,12 @@ async fn test_config_change(from_snapshot: bool, version: ProtocolVersionId) { validator.push_random_blocks(rng, account, 5).await; let want_last = validator.last_block(); let want = validator_pool - .wait_for_block_certificates_and_verify(ctx, want_last) + .wait_for_blocks_and_verify_certs(ctx, want_last) .await?; assert_eq!( want, node_pool - .wait_for_block_certificates_and_verify(ctx, want_last) + .wait_for_blocks_and_verify_certs(ctx, want_last) .await? ); Ok(()) @@ -322,16 +410,16 @@ async fn test_config_change(from_snapshot: bool, version: ProtocolVersionId) { // Test running a validator node and a couple of full nodes. // Validator is producing signed blocks and fetchers are expected to fetch // them directly or indirectly. -#[test_casing(4, Product((FROM_SNAPSHOT,VERSIONS)))] +#[test_casing(8, Product((FROM_SNAPSHOT,VERSIONS,PREGENESIS)))] #[tokio::test] -async fn test_full_nodes(from_snapshot: bool, version: ProtocolVersionId) { +async fn test_full_nodes(from_snapshot: bool, version: ProtocolVersionId, pregenesis: bool) { const NODES: usize = 2; zksync_concurrency::testonly::abort_on_panic(); let ctx = &ctx::test_root(&ctx::AffineClock::new(10.)); let rng = &mut ctx.rng(); let setup = Setup::new(rng, 1); - let validator_cfg = testonly::new_configs(rng, &setup, 0)[0].clone(); + let validator_cfg = testonly::new_configs(rng, &setup, 0, pregenesis)[0].clone(); let account = &mut Account::random(); // topology: @@ -391,13 +479,15 @@ async fn test_full_nodes(from_snapshot: bool, version: ProtocolVersionId) { // Note that block from before and after genesis have to be fetched. validator.push_random_blocks(rng, account, 5).await; let want_last = validator.last_block(); + tracing::info!("Waiting for the validator to produce block {want_last}."); let want = validator_pool - .wait_for_block_certificates_and_verify(ctx, want_last) + .wait_for_blocks_and_verify_certs(ctx, want_last) .await?; + tracing::info!("Waiting for the nodes to fetch block {want_last}."); for pool in &node_pools { assert_eq!( want, - pool.wait_for_block_certificates_and_verify(ctx, want_last) + pool.wait_for_blocks_and_verify_certs(ctx, want_last) .await? ); } @@ -408,16 +498,16 @@ async fn test_full_nodes(from_snapshot: bool, version: ProtocolVersionId) { } // Test running external node (non-leader) validators. -#[test_casing(4, Product((FROM_SNAPSHOT,VERSIONS)))] +#[test_casing(8, Product((FROM_SNAPSHOT,VERSIONS,PREGENESIS)))] #[tokio::test] -async fn test_en_validators(from_snapshot: bool, version: ProtocolVersionId) { +async fn test_en_validators(from_snapshot: bool, version: ProtocolVersionId, pregenesis: bool) { const NODES: usize = 3; zksync_concurrency::testonly::abort_on_panic(); let ctx = &ctx::test_root(&ctx::AffineClock::new(10.)); let rng = &mut ctx.rng(); let setup = Setup::new(rng, NODES); - let cfgs = testonly::new_configs(rng, &setup, 1); + let cfgs = testonly::new_configs(rng, &setup, 1, pregenesis); let account = &mut Account::random(); // Run all nodes in parallel. @@ -475,12 +565,12 @@ async fn test_en_validators(from_snapshot: bool, version: ProtocolVersionId) { main_node.push_random_blocks(rng, account, 5).await; let want_last = main_node.last_block(); let want = main_node_pool - .wait_for_block_certificates_and_verify(ctx, want_last) + .wait_for_blocks_and_verify_certs(ctx, want_last) .await?; for pool in &ext_node_pools { assert_eq!( want, - pool.wait_for_block_certificates_and_verify(ctx, want_last) + pool.wait_for_blocks_and_verify_certs(ctx, want_last) .await? ); } @@ -491,14 +581,18 @@ async fn test_en_validators(from_snapshot: bool, version: ProtocolVersionId) { } // Test fetcher back filling missing certs. -#[test_casing(4, Product((FROM_SNAPSHOT,VERSIONS)))] +#[test_casing(8, Product((FROM_SNAPSHOT,VERSIONS,PREGENESIS)))] #[tokio::test] -async fn test_p2p_fetcher_backfill_certs(from_snapshot: bool, version: ProtocolVersionId) { +async fn test_p2p_fetcher_backfill_certs( + from_snapshot: bool, + version: ProtocolVersionId, + pregenesis: bool, +) { zksync_concurrency::testonly::abort_on_panic(); let ctx = &ctx::test_root(&ctx::AffineClock::new(10.)); let rng = &mut ctx.rng(); let setup = Setup::new(rng, 1); - let validator_cfg = testonly::new_configs(rng, &setup, 0)[0].clone(); + let validator_cfg = testonly::new_configs(rng, &setup, 0, pregenesis)[0].clone(); let node_cfg = validator_cfg.new_fullnode(rng); let account = &mut Account::random(); @@ -555,10 +649,10 @@ async fn test_p2p_fetcher_backfill_certs(from_snapshot: bool, version: ProtocolV s.spawn_bg(node.run_consensus(ctx, client.clone(), node_cfg)); validator.push_random_blocks(rng, account, 3).await; let want = validator_pool - .wait_for_block_certificates_and_verify(ctx, validator.last_block()) + .wait_for_blocks_and_verify_certs(ctx, validator.last_block()) .await?; let got = node_pool - .wait_for_block_certificates_and_verify(ctx, validator.last_block()) + .wait_for_blocks_and_verify_certs(ctx, validator.last_block()) .await?; assert_eq!(want, got); Ok(()) @@ -571,14 +665,14 @@ async fn test_p2p_fetcher_backfill_certs(from_snapshot: bool, version: ProtocolV .unwrap(); } -#[test_casing(2, VERSIONS)] +#[test_casing(4, Product((VERSIONS,PREGENESIS)))] #[tokio::test] -async fn test_with_pruning(version: ProtocolVersionId) { +async fn test_with_pruning(version: ProtocolVersionId, pregenesis: bool) { zksync_concurrency::testonly::abort_on_panic(); let ctx = &ctx::test_root(&ctx::RealClock); let rng = &mut ctx.rng(); let setup = Setup::new(rng, 1); - let validator_cfg = testonly::new_configs(rng, &setup, 0)[0].clone(); + let validator_cfg = testonly::new_configs(rng, &setup, 0, pregenesis)[0].clone(); let node_cfg = validator_cfg.new_fullnode(rng); let account = &mut Account::random(); @@ -642,27 +736,28 @@ async fn test_with_pruning(version: ProtocolVersionId) { validator.push_random_blocks(rng, account, 5).await; validator.seal_batch().await; validator_pool - .wait_for_batch(ctx, validator.last_sealed_batch()) - .await?; + .wait_for_batch_info(ctx, validator.last_sealed_batch(), POLL_INTERVAL) + .await + .wrap("wait_for_batch_info()")?; // The main node is not supposed to be pruned. In particular `ConsensusDal::attestation_status` // does not look for where the last prune happened at, and thus if we prune the block genesis // points at, we might never be able to start the Executor. tracing::info!("Wait until the external node has all the batches we want to prune"); node_pool - .wait_for_batch(ctx, to_prune.next()) + .wait_for_batch_info(ctx, to_prune.next(), POLL_INTERVAL) .await - .context("wait_for_batch()")?; + .wrap("wait_for_batch_info()")?; tracing::info!("Prune some blocks and sync more"); node_pool .prune_batches(ctx, to_prune) .await - .context("prune_batches")?; + .wrap("prune_batches")?; validator.push_random_blocks(rng, account, 5).await; node_pool - .wait_for_block_certificates(ctx, validator.last_block()) + .wait_for_blocks(ctx, validator.last_block()) .await - .context("wait_for_block_certificates()")?; + .wrap("wait_for_blocks()")?; Ok(()) }) .await diff --git a/prover/Cargo.lock b/prover/Cargo.lock index c085c1b5455..1d584a473d9 100644 --- a/prover/Cargo.lock +++ b/prover/Cargo.lock @@ -2393,6 +2393,20 @@ dependencies = [ "tracing", ] +[[package]] +name = "handlebars" +version = "3.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4498fc115fa7d34de968184e473529abb40eeb6be8bc5f7faba3d08c316cb3e3" +dependencies = [ + "log", + "pest", + "pest_derive", + "quick-error 2.0.1", + "serde", + "serde_json", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -4629,6 +4643,12 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quick-protobuf" version = "0.8.1" @@ -5213,7 +5233,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" dependencies = [ "fnv", - "quick-error", + "quick-error 1.2.3", "tempfile", "wait-timeout", ] @@ -7796,9 +7816,9 @@ dependencies = [ [[package]] name = "zksync_concurrency" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4724d51934e475c846ba9e6ed169e25587385188b928a9ecfbbf616092a1c17" +checksum = "035269d811b3770debca372141ab64cad067dce8e58cb39a48cb7617d30c626b" dependencies = [ "anyhow", "once_cell", @@ -7836,9 +7856,9 @@ dependencies = [ [[package]] name = "zksync_consensus_crypto" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7760e7a140f16f0435fbf2ad9a4b09feaad74568d05b553751d222f4803a42e" +checksum = "49e38d1b5ed28c66e785caff53ea4863375555d818aafa03290397192dd3e665" dependencies = [ "anyhow", "blst", @@ -7857,9 +7877,9 @@ dependencies = [ [[package]] name = "zksync_consensus_roles" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96f903187836210602beba27655e111e22efb229ef90bd2a95a3d6799b31685c" +checksum = "e49fbd4e69b276058f3dfc06cf6ada0e8caa6ed826e81289e4d596da95a0f17a" dependencies = [ "anyhow", "bit-vec", @@ -7879,9 +7899,9 @@ dependencies = [ [[package]] name = "zksync_consensus_storage" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff43cfd03ea205c763e74362dc6ec5a4d74b6b1baef0fb134dde92a8880397f7" +checksum = "b2b2aab4ed18b13cd584f4edcc2546c8da82f89ac62e525063e12935ff28c9be" dependencies = [ "anyhow", "async-trait", @@ -7899,9 +7919,9 @@ dependencies = [ [[package]] name = "zksync_consensus_utils" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1020308512c01ab80327fb874b5b61c6fd513a6b26c8a5fce3e077600da04e4b" +checksum = "10bac8f471b182d4fa3d40cf158aac3624fe636a1ff0b4cf3fe26a0e20c68a42" dependencies = [ "anyhow", "rand 0.8.5", @@ -7986,10 +8006,13 @@ dependencies = [ "tracing", "vise", "zksync_concurrency", + "zksync_consensus_crypto", "zksync_consensus_roles", "zksync_consensus_storage", + "zksync_consensus_utils", "zksync_contracts", "zksync_db_connection", + "zksync_l1_contract_interface", "zksync_protobuf", "zksync_protobuf_build", "zksync_system_constants", @@ -8098,6 +8121,21 @@ dependencies = [ "zkevm_circuits 0.150.5", ] +[[package]] +name = "zksync_l1_contract_interface" +version = "0.1.0" +dependencies = [ + "anyhow", + "hex", + "once_cell", + "sha2 0.10.8", + "sha3 0.10.8", + "zksync_kzg", + "zksync_prover_interface", + "zksync_solidity_vk_codegen", + "zksync_types", +] + [[package]] name = "zksync_mini_merkle_tree" version = "0.1.0" @@ -8209,9 +8247,9 @@ dependencies = [ [[package]] name = "zksync_protobuf" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2d9ce9b9697daae6023c8da5cfe8764690a9d9c91ff32b8e1e54a7c8301fb3" +checksum = "abd55c64f54cb10967a435422f66ff5880ae14a232b245517c7ce38da32e0cab" dependencies = [ "anyhow", "bit-vec", @@ -8230,9 +8268,9 @@ dependencies = [ [[package]] name = "zksync_protobuf_build" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "903c23a12e160a703f9b68d0dd961daa24156af912ca1bc9efb74969f3acc645" +checksum = "4121952bcaf711005dd554612fc6e2de9b30cb58088508df87f1d38046ce8ac8" dependencies = [ "anyhow", "heck 0.5.0", @@ -8472,6 +8510,23 @@ dependencies = [ "zksync_utils", ] +[[package]] +name = "zksync_solidity_vk_codegen" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b310ab8a21681270e73f177ddf7974cabb7a96f0624ab8b008fd6ee1f9b4f687" +dependencies = [ + "ethereum-types", + "franklin-crypto", + "handlebars", + "hex", + "paste", + "rescue_poseidon", + "serde", + "serde_derive", + "serde_json", +] + [[package]] name = "zksync_system_constants" version = "0.1.0" diff --git a/zk_toolbox/Cargo.lock b/zk_toolbox/Cargo.lock index 97d4d181c52..f6f8087c699 100644 --- a/zk_toolbox/Cargo.lock +++ b/zk_toolbox/Cargo.lock @@ -6780,9 +6780,9 @@ dependencies = [ [[package]] name = "zksync_concurrency" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4724d51934e475c846ba9e6ed169e25587385188b928a9ecfbbf616092a1c17" +checksum = "035269d811b3770debca372141ab64cad067dce8e58cb39a48cb7617d30c626b" dependencies = [ "anyhow", "once_cell", @@ -6818,9 +6818,9 @@ dependencies = [ [[package]] name = "zksync_consensus_crypto" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7760e7a140f16f0435fbf2ad9a4b09feaad74568d05b553751d222f4803a42e" +checksum = "49e38d1b5ed28c66e785caff53ea4863375555d818aafa03290397192dd3e665" dependencies = [ "anyhow", "blst", @@ -6839,9 +6839,9 @@ dependencies = [ [[package]] name = "zksync_consensus_roles" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96f903187836210602beba27655e111e22efb229ef90bd2a95a3d6799b31685c" +checksum = "e49fbd4e69b276058f3dfc06cf6ada0e8caa6ed826e81289e4d596da95a0f17a" dependencies = [ "anyhow", "bit-vec", @@ -6861,9 +6861,9 @@ dependencies = [ [[package]] name = "zksync_consensus_utils" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1020308512c01ab80327fb874b5b61c6fd513a6b26c8a5fce3e077600da04e4b" +checksum = "10bac8f471b182d4fa3d40cf158aac3624fe636a1ff0b4cf3fe26a0e20c68a42" dependencies = [ "anyhow", "rand", @@ -6912,9 +6912,9 @@ dependencies = [ [[package]] name = "zksync_protobuf" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2d9ce9b9697daae6023c8da5cfe8764690a9d9c91ff32b8e1e54a7c8301fb3" +checksum = "abd55c64f54cb10967a435422f66ff5880ae14a232b245517c7ce38da32e0cab" dependencies = [ "anyhow", "bit-vec", @@ -6933,9 +6933,9 @@ dependencies = [ [[package]] name = "zksync_protobuf_build" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "903c23a12e160a703f9b68d0dd961daa24156af912ca1bc9efb74969f3acc645" +checksum = "4121952bcaf711005dd554612fc6e2de9b30cb58088508df87f1d38046ce8ac8" dependencies = [ "anyhow", "heck", diff --git a/zk_toolbox/Cargo.toml b/zk_toolbox/Cargo.toml index d37c7e25677..cb442a6182e 100644 --- a/zk_toolbox/Cargo.toml +++ b/zk_toolbox/Cargo.toml @@ -32,11 +32,11 @@ git_version_macro = { path = "crates/git_version_macro" } zksync_config = { path = "../core/lib/config" } zksync_protobuf_config = { path = "../core/lib/protobuf_config" } zksync_basic_types = { path = "../core/lib/basic_types" } -zksync_consensus_roles = "=0.3.0" -zksync_consensus_crypto = "=0.3.0" -zksync_consensus_utils = "=0.3.0" -zksync_protobuf = "=0.3.0" -zksync_protobuf_build = "=0.3.0" +zksync_consensus_roles = "=0.5.0" +zksync_consensus_crypto = "=0.5.0" +zksync_consensus_utils = "=0.5.0" +zksync_protobuf = "=0.5.0" +zksync_protobuf_build = "=0.5.0" # External dependencies anyhow = "1.0.82" diff --git a/zk_toolbox/crates/zk_supervisor/src/commands/test/mod.rs b/zk_toolbox/crates/zk_supervisor/src/commands/test/mod.rs index 711a4bffae2..facd98850d4 100644 --- a/zk_toolbox/crates/zk_supervisor/src/commands/test/mod.rs +++ b/zk_toolbox/crates/zk_supervisor/src/commands/test/mod.rs @@ -30,7 +30,7 @@ mod wallet; pub enum TestCommands { #[clap(about = MSG_INTEGRATION_TESTS_ABOUT, alias = "i")] Integration(IntegrationArgs), - #[clap(about = "Run fees test", alias = "i")] + #[clap(about = "Run fees test", alias = "f")] Fees(FeesArgs), #[clap(about = MSG_REVERT_TEST_ABOUT, alias = "r")] Revert(RevertArgs),