From b97598afa769f3829eca73e7eeef0b52444625b9 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Mon, 29 Jul 2024 18:31:40 +0200 Subject: [PATCH 1/5] WIP --- ...6c5f4e49a7f0b4f3a5c74c2abd8daebc457dd.json | 22 ++++++ core/lib/dal/build.rs | 2 +- core/lib/dal/src/consensus/mod.rs | 34 ++++++++- core/lib/dal/src/consensus/proto/mod.proto | 7 ++ core/lib/dal/src/consensus/testonly.rs | 15 ++++ core/lib/dal/src/consensus/tests.rs | 6 +- core/lib/dal/src/consensus_dal.rs | 70 +++++++++++-------- core/lib/types/src/api/en.rs | 4 +- core/lib/web3_decl/src/namespaces/en.rs | 2 +- .../web3/backend_jsonrpsee/namespaces/en.rs | 2 +- .../node/api_server/src/web3/namespaces/en.rs | 27 +++---- core/node/consensus/src/testonly.rs | 6 +- core/node/consensus/src/tests.rs | 25 ++++--- core/node/node_sync/src/client.rs | 4 +- core/node/node_sync/src/testonly.rs | 2 +- 15 files changed, 155 insertions(+), 73 deletions(-) create mode 100644 core/lib/dal/.sqlx/query-751c8e5ed1fc211dbb4c7419a316c5f4e49a7f0b4f3a5c74c2abd8daebc457dd.json create mode 100644 core/lib/dal/src/consensus/testonly.rs diff --git a/core/lib/dal/.sqlx/query-751c8e5ed1fc211dbb4c7419a316c5f4e49a7f0b4f3a5c74c2abd8daebc457dd.json b/core/lib/dal/.sqlx/query-751c8e5ed1fc211dbb4c7419a316c5f4e49a7f0b4f3a5c74c2abd8daebc457dd.json new file mode 100644 index 000000000000..0993a0e216c3 --- /dev/null +++ b/core/lib/dal/.sqlx/query-751c8e5ed1fc211dbb4c7419a316c5f4e49a7f0b4f3a5c74c2abd8daebc457dd.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT l1_batch_number FROM miniblocks WHERE number = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "l1_batch_number", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [ + true + ] + }, + "hash": "751c8e5ed1fc211dbb4c7419a316c5f4e49a7f0b4f3a5c74c2abd8daebc457dd" +} diff --git a/core/lib/dal/build.rs b/core/lib/dal/build.rs index df238b456ba5..28fe14cbf10d 100644 --- a/core/lib/dal/build.rs +++ b/core/lib/dal/build.rs @@ -3,7 +3,7 @@ fn main() { zksync_protobuf_build::Config { input_root: "src/consensus/proto".into(), proto_root: "zksync/dal".into(), - dependencies: vec![], + dependencies: vec!["::zksync_consensus_roles::proto".parse().unwrap()], protobuf_crate: "::zksync_protobuf".parse().unwrap(), is_public: true, } diff --git a/core/lib/dal/src/consensus/mod.rs b/core/lib/dal/src/consensus/mod.rs index fac045ce2224..fbe638ed7e0b 100644 --- a/core/lib/dal/src/consensus/mod.rs +++ b/core/lib/dal/src/consensus/mod.rs @@ -2,10 +2,12 @@ pub mod proto; #[cfg(test)] mod tests; +#[cfg(test)] +mod testonly; use anyhow::{anyhow, Context as _}; -use zksync_consensus_roles::validator; -use zksync_protobuf::{required, ProtoFmt, ProtoRepr}; +use zksync_consensus_roles::{attester,validator}; +use zksync_protobuf::{read_required, required, ProtoFmt, ProtoRepr}; use zksync_types::{ abi, ethabi, fee::Fee, @@ -20,6 +22,34 @@ use zksync_utils::{h256_to_u256, u256_to_h256}; use crate::models::{parse_h160, parse_h256}; +/// Global attestation status served by +/// `attestationStatus` RPC. +#[derive(Debug,PartialEq,Clone)] +pub struct AttestationStatus { + pub genesis: validator::GenesisHash, + 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 { diff --git a/core/lib/dal/src/consensus/proto/mod.proto b/core/lib/dal/src/consensus/proto/mod.proto index a7b5ea344152..ea0c12f1b5f3 100644 --- a/core/lib/dal/src/consensus/proto/mod.proto +++ b/core/lib/dal/src/consensus/proto/mod.proto @@ -2,6 +2,8 @@ syntax = "proto3"; package zksync.dal; +import "zksync/roles/validator.proto"; + message Payload { // zksync-era ProtocolVersionId optional uint32 protocol_version = 9; // required; u16 @@ -114,3 +116,8 @@ message PaymasterParams { optional bytes paymaster_address = 1; // required; H160 optional bytes paymaster_input = 2; // required } + +message AttestationStatus { + optional roles.validator.GenesisHash genesis = 1; // required + optional uint64 next_batch_to_attest = 2; // required +} diff --git a/core/lib/dal/src/consensus/testonly.rs b/core/lib/dal/src/consensus/testonly.rs new file mode 100644 index 000000000000..c7773487503f --- /dev/null +++ b/core/lib/dal/src/consensus/testonly.rs @@ -0,0 +1,15 @@ +use super::AttestationStatus; +use rand::{ + distributions::{Distribution, Standard}, + Rng, +}; + +impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> AttestationStatus { + AttestationStatus { + genesis: rng.gen(), + next_batch_to_attest: rng.gen(), + } + } +} + diff --git a/core/lib/dal/src/consensus/tests.rs b/core/lib/dal/src/consensus/tests.rs index 4a69bebdc362..06a1ea0fe497 100644 --- a/core/lib/dal/src/consensus/tests.rs +++ b/core/lib/dal/src/consensus/tests.rs @@ -5,6 +5,7 @@ use zksync_concurrency::ctx; use zksync_protobuf::{ repr::{decode, encode}, testonly::test_encode, + testonly::test_encode_random, ProtoRepr, }; use zksync_test_account::Account; @@ -12,7 +13,7 @@ use zksync_types::{ web3::Bytes, Execute, ExecuteTransactionCommon, L1BatchNumber, ProtocolVersionId, Transaction, }; -use super::{proto, Payload}; +use super::{proto, Payload, AttestationStatus}; use crate::tests::mock_protocol_upgrade_transaction; fn execute(rng: &mut impl Rng) -> Execute { @@ -53,12 +54,13 @@ fn payload(rng: &mut impl Rng, protocol_version: ProtocolVersionId) -> Payload { last_in_batch: rng.gen(), } } - + /// Tests struct <-> proto struct conversions. #[test] fn test_encoding() { let ctx = &ctx::test_root(&ctx::RealClock); let rng = &mut ctx.rng(); + test_encode_random::(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.rs index 87285266d58e..184b5ba98e32 100644 --- a/core/lib/dal/src/consensus_dal.rs +++ b/core/lib/dal/src/consensus_dal.rs @@ -9,7 +9,7 @@ use zksync_db_connection::{ }; use zksync_types::L2BlockNumber; -pub use crate::consensus::Payload; +pub use crate::consensus::{AttestationStatus,Payload}; use crate::{Core, CoreDal}; /// Storage access methods for `zksync_core::consensus` module. @@ -458,38 +458,46 @@ impl ConsensusDal<'_, '_> { ))) } - /// Next batch that the attesters should vote for. + /// Number of L1 batch that the L2 block belongs to. + async fn batch_of_block(&mut self, block: validator::BlockNumber) -> anyhow::Result> { + let Some(row) = sqlx::query!( + "SELECT l1_batch_number FROM miniblocks WHERE number = $1", + i64::try_from(block.0).context("overflow")?, + ) + .instrument("batch_of_block") + .report_latency() + .fetch_optional(self.storage) + .await? else { return Ok(None) }; + let Some(batch) = row.l1_batch_number else { return Ok(None) }; + Ok(Some(attester::BatchNumber(batch.try_into().context("overflow")?))) + } + + /// Global attestation status. + /// Includes the next batch that the attesters should vote for. /// This is a main node only query. /// ENs should call the attestation_status RPC of the main node. - pub async fn next_batch_to_attest(&mut self) -> anyhow::Result { - // First batch that we don't have a certificate for. - if let Some(last) = self - .get_last_batch_certificate_number() - .await - .context("get_last_batch_certificate_number()")? - { - return Ok(last + 1); - } - // Otherwise start with the last sealed L1 batch. - // We don't want to backfill certificates for old batches. - // Note that there is a race condition in case the next - // batch is sealed before the certificate for the current - // last sealed batch is stored. This is only relevant - // for the first certificate though and anyway this is - // a test setup, so we are OK with that race condition. - if let Some(sealed) = self - .storage - .blocks_dal() - .get_sealed_l1_batch_number() - .await - .context("get_sealed_l1_batch_number()")? - { - return Ok(attester::BatchNumber(sealed.0.into())); - } - // Otherwise start with 0. - // Note that main node doesn't start from snapshot - // and doesn't have prunning enabled. - Ok(attester::BatchNumber(0)) + pub async fn attestation_status(&mut self) -> anyhow::Result> { + let Some(genesis) = self.genesis().await.context("genesis()")? else { + return Ok(None); + }; + let Some(next_batch_to_attest) = async { + // First batch that we don't have a certificate for. + if let Some(last) = self + .get_last_batch_certificate_number() + .await + .context("get_last_batch_certificate_number()")? + { + return Ok(Some(last + 1)); + } + // Otherwise start with the batch containing the first block of the fork. + self.batch_of_block(genesis.first_block).await.context("batch_of_block()") + }.await? else { + return Ok(None); + }; + Ok(Some(AttestationStatus { + genesis: genesis.hash(), + next_batch_to_attest, + })) } } diff --git a/core/lib/types/src/api/en.rs b/core/lib/types/src/api/en.rs index 75de25ad80b2..bf26caddd07b 100644 --- a/core/lib/types/src/api/en.rs +++ b/core/lib/types/src/api/en.rs @@ -50,6 +50,4 @@ pub struct ConsensusGenesis(pub serde_json::Value); /// AttestationStatus maintained by the main node. /// Used for testing L1 batch signing by consensus attesters. #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct AttestationStatus { - pub next_batch_to_attest: L1BatchNumber, -} +pub struct AttestationStatus(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 0a4c8acb4c60..7bb80bccd1c4 100644 --- a/core/lib/web3_decl/src/namespaces/en.rs +++ b/core/lib/web3_decl/src/namespaces/en.rs @@ -40,7 +40,7 @@ pub trait EnNamespace { /// This is a temporary RPC used for testing L1 batch signing /// by consensus attesters. #[method(name = "attestationStatus")] - async fn attestation_status(&self) -> RpcResult; + async fn attestation_status(&self) -> RpcResult>; /// Get tokens that are white-listed and it can be used by paymasters. #[method(name = "whitelistedTokensForAA")] 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 625d774465e5..c3e116d39928 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 @@ -25,7 +25,7 @@ impl EnNamespaceServer for EnNamespace { .map_err(|err| self.current_method().map_err(err)) } - async fn attestation_status(&self) -> RpcResult { + async fn attestation_status(&self) -> RpcResult> { self.attestation_status_impl() .await .map_err(|err| self.current_method().map_err(err)) diff --git a/core/node/api_server/src/web3/namespaces/en.rs b/core/node/api_server/src/web3/namespaces/en.rs index 5f635b527b9d..0caaaecc7961 100644 --- a/core/node/api_server/src/web3/namespaces/en.rs +++ b/core/node/api_server/src/web3/namespaces/en.rs @@ -1,6 +1,5 @@ use anyhow::Context as _; use zksync_config::{configs::EcosystemContracts, GenesisConfig}; -use zksync_consensus_roles::attester; use zksync_dal::{CoreDal, DalError}; use zksync_types::{ api::en, protocol_version::ProtocolSemanticVersion, tokens::TokenInfo, Address, L1BatchNumber, @@ -17,12 +16,6 @@ pub(crate) struct EnNamespace { state: RpcState, } -fn to_l1_batch_number(n: attester::BatchNumber) -> anyhow::Result { - Ok(L1BatchNumber( - n.0.try_into().context("L1BatchNumber overflow")?, - )) -} - impl EnNamespace { pub fn new(state: RpcState) -> Self { Self { state } @@ -44,18 +37,14 @@ impl EnNamespace { } #[tracing::instrument(skip(self))] - pub async fn attestation_status_impl(&self) -> Result { - Ok(en::AttestationStatus { - next_batch_to_attest: to_l1_batch_number( - self.state - .acquire_connection() - .await? - .consensus_dal() - .next_batch_to_attest() - .await - .context("next_batch_to_attest()")?, - )?, - }) + pub async fn attestation_status_impl(&self) -> Result, Web3Error> { + let status = self.state + .acquire_connection().await? + .consensus_dal().attestation_status().await?; + + Ok(status.map(|s|en::AttestationStatus( + zksync_protobuf::serde::serialize(&s, serde_json::value::Serializer).unwrap(), + ))) } pub(crate) fn current_method(&self) -> &MethodTracer { diff --git a/core/node/consensus/src/testonly.rs b/core/node/consensus/src/testonly.rs index a2009d14dece..b4a2cff7e987 100644 --- a/core/node/consensus/src/testonly.rs +++ b/core/node/consensus/src/testonly.rs @@ -14,7 +14,7 @@ use zksync_config::{ }; use zksync_consensus_crypto::TextFmt as _; use zksync_consensus_network as network; -use zksync_consensus_roles::validator; +use zksync_consensus_roles::{attester,validator}; use zksync_dal::{CoreDal, DalError}; use zksync_l1_contract_interface::i_executor::structures::StoredBatchInfo; use zksync_metadata_calculator::{ @@ -53,6 +53,10 @@ use crate::{ storage::ConnectionPool, }; +pub fn cast_batch(n: attester::BatchNumber) -> anyhow::Result { + Ok(L1BatchNumber(n.0.try_into().context("overflow")?)) +} + /// Fake StateKeeper for tests. pub(super) struct StateKeeper { protocol_version: ProtocolVersionId, diff --git a/core/node/consensus/src/tests.rs b/core/node/consensus/src/tests.rs index 9890165ad81f..887b76734246 100644 --- a/core/node/consensus/src/tests.rs +++ b/core/node/consensus/src/tests.rs @@ -9,6 +9,7 @@ use zksync_consensus_roles::{ attester, validator, validator::testonly::{Setup, SetupSpec}, }; +use zksync_dal::consensus_dal::AttestationStatus; use zksync_consensus_storage::BlockStore; use zksync_node_sync::MainNodeClient; use zksync_types::{L1BatchNumber, ProtocolVersionId}; @@ -681,21 +682,30 @@ async fn test_attestation_status_api(version: ProtocolVersionId) { testonly::StateKeeper::new(ctx, validator_pool.clone()).await?; s.spawn_bg(runner.run(ctx).instrument(tracing::info_span!("validator"))); + // TODO: actually spawn the server which will initialize genesis. + // API server needs at least 1 L1 batch to start. validator.seal_batch().await; let api = validator.connect(ctx).await?; + let fetch_status = || async { + let s = api.fetch_attestation_status().await?.context("no status available")?; + let s : AttestationStatus = zksync_protobuf::serde::deserialize(&s.0).context("deserialize()")?; + anyhow::Ok(s) + }; + // If the main node has no L1 batch certificates, - // the first one to sign should be `last_sealed_batch`. + // then the first one to sign should be the batch with the `genesis.first_block`. + // TODO: validator_pool .wait_for_batch(ctx, validator.last_sealed_batch()) .await?; - let status = api.fetch_attestation_status().await?; - assert_eq!(status.next_batch_to_attest, validator.last_sealed_batch()); + let status = fetch_status().await?; + assert_eq!(status.next_batch_to_attest, attester::BatchNumber(validator.last_sealed_batch().0.into())); // Insert a cert, then check again. validator_pool - .wait_for_batch(ctx, status.next_batch_to_attest) + .wait_for_batch(ctx, testonly::cast_batch(status.next_batch_to_attest)?) .await?; { let mut conn = validator_pool.connection(ctx).await?; @@ -709,11 +719,8 @@ async fn test_attestation_status_api(version: ProtocolVersionId) { .await .context("insert_batch_certificate()")?; } - let want = status.next_batch_to_attest + 1; - let got = api - .fetch_attestation_status() - .await - .context("fetch_attestation_status()")?; + let want = status.next_batch_to_attest.next(); + let got = fetch_status().await?; assert_eq!(want, got.next_batch_to_attest); Ok(()) diff --git a/core/node/node_sync/src/client.rs b/core/node/node_sync/src/client.rs index c4aaa383bb0c..fb77617282e8 100644 --- a/core/node/node_sync/src/client.rs +++ b/core/node/node_sync/src/client.rs @@ -46,7 +46,7 @@ pub trait MainNodeClient: 'static + Send + Sync + fmt::Debug { async fn fetch_genesis_config(&self) -> EnrichedClientResult; - async fn fetch_attestation_status(&self) -> EnrichedClientResult; + async fn fetch_attestation_status(&self) -> EnrichedClientResult>; } #[async_trait] @@ -139,7 +139,7 @@ impl MainNodeClient for Box> { .await } - async fn fetch_attestation_status(&self) -> EnrichedClientResult { + async fn fetch_attestation_status(&self) -> EnrichedClientResult> { self.attestation_status() .rpc_context("attestation_status") .await diff --git a/core/node/node_sync/src/testonly.rs b/core/node/node_sync/src/testonly.rs index 677f548c6281..1cf3d1fe1e71 100644 --- a/core/node/node_sync/src/testonly.rs +++ b/core/node/node_sync/src/testonly.rs @@ -77,7 +77,7 @@ impl MainNodeClient for MockMainNodeClient { unimplemented!() } - async fn fetch_attestation_status(&self) -> EnrichedClientResult { + async fn fetch_attestation_status(&self) -> EnrichedClientResult> { unimplemented!() } From d61d98f9e799621234e9fb5a49e694fd7e22682e Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Tue, 30 Jul 2024 11:11:33 +0200 Subject: [PATCH 2/5] stable endpoint --- core/lib/dal/src/consensus/mod.rs | 10 ++-- core/lib/dal/src/consensus/testonly.rs | 4 +- core/lib/dal/src/consensus/tests.rs | 7 +-- core/lib/dal/src/consensus_dal.rs | 39 +++++++++--- .../node/api_server/src/web3/namespaces/en.rs | 22 ++++--- core/node/consensus/src/testonly.rs | 11 ++-- core/node/consensus/src/tests.rs | 60 ++++++++++++------- core/node/node_sync/src/client.rs | 7 ++- core/node/node_sync/src/testonly.rs | 4 +- 9 files changed, 106 insertions(+), 58 deletions(-) diff --git a/core/lib/dal/src/consensus/mod.rs b/core/lib/dal/src/consensus/mod.rs index fbe638ed7e0b..658da6c76821 100644 --- a/core/lib/dal/src/consensus/mod.rs +++ b/core/lib/dal/src/consensus/mod.rs @@ -1,12 +1,12 @@ pub mod proto; -#[cfg(test)] -mod tests; #[cfg(test)] mod testonly; +#[cfg(test)] +mod tests; use anyhow::{anyhow, Context as _}; -use zksync_consensus_roles::{attester,validator}; +use zksync_consensus_roles::{attester, validator}; use zksync_protobuf::{read_required, required, ProtoFmt, ProtoRepr}; use zksync_types::{ abi, ethabi, @@ -24,7 +24,7 @@ use crate::models::{parse_h160, parse_h256}; /// Global attestation status served by /// `attestationStatus` RPC. -#[derive(Debug,PartialEq,Clone)] +#[derive(Debug, PartialEq, Clone)] pub struct AttestationStatus { pub genesis: validator::GenesisHash, pub next_batch_to_attest: attester::BatchNumber, @@ -32,7 +32,7 @@ pub struct AttestationStatus { impl ProtoFmt for AttestationStatus { type Proto = proto::AttestationStatus; - + fn read(r: &Self::Proto) -> anyhow::Result { Ok(Self { genesis: read_required(&r.genesis).context("genesis")?, diff --git a/core/lib/dal/src/consensus/testonly.rs b/core/lib/dal/src/consensus/testonly.rs index c7773487503f..904a4c563d2a 100644 --- a/core/lib/dal/src/consensus/testonly.rs +++ b/core/lib/dal/src/consensus/testonly.rs @@ -1,9 +1,10 @@ -use super::AttestationStatus; use rand::{ distributions::{Distribution, Standard}, Rng, }; +use super::AttestationStatus; + impl Distribution for Standard { fn sample(&self, rng: &mut R) -> AttestationStatus { AttestationStatus { @@ -12,4 +13,3 @@ impl Distribution for Standard { } } } - diff --git a/core/lib/dal/src/consensus/tests.rs b/core/lib/dal/src/consensus/tests.rs index 06a1ea0fe497..f21d09290a2f 100644 --- a/core/lib/dal/src/consensus/tests.rs +++ b/core/lib/dal/src/consensus/tests.rs @@ -4,8 +4,7 @@ use rand::Rng; use zksync_concurrency::ctx; use zksync_protobuf::{ repr::{decode, encode}, - testonly::test_encode, - testonly::test_encode_random, + testonly::{test_encode, test_encode_random}, ProtoRepr, }; use zksync_test_account::Account; @@ -13,7 +12,7 @@ use zksync_types::{ web3::Bytes, Execute, ExecuteTransactionCommon, L1BatchNumber, ProtocolVersionId, Transaction, }; -use super::{proto, Payload, AttestationStatus}; +use super::{proto, AttestationStatus, Payload}; use crate::tests::mock_protocol_upgrade_transaction; fn execute(rng: &mut impl Rng) -> Execute { @@ -54,7 +53,7 @@ fn payload(rng: &mut impl Rng, protocol_version: ProtocolVersionId) -> Payload { last_in_batch: rng.gen(), } } - + /// Tests struct <-> proto struct conversions. #[test] fn test_encoding() { diff --git a/core/lib/dal/src/consensus_dal.rs b/core/lib/dal/src/consensus_dal.rs index 184b5ba98e32..bbf6183e8856 100644 --- a/core/lib/dal/src/consensus_dal.rs +++ b/core/lib/dal/src/consensus_dal.rs @@ -9,7 +9,7 @@ use zksync_db_connection::{ }; use zksync_types::L2BlockNumber; -pub use crate::consensus::{AttestationStatus,Payload}; +pub use crate::consensus::{AttestationStatus, Payload}; use crate::{Core, CoreDal}; /// Storage access methods for `zksync_core::consensus` module. @@ -459,17 +459,34 @@ impl ConsensusDal<'_, '_> { } /// Number of L1 batch that the L2 block belongs to. - async fn batch_of_block(&mut self, block: validator::BlockNumber) -> anyhow::Result> { + async fn batch_of_block( + &mut self, + block: validator::BlockNumber, + ) -> anyhow::Result> { let Some(row) = sqlx::query!( - "SELECT l1_batch_number FROM miniblocks WHERE number = $1", + r#" + SELECT + l1_batch_number + FROM + miniblocks + WHERE + number = $1 + "#, i64::try_from(block.0).context("overflow")?, ) .instrument("batch_of_block") .report_latency() .fetch_optional(self.storage) - .await? else { return Ok(None) }; - let Some(batch) = row.l1_batch_number else { return Ok(None) }; - Ok(Some(attester::BatchNumber(batch.try_into().context("overflow")?))) + .await? + else { + return Ok(None); + }; + let Some(batch) = row.l1_batch_number else { + return Ok(None); + }; + Ok(Some(attester::BatchNumber( + batch.try_into().context("overflow")?, + ))) } /// Global attestation status. @@ -489,9 +506,13 @@ impl ConsensusDal<'_, '_> { { return Ok(Some(last + 1)); } - // Otherwise start with the batch containing the first block of the fork. - self.batch_of_block(genesis.first_block).await.context("batch_of_block()") - }.await? else { + // Otherwise start with the batch containing the first block of the fork. + self.batch_of_block(genesis.first_block) + .await + .context("batch_of_block()") + } + .await? + else { return Ok(None); }; Ok(Some(AttestationStatus { diff --git a/core/node/api_server/src/web3/namespaces/en.rs b/core/node/api_server/src/web3/namespaces/en.rs index 0caaaecc7961..d3c14bb5f3e6 100644 --- a/core/node/api_server/src/web3/namespaces/en.rs +++ b/core/node/api_server/src/web3/namespaces/en.rs @@ -37,14 +37,22 @@ impl EnNamespace { } #[tracing::instrument(skip(self))] - pub async fn attestation_status_impl(&self) -> Result, Web3Error> { - let status = self.state - .acquire_connection().await? - .consensus_dal().attestation_status().await?; + pub async fn attestation_status_impl( + &self, + ) -> Result, Web3Error> { + let status = self + .state + .acquire_connection() + .await? + .consensus_dal() + .attestation_status() + .await?; - Ok(status.map(|s|en::AttestationStatus( - zksync_protobuf::serde::serialize(&s, serde_json::value::Serializer).unwrap(), - ))) + Ok(status.map(|s| { + en::AttestationStatus( + zksync_protobuf::serde::serialize(&s, serde_json::value::Serializer).unwrap(), + ) + })) } pub(crate) fn current_method(&self) -> &MethodTracer { diff --git a/core/node/consensus/src/testonly.rs b/core/node/consensus/src/testonly.rs index b4a2cff7e987..8ed9b933d164 100644 --- a/core/node/consensus/src/testonly.rs +++ b/core/node/consensus/src/testonly.rs @@ -14,7 +14,7 @@ use zksync_config::{ }; use zksync_consensus_crypto::TextFmt as _; use zksync_consensus_network as network; -use zksync_consensus_roles::{attester,validator}; +use zksync_consensus_roles::validator; use zksync_dal::{CoreDal, DalError}; use zksync_l1_contract_interface::i_executor::structures::StoredBatchInfo; use zksync_metadata_calculator::{ @@ -53,10 +53,6 @@ use crate::{ storage::ConnectionPool, }; -pub fn cast_batch(n: attester::BatchNumber) -> anyhow::Result { - Ok(L1BatchNumber(n.0.try_into().context("overflow")?)) -} - /// Fake StateKeeper for tests. pub(super) struct StateKeeper { protocol_version: ProtocolVersionId, @@ -306,6 +302,11 @@ impl StateKeeper { validator::BlockNumber(self.last_block.0.into()) } + /// Batch of the `last_block`. + pub fn last_batch(&self) -> L1BatchNumber { + self.last_batch + } + /// Last L1 batch that has been sealed and will have /// metadata computed eventually. pub fn last_sealed_batch(&self) -> L1BatchNumber { diff --git a/core/node/consensus/src/tests.rs b/core/node/consensus/src/tests.rs index 887b76734246..27c3a7175c7a 100644 --- a/core/node/consensus/src/tests.rs +++ b/core/node/consensus/src/tests.rs @@ -9,8 +9,8 @@ use zksync_consensus_roles::{ attester, validator, validator::testonly::{Setup, SetupSpec}, }; -use zksync_dal::consensus_dal::AttestationStatus; use zksync_consensus_storage::BlockStore; +use zksync_dal::consensus_dal::AttestationStatus; use zksync_node_sync::MainNodeClient; use zksync_types::{L1BatchNumber, ProtocolVersionId}; @@ -676,40 +676,54 @@ async fn test_centralized_fetcher(from_snapshot: bool, version: ProtocolVersionI async fn test_attestation_status_api(version: ProtocolVersionId) { zksync_concurrency::testonly::abort_on_panic(); let ctx = &ctx::test_root(&ctx::RealClock); + let rng = &mut ctx.rng(); + scope::run!(ctx, |ctx, s| async { - let validator_pool = ConnectionPool::test(false, version).await; - let (mut validator, runner) = - testonly::StateKeeper::new(ctx, validator_pool.clone()).await?; + let pool = ConnectionPool::test(false, version).await; + let (mut sk, runner) = testonly::StateKeeper::new(ctx, pool.clone()).await?; s.spawn_bg(runner.run(ctx).instrument(tracing::info_span!("validator"))); - // TODO: actually spawn the server which will initialize genesis. + // Setup nontrivial genesis. + while sk.last_sealed_batch() < L1BatchNumber(3) { + sk.push_random_blocks(rng, 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 mut conn = pool.connection(ctx).await.wrap("connection()")?; + conn.try_update_genesis(ctx, &setup.genesis) + .await + .wrap("try_update_genesis()")?; + // Make sure that the first_batch is actually sealed. + sk.seal_batch().await; + pool.wait_for_batch(ctx, first_batch).await?; - // API server needs at least 1 L1 batch to start. - validator.seal_batch().await; - let api = validator.connect(ctx).await?; + // Connect to API endpoint. + let api = sk.connect(ctx).await?; let fetch_status = || async { - let s = api.fetch_attestation_status().await?.context("no status available")?; - let s : AttestationStatus = zksync_protobuf::serde::deserialize(&s.0).context("deserialize()")?; - anyhow::Ok(s) + let s = api + .fetch_attestation_status() + .await? + .context("no attestation_status")?; + let s: AttestationStatus = + zksync_protobuf::serde::deserialize(&s.0).context("deserialize()")?; + anyhow::ensure!(s.genesis == setup.genesis.hash(), "genesis hash mismatch"); + Ok(s) }; - // If the main node has no L1 batch certificates, // then the first one to sign should be the batch with the `genesis.first_block`. - // TODO: - validator_pool - .wait_for_batch(ctx, validator.last_sealed_batch()) - .await?; let status = fetch_status().await?; - assert_eq!(status.next_batch_to_attest, attester::BatchNumber(validator.last_sealed_batch().0.into())); + assert_eq!( + status.next_batch_to_attest, + attester::BatchNumber(first_batch.0.into()) + ); - // Insert a cert, then check again. - validator_pool - .wait_for_batch(ctx, testonly::cast_batch(status.next_batch_to_attest)?) - .await?; + // Insert a (fake) cert, then check again. { - let mut conn = validator_pool.connection(ctx).await?; - let number = attester::BatchNumber(status.next_batch_to_attest.0.into()); + let mut conn = pool.connection(ctx).await?; + let number = status.next_batch_to_attest; let hash = conn.batch_hash(ctx, number).await?.unwrap(); let cert = attester::BatchQC { signatures: attester::MultiSig::default(), diff --git a/core/node/node_sync/src/client.rs b/core/node/node_sync/src/client.rs index fb77617282e8..d064803eab59 100644 --- a/core/node/node_sync/src/client.rs +++ b/core/node/node_sync/src/client.rs @@ -46,7 +46,8 @@ pub trait MainNodeClient: 'static + Send + Sync + fmt::Debug { async fn fetch_genesis_config(&self) -> EnrichedClientResult; - async fn fetch_attestation_status(&self) -> EnrichedClientResult>; + async fn fetch_attestation_status(&self) + -> EnrichedClientResult>; } #[async_trait] @@ -139,7 +140,9 @@ impl MainNodeClient for Box> { .await } - async fn fetch_attestation_status(&self) -> EnrichedClientResult> { + async fn fetch_attestation_status( + &self, + ) -> EnrichedClientResult> { self.attestation_status() .rpc_context("attestation_status") .await diff --git a/core/node/node_sync/src/testonly.rs b/core/node/node_sync/src/testonly.rs index 1cf3d1fe1e71..b9e1adc995af 100644 --- a/core/node/node_sync/src/testonly.rs +++ b/core/node/node_sync/src/testonly.rs @@ -77,7 +77,9 @@ impl MainNodeClient for MockMainNodeClient { unimplemented!() } - async fn fetch_attestation_status(&self) -> EnrichedClientResult> { + async fn fetch_attestation_status( + &self, + ) -> EnrichedClientResult> { unimplemented!() } From 44f7ad9318edcd9b33123be6b5b8715f786b00a2 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Tue, 30 Jul 2024 21:05:51 +0200 Subject: [PATCH 3/5] readonly transaction --- core/node/api_server/src/web3/namespaces/en.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/node/api_server/src/web3/namespaces/en.rs b/core/node/api_server/src/web3/namespaces/en.rs index d3c14bb5f3e6..906fa753478d 100644 --- a/core/node/api_server/src/web3/namespaces/en.rs +++ b/core/node/api_server/src/web3/namespaces/en.rs @@ -44,6 +44,14 @@ impl EnNamespace { .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() .attestation_status() .await?; From d814fac3743740b28016244674e7f4842096d609 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Wed, 31 Jul 2024 13:04:38 +0200 Subject: [PATCH 4/5] more complex query --- ...b12954dde40c6e2000270738ec5ee2aa953b7.json | 22 ++++++++++++++++ ...6c5f4e49a7f0b4f3a5c74c2abd8daebc457dd.json | 22 ---------------- core/lib/dal/src/consensus_dal.rs | 25 +++++++++++++++---- 3 files changed, 42 insertions(+), 27 deletions(-) create mode 100644 core/lib/dal/.sqlx/query-5efcef56710261e3c7f9be890d6b12954dde40c6e2000270738ec5ee2aa953b7.json delete mode 100644 core/lib/dal/.sqlx/query-751c8e5ed1fc211dbb4c7419a316c5f4e49a7f0b4f3a5c74c2abd8daebc457dd.json diff --git a/core/lib/dal/.sqlx/query-5efcef56710261e3c7f9be890d6b12954dde40c6e2000270738ec5ee2aa953b7.json b/core/lib/dal/.sqlx/query-5efcef56710261e3c7f9be890d6b12954dde40c6e2000270738ec5ee2aa953b7.json new file mode 100644 index 000000000000..90aa5ea0a2e3 --- /dev/null +++ b/core/lib/dal/.sqlx/query-5efcef56710261e3c7f9be890d6b12954dde40c6e2000270738ec5ee2aa953b7.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n COALESCE(\n miniblocks.l1_batch_number,\n (\n SELECT\n (MAX(number) + 1)\n FROM\n l1_batches\n ),\n (\n SELECT\n MAX(l1_batch_number) + 1\n FROM\n snapshot_recovery\n )\n ) as \"l1_batch_number!\"\n FROM\n miniblocks\n WHERE\n number = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "l1_batch_number!", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [ + null + ] + }, + "hash": "5efcef56710261e3c7f9be890d6b12954dde40c6e2000270738ec5ee2aa953b7" +} diff --git a/core/lib/dal/.sqlx/query-751c8e5ed1fc211dbb4c7419a316c5f4e49a7f0b4f3a5c74c2abd8daebc457dd.json b/core/lib/dal/.sqlx/query-751c8e5ed1fc211dbb4c7419a316c5f4e49a7f0b4f3a5c74c2abd8daebc457dd.json deleted file mode 100644 index 0993a0e216c3..000000000000 --- a/core/lib/dal/.sqlx/query-751c8e5ed1fc211dbb4c7419a316c5f4e49a7f0b4f3a5c74c2abd8daebc457dd.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT l1_batch_number FROM miniblocks WHERE number = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "l1_batch_number", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [ - true - ] - }, - "hash": "751c8e5ed1fc211dbb4c7419a316c5f4e49a7f0b4f3a5c74c2abd8daebc457dd" -} diff --git a/core/lib/dal/src/consensus_dal.rs b/core/lib/dal/src/consensus_dal.rs index 176dd4acc0ed..28559e8a62d2 100644 --- a/core/lib/dal/src/consensus_dal.rs +++ b/core/lib/dal/src/consensus_dal.rs @@ -467,6 +467,7 @@ impl ConsensusDal<'_, '_> { } /// Number of L1 batch that the L2 block belongs to. + /// None if the L2 block doesn't exist. async fn batch_of_block( &mut self, block: validator::BlockNumber, @@ -474,7 +475,21 @@ impl ConsensusDal<'_, '_> { let Some(row) = sqlx::query!( r#" SELECT - l1_batch_number + COALESCE( + miniblocks.l1_batch_number, + ( + SELECT + (MAX(number) + 1) + FROM + l1_batches + ), + ( + SELECT + MAX(l1_batch_number) + 1 + FROM + snapshot_recovery + ) + ) AS "l1_batch_number!" FROM miniblocks WHERE @@ -489,16 +504,16 @@ impl ConsensusDal<'_, '_> { else { return Ok(None); }; - let Some(batch) = row.l1_batch_number else { - return Ok(None); - }; Ok(Some(attester::BatchNumber( - batch.try_into().context("overflow")?, + row.l1_batch_number.try_into().context("overflow")?, ))) } /// Global attestation status. /// Includes the next batch that the attesters should vote for. + /// None iff the consensus genesis is missing (i.e. consensus wasn't enabled) or + /// L2 block with number `genesis.first_block` doesn't exist yet. + /// /// This is a main node only query. /// ENs should call the attestation_status RPC of the main node. pub async fn attestation_status(&mut self) -> anyhow::Result> { From c6e1294f7b8c76a73531aa56ff1e25fd56501654 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Wed, 31 Jul 2024 13:20:29 +0200 Subject: [PATCH 5/5] stupid formatting --- ...6c76f1e4e9ee2283cab780f7ed1d91199b4d34011cdc9376c005.json} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename core/lib/dal/.sqlx/{query-5efcef56710261e3c7f9be890d6b12954dde40c6e2000270738ec5ee2aa953b7.json => query-e475ff151b9f6c76f1e4e9ee2283cab780f7ed1d91199b4d34011cdc9376c005.json} (86%) diff --git a/core/lib/dal/.sqlx/query-5efcef56710261e3c7f9be890d6b12954dde40c6e2000270738ec5ee2aa953b7.json b/core/lib/dal/.sqlx/query-e475ff151b9f6c76f1e4e9ee2283cab780f7ed1d91199b4d34011cdc9376c005.json similarity index 86% rename from core/lib/dal/.sqlx/query-5efcef56710261e3c7f9be890d6b12954dde40c6e2000270738ec5ee2aa953b7.json rename to core/lib/dal/.sqlx/query-e475ff151b9f6c76f1e4e9ee2283cab780f7ed1d91199b4d34011cdc9376c005.json index 90aa5ea0a2e3..2598be6267d1 100644 --- a/core/lib/dal/.sqlx/query-5efcef56710261e3c7f9be890d6b12954dde40c6e2000270738ec5ee2aa953b7.json +++ b/core/lib/dal/.sqlx/query-e475ff151b9f6c76f1e4e9ee2283cab780f7ed1d91199b4d34011cdc9376c005.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n COALESCE(\n miniblocks.l1_batch_number,\n (\n SELECT\n (MAX(number) + 1)\n FROM\n l1_batches\n ),\n (\n SELECT\n MAX(l1_batch_number) + 1\n FROM\n snapshot_recovery\n )\n ) as \"l1_batch_number!\"\n FROM\n miniblocks\n WHERE\n number = $1\n ", + "query": "\n SELECT\n COALESCE(\n miniblocks.l1_batch_number,\n (\n SELECT\n (MAX(number) + 1)\n FROM\n l1_batches\n ),\n (\n SELECT\n MAX(l1_batch_number) + 1\n FROM\n snapshot_recovery\n )\n ) AS \"l1_batch_number!\"\n FROM\n miniblocks\n WHERE\n number = $1\n ", "describe": { "columns": [ { @@ -18,5 +18,5 @@ null ] }, - "hash": "5efcef56710261e3c7f9be890d6b12954dde40c6e2000270738ec5ee2aa953b7" + "hash": "e475ff151b9f6c76f1e4e9ee2283cab780f7ed1d91199b4d34011cdc9376c005" }