diff --git a/Cargo.lock b/Cargo.lock index 8446831eca71..d8252593a61c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8724,6 +8724,22 @@ dependencies = [ "zksync_types", ] +[[package]] +name = "zksync_external_proof_integration_api" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum", + "bincode", + "tokio", + "tracing", + "zksync_basic_types", + "zksync_config", + "zksync_dal", + "zksync_object_store", + "zksync_prover_interface", +] + [[package]] name = "zksync_health_check" version = "0.1.0" @@ -9073,6 +9089,7 @@ dependencies = [ "zksync_eth_sender", "zksync_eth_watch", "zksync_external_price_api", + "zksync_external_proof_integration_api", "zksync_health_check", "zksync_house_keeper", "zksync_metadata_calculator", diff --git a/Cargo.toml b/Cargo.toml index 691ce1c4c3ff..20e24bff044e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ members = [ "core/node/api_server", "core/node/tee_verifier_input_producer", "core/node/base_token_adjuster", + "core/node/external_proof_integration_api", # Libraries "core/lib/db_connection", "core/lib/zksync_core_leftovers", @@ -77,7 +78,6 @@ members = [ "core/tests/loadnext", "core/tests/vm-benchmark", "core/tests/vm-benchmark/harness", - # Parts of prover workspace that are needed for Core workspace "prover/crates/lib/prover_dal", ] @@ -282,6 +282,7 @@ zksync_eth_sender = { version = "0.1.0", path = "core/node/eth_sender" } zksync_node_db_pruner = { version = "0.1.0", path = "core/node/db_pruner" } zksync_node_fee_model = { version = "0.1.0", path = "core/node/fee_model" } zksync_vm_runner = { version = "0.1.0", path = "core/node/vm_runner" } +zksync_external_proof_integration_api = { version = "0.1.0", path = "core/node/external_proof_integration_api" } zksync_node_test_utils = { version = "0.1.0", path = "core/node/test_utils" } zksync_state_keeper = { version = "0.1.0", path = "core/node/state_keeper" } zksync_reorg_detector = { version = "0.1.0", path = "core/node/reorg_detector" } diff --git a/core/bin/zksync_server/src/main.rs b/core/bin/zksync_server/src/main.rs index a7fee61c8a01..b1d522171fe1 100644 --- a/core/bin/zksync_server/src/main.rs +++ b/core/bin/zksync_server/src/main.rs @@ -18,8 +18,8 @@ use zksync_config::{ ProtectiveReadsWriterConfig, Secrets, }, ApiConfig, BaseTokenAdjusterConfig, ContractVerifierConfig, DADispatcherConfig, DBConfig, - EthConfig, EthWatchConfig, GasAdjusterConfig, GenesisConfig, ObjectStoreConfig, PostgresConfig, - SnapshotsCreatorConfig, + EthConfig, EthWatchConfig, ExternalProofIntegrationApiConfig, GasAdjusterConfig, GenesisConfig, + ObjectStoreConfig, PostgresConfig, SnapshotsCreatorConfig, }; use zksync_core_leftovers::{ temp_config_store::{decode_yaml_repr, TempConfigStore}, @@ -208,5 +208,6 @@ fn load_env_config() -> anyhow::Result { pruning: None, snapshot_recovery: None, external_price_api_client_config: ExternalPriceApiClientConfig::from_env().ok(), + external_proof_integration_api_config: ExternalProofIntegrationApiConfig::from_env().ok(), }) } diff --git a/core/bin/zksync_server/src/node_builder.rs b/core/bin/zksync_server/src/node_builder.rs index 7b83587a29eb..648050a08ebc 100644 --- a/core/bin/zksync_server/src/node_builder.rs +++ b/core/bin/zksync_server/src/node_builder.rs @@ -31,6 +31,7 @@ use zksync_node_framework::{ da_dispatcher::DataAvailabilityDispatcherLayer, eth_sender::{EthTxAggregatorLayer, EthTxManagerLayer}, eth_watch::EthWatchLayer, + external_proof_integration_api::ExternalProofIntegrationApiLayer, gas_adjuster::GasAdjusterLayer, healtcheck_server::HealthCheckLayer, house_keeper::HouseKeeperLayer, @@ -574,6 +575,16 @@ impl MainNodeBuilder { Ok(self) } + fn add_external_proof_integration_api_layer(mut self) -> anyhow::Result { + let config = try_load_config!(self.configs.external_proof_integration_api_config); + self.node.add_layer(ExternalProofIntegrationApiLayer::new( + config, + self.genesis_config.l1_batch_commit_data_generator_mode, + )); + + Ok(self) + } + /// This layer will make sure that the database is initialized correctly, /// e.g. genesis will be performed if it's required. /// @@ -718,6 +729,9 @@ impl MainNodeBuilder { Component::VmRunnerBwip => { self = self.add_vm_runner_bwip_layer()?; } + Component::ExternalProofIntegrationApi => { + self = self.add_external_proof_integration_api_layer()?; + } } } Ok(self.node.build()) diff --git a/core/lib/config/src/configs/external_proof_integration_api.rs b/core/lib/config/src/configs/external_proof_integration_api.rs new file mode 100644 index 000000000000..f9a43995ad17 --- /dev/null +++ b/core/lib/config/src/configs/external_proof_integration_api.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Debug, Deserialize, Clone, PartialEq)] +pub struct ExternalProofIntegrationApiConfig { + pub http_port: u16, +} diff --git a/core/lib/config/src/configs/general.rs b/core/lib/config/src/configs/general.rs index 5707b5c70492..91106a7ca1d4 100644 --- a/core/lib/config/src/configs/general.rs +++ b/core/lib/config/src/configs/general.rs @@ -14,8 +14,8 @@ use crate::{ FriWitnessVectorGeneratorConfig, ObservabilityConfig, PrometheusConfig, ProofDataHandlerConfig, }, - ApiConfig, ContractVerifierConfig, DBConfig, EthConfig, ObjectStoreConfig, PostgresConfig, - SnapshotsCreatorConfig, + ApiConfig, ContractVerifierConfig, DBConfig, EthConfig, ExternalProofIntegrationApiConfig, + ObjectStoreConfig, PostgresConfig, SnapshotsCreatorConfig, }; #[derive(Debug, Clone, PartialEq)] @@ -50,4 +50,5 @@ pub struct GeneralConfig { pub base_token_adjuster: Option, pub external_price_api_client_config: Option, pub consensus_config: Option, + pub external_proof_integration_api_config: Option, } diff --git a/core/lib/config/src/configs/mod.rs b/core/lib/config/src/configs/mod.rs index 0da6f986f353..eb6c26dbe944 100644 --- a/core/lib/config/src/configs/mod.rs +++ b/core/lib/config/src/configs/mod.rs @@ -11,6 +11,7 @@ pub use self::{ eth_watch::EthWatchConfig, experimental::ExperimentalDBConfig, external_price_api_client::ExternalPriceApiClientConfig, + external_proof_integration_api::ExternalProofIntegrationApiConfig, fri_proof_compressor::FriProofCompressorConfig, fri_prover::FriProverConfig, fri_prover_gateway::FriProverGatewayConfig, @@ -43,6 +44,7 @@ pub mod eth_sender; pub mod eth_watch; mod experimental; pub mod external_price_api_client; +pub mod external_proof_integration_api; pub mod fri_proof_compressor; pub mod fri_prover; pub mod fri_prover_gateway; diff --git a/core/lib/config/src/lib.rs b/core/lib/config/src/lib.rs index c5944e581a97..ae8288fa72ea 100644 --- a/core/lib/config/src/lib.rs +++ b/core/lib/config/src/lib.rs @@ -2,8 +2,8 @@ pub use crate::configs::{ ApiConfig, BaseTokenAdjusterConfig, ContractVerifierConfig, ContractsConfig, - DADispatcherConfig, DBConfig, EthConfig, EthWatchConfig, GasAdjusterConfig, GenesisConfig, - ObjectStoreConfig, PostgresConfig, SnapshotsCreatorConfig, + DADispatcherConfig, DBConfig, EthConfig, EthWatchConfig, ExternalProofIntegrationApiConfig, + GasAdjusterConfig, GenesisConfig, ObjectStoreConfig, PostgresConfig, SnapshotsCreatorConfig, }; pub mod configs; diff --git a/core/lib/config/src/testonly.rs b/core/lib/config/src/testonly.rs index f31ec9a0acab..2d6f02e1f6c5 100644 --- a/core/lib/config/src/testonly.rs +++ b/core/lib/config/src/testonly.rs @@ -995,6 +995,19 @@ impl Distribution for Enc } } +impl Distribution + for EncodeDist +{ + fn sample( + &self, + rng: &mut R, + ) -> configs::external_proof_integration_api::ExternalProofIntegrationApiConfig { + configs::external_proof_integration_api::ExternalProofIntegrationApiConfig { + http_port: self.sample(rng), + } + } +} + impl Distribution for EncodeDist { fn sample( &self, @@ -1044,6 +1057,7 @@ impl Distribution for EncodeDist { base_token_adjuster: self.sample(rng), external_price_api_client_config: self.sample(rng), consensus_config: self.sample(rng), + external_proof_integration_api_config: self.sample(rng), } } } diff --git a/core/lib/dal/.sqlx/query-782726c01284ded3905c312262afd52908e6748be50524871ff40e3301fecab1.json b/core/lib/dal/.sqlx/query-782726c01284ded3905c312262afd52908e6748be50524871ff40e3301fecab1.json new file mode 100644 index 000000000000..2be235312c77 --- /dev/null +++ b/core/lib/dal/.sqlx/query-782726c01284ded3905c312262afd52908e6748be50524871ff40e3301fecab1.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n l1_batch_number\n FROM\n proof_generation_details\n WHERE\n proof_blob_url IS NOT NULL\n ORDER BY\n l1_batch_number ASC\n LIMIT\n 1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "l1_batch_number", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false + ] + }, + "hash": "782726c01284ded3905c312262afd52908e6748be50524871ff40e3301fecab1" +} diff --git a/core/lib/dal/src/proof_generation_dal.rs b/core/lib/dal/src/proof_generation_dal.rs index 4e37cc644f8e..808f4c6f48a0 100644 --- a/core/lib/dal/src/proof_generation_dal.rs +++ b/core/lib/dal/src/proof_generation_dal.rs @@ -88,6 +88,29 @@ impl ProofGenerationDal<'_, '_> { Ok(result) } + pub async fn get_available_batch(&mut self) -> DalResult { + let result = sqlx::query!( + r#" + SELECT + l1_batch_number + FROM + proof_generation_details + WHERE + proof_blob_url IS NOT NULL + ORDER BY + l1_batch_number ASC + LIMIT + 1 + "#, + ) + .instrument("get_available batch") + .fetch_one(self.storage) + .await? + .l1_batch_number as u32; + + Ok(L1BatchNumber(result)) + } + /// Marks a previously locked batch as 'unpicked', allowing it to be picked without having /// to wait for the processing timeout. pub async fn unlock_batch(&mut self, l1_batch_number: L1BatchNumber) -> DalResult<()> { diff --git a/core/lib/env_config/src/external_proof_integration_api.rs b/core/lib/env_config/src/external_proof_integration_api.rs new file mode 100644 index 000000000000..dddca93eb0ec --- /dev/null +++ b/core/lib/env_config/src/external_proof_integration_api.rs @@ -0,0 +1,35 @@ +use zksync_config::configs::ExternalProofIntegrationApiConfig; + +use crate::{envy_load, FromEnv}; + +impl FromEnv for ExternalProofIntegrationApiConfig { + fn from_env() -> anyhow::Result { + envy_load( + "external_proof_integration_api", + "EXTERNAL_PROOF_INTEGRATION_API_", + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_utils::EnvMutex; + + static MUTEX: EnvMutex = EnvMutex::new(); + + fn expected_config() -> ExternalProofIntegrationApiConfig { + ExternalProofIntegrationApiConfig { http_port: 3320 } + } + + #[test] + fn from_env() { + let config = r#" + EXTERNAL_PROOF_INTEGRATION_API_HTTP_PORT="3320" + "#; + let mut lock = MUTEX.lock(); + lock.set_env(config); + let actual = ExternalProofIntegrationApiConfig::from_env().unwrap(); + assert_eq!(actual, expected_config()); + } +} diff --git a/core/lib/env_config/src/lib.rs b/core/lib/env_config/src/lib.rs index 789f6f8be2fd..fcb0f3625ea1 100644 --- a/core/lib/env_config/src/lib.rs +++ b/core/lib/env_config/src/lib.rs @@ -24,6 +24,7 @@ mod utils; mod base_token_adjuster; mod da_dispatcher; mod external_price_api_client; +mod external_proof_integration_api; mod genesis; #[cfg(test)] mod test_utils; diff --git a/core/lib/protobuf_config/src/external_proof_integration_api.rs b/core/lib/protobuf_config/src/external_proof_integration_api.rs new file mode 100644 index 000000000000..e824df50dfc6 --- /dev/null +++ b/core/lib/protobuf_config/src/external_proof_integration_api.rs @@ -0,0 +1,22 @@ +use anyhow::Context; +use zksync_config::ExternalProofIntegrationApiConfig; +use zksync_protobuf::{required, ProtoRepr}; + +use crate::proto::external_proof_integration_api as proto; + +impl ProtoRepr for proto::ExternalProofIntegrationApi { + type Type = ExternalProofIntegrationApiConfig; + fn read(&self) -> anyhow::Result { + Ok(Self::Type { + http_port: required(&self.http_port) + .and_then(|p| Ok((*p).try_into()?)) + .context("http_port")?, + }) + } + + fn build(this: &Self::Type) -> Self { + Self { + http_port: Some(this.http_port.into()), + } + } +} diff --git a/core/lib/protobuf_config/src/general.rs b/core/lib/protobuf_config/src/general.rs index 367458f7aa25..88da18997608 100644 --- a/core/lib/protobuf_config/src/general.rs +++ b/core/lib/protobuf_config/src/general.rs @@ -40,6 +40,9 @@ impl ProtoRepr for proto::GeneralConfig { snapshot_recovery: read_optional_repr(&self.snapshot_recovery), external_price_api_client_config: read_optional_repr(&self.external_price_api_client), consensus_config: read_optional_repr(&self.consensus), + external_proof_integration_api_config: read_optional_repr( + &self.external_proof_integration_api, + ), }) } @@ -90,6 +93,10 @@ impl ProtoRepr for proto::GeneralConfig { .as_ref() .map(ProtoRepr::build), consensus: this.consensus_config.as_ref().map(ProtoRepr::build), + external_proof_integration_api: this + .external_proof_integration_api_config + .as_ref() + .map(ProtoRepr::build), } } } diff --git a/core/lib/protobuf_config/src/lib.rs b/core/lib/protobuf_config/src/lib.rs index d7a4a4e570ad..ee526b2bb67f 100644 --- a/core/lib/protobuf_config/src/lib.rs +++ b/core/lib/protobuf_config/src/lib.rs @@ -30,6 +30,7 @@ mod secrets; mod snapshots_creator; mod external_price_api_client; +mod external_proof_integration_api; mod snapshot_recovery; #[cfg(test)] mod tests; diff --git a/core/lib/protobuf_config/src/proto/config/external_proof_integration_api.proto b/core/lib/protobuf_config/src/proto/config/external_proof_integration_api.proto new file mode 100644 index 000000000000..07203202c9d6 --- /dev/null +++ b/core/lib/protobuf_config/src/proto/config/external_proof_integration_api.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +package zksync.config.external_proof_integration_api; + +message ExternalProofIntegrationApi { + optional uint32 http_port = 1; +} diff --git a/core/lib/protobuf_config/src/proto/config/general.proto b/core/lib/protobuf_config/src/proto/config/general.proto index 37d507b9ab62..cb4629f2d85f 100644 --- a/core/lib/protobuf_config/src/proto/config/general.proto +++ b/core/lib/protobuf_config/src/proto/config/general.proto @@ -21,37 +21,39 @@ import "zksync/config/pruning.proto"; import "zksync/config/object_store.proto"; import "zksync/config/base_token_adjuster.proto"; import "zksync/config/external_price_api_client.proto"; +import "zksync/config/external_proof_integration_api.proto"; import "zksync/core/consensus.proto"; message GeneralConfig { - optional database.Postgres postgres = 1; - optional api.Api api = 2; - optional contract_verifier.ContractVerifier contract_verifier = 3; - optional circuit_breaker.CircuitBreaker circuit_breaker = 5; - optional chain.Mempool mempool = 6; - optional chain.OperationsManager operations_manager = 8; - optional chain.StateKeeper state_keeper = 9; - optional house_keeper.HouseKeeper house_keeper = 10; - optional prover.Prover prover = 12; - optional utils.Prometheus prometheus = 15; - optional database.DB db = 20; - optional eth.ETH eth = 22; - optional prover.WitnessGenerator witness_generator = 24; - optional prover.WitnessVectorGenerator witness_vector_generator = 25; - optional prover.ProofCompressor proof_compressor = 27; - optional prover.ProofDataHandler data_handler = 28; - optional prover.ProverGroup prover_group = 29; - optional prover.ProverGateway prover_gateway = 30; - optional snapshot_creator.SnapshotsCreator snapshot_creator = 31; - optional observability.Observability observability = 32; - optional vm_runner.ProtectiveReadsWriter protective_reads_writer = 33; - optional object_store.ObjectStore core_object_store = 34; - optional snapshot_recovery.SnapshotRecovery snapshot_recovery = 35; - optional pruning.Pruning pruning = 36; - optional commitment_generator.CommitmentGenerator commitment_generator = 37; - optional da_dispatcher.DataAvailabilityDispatcher da_dispatcher = 38; - optional base_token_adjuster.BaseTokenAdjuster base_token_adjuster = 39; - optional vm_runner.BasicWitnessInputProducer basic_witness_input_producer = 40; - optional external_price_api_client.ExternalPriceApiClient external_price_api_client = 41; - optional core.consensus.Config consensus = 42; + optional database.Postgres postgres = 1; + optional api.Api api = 2; + optional contract_verifier.ContractVerifier contract_verifier = 3; + optional circuit_breaker.CircuitBreaker circuit_breaker = 5; + optional chain.Mempool mempool = 6; + optional chain.OperationsManager operations_manager = 8; + optional chain.StateKeeper state_keeper = 9; + optional house_keeper.HouseKeeper house_keeper = 10; + optional prover.Prover prover = 12; + optional utils.Prometheus prometheus = 15; + optional database.DB db = 20; + optional eth.ETH eth = 22; + optional prover.WitnessGenerator witness_generator = 24; + optional prover.WitnessVectorGenerator witness_vector_generator = 25; + optional prover.ProofCompressor proof_compressor = 27; + optional prover.ProofDataHandler data_handler = 28; + optional prover.ProverGroup prover_group = 29; + optional prover.ProverGateway prover_gateway = 30; + optional snapshot_creator.SnapshotsCreator snapshot_creator = 31; + optional observability.Observability observability = 32; + optional vm_runner.ProtectiveReadsWriter protective_reads_writer = 33; + optional object_store.ObjectStore core_object_store = 34; + optional snapshot_recovery.SnapshotRecovery snapshot_recovery = 35; + optional pruning.Pruning pruning = 36; + optional commitment_generator.CommitmentGenerator commitment_generator = 37; + optional da_dispatcher.DataAvailabilityDispatcher da_dispatcher = 38; + optional base_token_adjuster.BaseTokenAdjuster base_token_adjuster = 39; + optional vm_runner.BasicWitnessInputProducer basic_witness_input_producer = 40; + optional external_price_api_client.ExternalPriceApiClient external_price_api_client = 41; + optional core.consensus.Config consensus = 42; + optional external_proof_integration_api.ExternalProofIntegrationApi external_proof_integration_api = 43; } diff --git a/core/lib/prover_interface/src/api.rs b/core/lib/prover_interface/src/api.rs index 00ac85a40739..e0b617ee3bea 100644 --- a/core/lib/prover_interface/src/api.rs +++ b/core/lib/prover_interface/src/api.rs @@ -14,7 +14,7 @@ use crate::{ // Structs for holding data returned in HTTP responses -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProofGenerationData { pub l1_batch_number: L1BatchNumber, pub witness_input_data: WitnessInputData, @@ -61,6 +61,12 @@ pub enum SubmitProofRequest { SkippedProofGeneration, } +#[derive(Debug, Serialize, Deserialize)] +pub struct OptionalProofGenerationDataRequest(pub Option); + +#[derive(Debug, Serialize, Deserialize)] +pub struct VerifyProofRequest(pub Box); + #[derive(Debug, PartialEq, Serialize, Deserialize)] pub struct SubmitTeeProofRequest(pub Box); diff --git a/core/lib/zksync_core_leftovers/src/lib.rs b/core/lib/zksync_core_leftovers/src/lib.rs index b79b86d718d0..c443fb9e8e07 100644 --- a/core/lib/zksync_core_leftovers/src/lib.rs +++ b/core/lib/zksync_core_leftovers/src/lib.rs @@ -62,6 +62,8 @@ pub enum Component { BaseTokenRatioPersister, /// VM runner-based component that saves VM execution data for basic witness generation. VmRunnerBwip, + /// External prover API that is used to retrieve data for proving and verifies final proofs against ones, generated by us + ExternalProofIntegrationApi, } #[derive(Debug)] @@ -106,6 +108,9 @@ impl FromStr for Components { Ok(Components(vec![Component::BaseTokenRatioPersister])) } "vm_runner_bwip" => Ok(Components(vec![Component::VmRunnerBwip])), + "external_proof_integration_api" => { + Ok(Components(vec![Component::ExternalProofIntegrationApi])) + } other => Err(format!("{} is not a valid component name", other)), } } diff --git a/core/lib/zksync_core_leftovers/src/temp_config_store/mod.rs b/core/lib/zksync_core_leftovers/src/temp_config_store/mod.rs index 1ad688ed14cb..6d2a06605be0 100644 --- a/core/lib/zksync_core_leftovers/src/temp_config_store/mod.rs +++ b/core/lib/zksync_core_leftovers/src/temp_config_store/mod.rs @@ -19,8 +19,8 @@ use zksync_config::{ PruningConfig, SnapshotRecoveryConfig, }, ApiConfig, BaseTokenAdjusterConfig, ContractVerifierConfig, DADispatcherConfig, DBConfig, - EthConfig, EthWatchConfig, GasAdjusterConfig, ObjectStoreConfig, PostgresConfig, - SnapshotsCreatorConfig, + EthConfig, EthWatchConfig, ExternalProofIntegrationApiConfig, GasAdjusterConfig, + ObjectStoreConfig, PostgresConfig, SnapshotsCreatorConfig, }; use zksync_env_config::FromEnv; use zksync_protobuf::repr::ProtoRepr; @@ -77,6 +77,7 @@ pub struct TempConfigStore { pub pruning: Option, pub snapshot_recovery: Option, pub external_price_api_client_config: Option, + pub external_proof_integration_api_config: Option, } impl TempConfigStore { @@ -112,6 +113,9 @@ impl TempConfigStore { pruning: self.pruning.clone(), external_price_api_client_config: self.external_price_api_client_config.clone(), consensus_config: None, + external_proof_integration_api_config: self + .external_proof_integration_api_config + .clone(), } } @@ -183,6 +187,7 @@ fn load_env_config() -> anyhow::Result { pruning: None, snapshot_recovery: None, external_price_api_client_config: ExternalPriceApiClientConfig::from_env().ok(), + external_proof_integration_api_config: ExternalProofIntegrationApiConfig::from_env().ok(), }) } diff --git a/core/node/external_proof_integration_api/Cargo.toml b/core/node/external_proof_integration_api/Cargo.toml new file mode 100644 index 000000000000..ae7cd4c4d031 --- /dev/null +++ b/core/node/external_proof_integration_api/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "zksync_external_proof_integration_api" +description = "ZKsync external proof integration API" +version.workspace = true +edition.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true +keywords.workspace = true +categories.workspace = true + +[dependencies] +axum.workspace = true +tracing.workspace = true +zksync_prover_interface.workspace = true +zksync_basic_types.workspace = true +zksync_config.workspace = true +zksync_object_store.workspace = true +zksync_dal.workspace = true +tokio.workspace = true +bincode.workspace = true +anyhow.workspace = true diff --git a/core/node/external_proof_integration_api/src/error.rs b/core/node/external_proof_integration_api/src/error.rs new file mode 100644 index 000000000000..dac8e2a27ed6 --- /dev/null +++ b/core/node/external_proof_integration_api/src/error.rs @@ -0,0 +1,86 @@ +use axum::{ + http::StatusCode, + response::{IntoResponse, Response}, +}; +use zksync_basic_types::L1BatchNumber; +use zksync_dal::DalError; +use zksync_object_store::ObjectStoreError; + +pub(crate) enum ProcessorError { + ObjectStore(ObjectStoreError), + Dal(DalError), + Serialization(bincode::Error), + InvalidProof, + BatchNotReady(L1BatchNumber), +} + +impl From for ProcessorError { + fn from(err: ObjectStoreError) -> Self { + Self::ObjectStore(err) + } +} + +impl From for ProcessorError { + fn from(err: DalError) -> Self { + Self::Dal(err) + } +} + +impl From for ProcessorError { + fn from(err: bincode::Error) -> Self { + Self::Serialization(err) + } +} + +impl IntoResponse for ProcessorError { + fn into_response(self) -> Response { + let (status_code, message) = match self { + ProcessorError::ObjectStore(err) => { + tracing::error!("GCS error: {:?}", err); + match err { + ObjectStoreError::KeyNotFound(_) => ( + StatusCode::NOT_FOUND, + "Proof verification not possible anymore, batch is too old.".to_owned(), + ), + _ => ( + StatusCode::INTERNAL_SERVER_ERROR, + "Failed fetching from GCS".to_owned(), + ), + } + } + ProcessorError::Dal(err) => { + tracing::error!("Sqlx error: {:?}", err); + match err.inner() { + zksync_dal::SqlxError::RowNotFound => { + (StatusCode::NOT_FOUND, "Non existing L1 batch".to_owned()) + } + _ => ( + StatusCode::INTERNAL_SERVER_ERROR, + "Failed fetching/saving from db".to_owned(), + ), + } + } + ProcessorError::Serialization(err) => { + tracing::error!("Serialization error: {:?}", err); + ( + StatusCode::BAD_REQUEST, + "Failed to deserialize proof data".to_owned(), + ) + } + ProcessorError::BatchNotReady(l1_batch_number) => { + tracing::error!( + "Batch {l1_batch_number:?} is not yet ready for proving. Most likely our proof for this batch is not generated yet" + ); + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Batch {l1_batch_number:?} is not yet ready for proving. Most likely our proof for this batch is not generated yet, try again later"), + ) + } + ProcessorError::InvalidProof => { + tracing::error!("Invalid proof data"); + (StatusCode::BAD_REQUEST, "Invalid proof data".to_owned()) + } + }; + (status_code, message).into_response() + } +} diff --git a/core/node/external_proof_integration_api/src/lib.rs b/core/node/external_proof_integration_api/src/lib.rs new file mode 100644 index 000000000000..51fecf8c23fc --- /dev/null +++ b/core/node/external_proof_integration_api/src/lib.rs @@ -0,0 +1,73 @@ +mod error; +mod processor; + +use std::{net::SocketAddr, sync::Arc}; + +use anyhow::Context; +use axum::{extract::Path, routing::post, Json, Router}; +use tokio::sync::watch; +use zksync_basic_types::commitment::L1BatchCommitmentMode; +use zksync_config::configs::external_proof_integration_api::ExternalProofIntegrationApiConfig; +use zksync_dal::{ConnectionPool, Core}; +use zksync_object_store::ObjectStore; +use zksync_prover_interface::api::{OptionalProofGenerationDataRequest, VerifyProofRequest}; + +use crate::processor::Processor; + +pub async fn run_server( + config: ExternalProofIntegrationApiConfig, + blob_store: Arc, + connection_pool: ConnectionPool, + commitment_mode: L1BatchCommitmentMode, + mut stop_receiver: watch::Receiver, +) -> anyhow::Result<()> { + let bind_address = SocketAddr::from(([0, 0, 0, 0], config.http_port)); + tracing::debug!("Starting external prover API server on {bind_address}"); + let app = create_router(blob_store, connection_pool, commitment_mode).await; + + let listener = tokio::net::TcpListener::bind(bind_address) + .await + .with_context(|| format!("Failed binding external prover API server to {bind_address}"))?; + axum::serve(listener, app) + .with_graceful_shutdown(async move { + if stop_receiver.changed().await.is_err() { + tracing::warn!("Stop signal sender for external prover API server was dropped without sending a signal"); + } + tracing::info!("Stop signal received, external prover API server is shutting down"); + }) + .await + .context("External prover API server failed")?; + tracing::info!("External prover API server shut down"); + Ok(()) +} + +async fn create_router( + blob_store: Arc, + connection_pool: ConnectionPool, + commitment_mode: L1BatchCommitmentMode, +) -> Router { + let mut processor = + Processor::new(blob_store.clone(), connection_pool.clone(), commitment_mode); + let verify_proof_processor = processor.clone(); + Router::new() + .route( + "/proof_generation_data", + post( + // we use post method because the returned data is not idempotent, + // i.e we return different result on each call. + move |payload: Json| async move { + processor.get_proof_generation_data(payload).await + }, + ), + ) + .route( + "/verify_proof/:l1_batch_number", + post( + move |l1_batch_number: Path, payload: Json| async move { + verify_proof_processor + .verify_proof(l1_batch_number, payload) + .await + }, + ), + ) +} diff --git a/core/node/external_proof_integration_api/src/processor.rs b/core/node/external_proof_integration_api/src/processor.rs new file mode 100644 index 000000000000..a15e45e48037 --- /dev/null +++ b/core/node/external_proof_integration_api/src/processor.rs @@ -0,0 +1,190 @@ +use std::sync::Arc; + +use axum::{extract::Path, Json}; +use zksync_basic_types::{ + basic_fri_types::Eip4844Blobs, commitment::L1BatchCommitmentMode, L1BatchNumber, +}; +use zksync_dal::{ConnectionPool, Core, CoreDal}; +use zksync_object_store::{bincode, ObjectStore}; +use zksync_prover_interface::{ + api::{ + OptionalProofGenerationDataRequest, ProofGenerationData, ProofGenerationDataResponse, + VerifyProofRequest, + }, + inputs::{ + L1BatchMetadataHashes, VMRunWitnessInputData, WitnessInputData, WitnessInputMerklePaths, + }, + outputs::L1BatchProofForL1, +}; + +use crate::error::ProcessorError; + +#[derive(Clone)] +pub(crate) struct Processor { + blob_store: Arc, + pool: ConnectionPool, + commitment_mode: L1BatchCommitmentMode, +} + +impl Processor { + pub(crate) fn new( + blob_store: Arc, + pool: ConnectionPool, + commitment_mode: L1BatchCommitmentMode, + ) -> Self { + Self { + blob_store, + pool, + commitment_mode, + } + } + + #[tracing::instrument(skip_all)] + pub(crate) async fn get_proof_generation_data( + &mut self, + request: Json, + ) -> Result, ProcessorError> { + tracing::info!("Received request for proof generation data: {:?}", request); + + let latest_available_batch = self + .pool + .connection() + .await + .unwrap() + .proof_generation_dal() + .get_available_batch() + .await?; + + let l1_batch_number = if let Some(l1_batch_number) = request.0 .0 { + if l1_batch_number > latest_available_batch { + tracing::error!( + "Requested batch is not available: {:?}, latest available batch is {:?}", + l1_batch_number, + latest_available_batch + ); + return Err(ProcessorError::BatchNotReady(l1_batch_number)); + } + l1_batch_number + } else { + latest_available_batch + }; + + let proof_generation_data = self + .proof_generation_data_for_existing_batch(l1_batch_number) + .await; + + match proof_generation_data { + Ok(data) => Ok(Json(ProofGenerationDataResponse::Success(Some(Box::new( + data, + ))))), + Err(err) => Err(err), + } + } + + #[tracing::instrument(skip(self))] + async fn proof_generation_data_for_existing_batch( + &self, + l1_batch_number: L1BatchNumber, + ) -> Result { + let vm_run_data: VMRunWitnessInputData = self + .blob_store + .get(l1_batch_number) + .await + .map_err(ProcessorError::ObjectStore)?; + let merkle_paths: WitnessInputMerklePaths = self + .blob_store + .get(l1_batch_number) + .await + .map_err(ProcessorError::ObjectStore)?; + + // Acquire connection after interacting with GCP, to avoid holding the connection for too long. + let mut conn = self.pool.connection().await.map_err(ProcessorError::Dal)?; + + let previous_batch_metadata = conn + .blocks_dal() + .get_l1_batch_metadata(L1BatchNumber(l1_batch_number.checked_sub(1).unwrap())) + .await + .map_err(ProcessorError::Dal)? + .expect("No metadata for previous batch"); + + let header = conn + .blocks_dal() + .get_l1_batch_header(l1_batch_number) + .await + .map_err(ProcessorError::Dal)? + .unwrap_or_else(|| panic!("Missing header for {}", l1_batch_number)); + + let minor_version = header.protocol_version.unwrap(); + let protocol_version = conn + .protocol_versions_dal() + .get_protocol_version_with_latest_patch(minor_version) + .await + .map_err(ProcessorError::Dal)? + .unwrap_or_else(|| { + panic!("Missing l1 verifier info for protocol version {minor_version}") + }); + + let batch_header = conn + .blocks_dal() + .get_l1_batch_header(l1_batch_number) + .await + .map_err(ProcessorError::Dal)? + .unwrap_or_else(|| panic!("Missing header for {}", l1_batch_number)); + + let eip_4844_blobs = match self.commitment_mode { + L1BatchCommitmentMode::Validium => Eip4844Blobs::empty(), + L1BatchCommitmentMode::Rollup => { + let blobs = batch_header.pubdata_input.as_deref().unwrap_or_else(|| { + panic!( + "expected pubdata, but it is not available for batch {l1_batch_number:?}" + ) + }); + Eip4844Blobs::decode(blobs).expect("failed to decode EIP-4844 blobs") + } + }; + + let blob = WitnessInputData { + vm_run_data, + merkle_paths, + eip_4844_blobs, + previous_batch_metadata: L1BatchMetadataHashes { + root_hash: previous_batch_metadata.metadata.root_hash, + meta_hash: previous_batch_metadata.metadata.meta_parameters_hash, + aux_hash: previous_batch_metadata.metadata.aux_data_hash, + }, + }; + + Ok(ProofGenerationData { + l1_batch_number, + witness_input_data: blob, + protocol_version: protocol_version.version, + l1_verifier_config: protocol_version.l1_verifier_config, + }) + } + + pub(crate) async fn verify_proof( + &self, + Path(l1_batch_number): Path, + Json(payload): Json, + ) -> Result<(), ProcessorError> { + let l1_batch_number = L1BatchNumber(l1_batch_number); + tracing::info!( + "Received request to verify proof for batch: {:?}", + l1_batch_number + ); + + let serialized_proof = bincode::serialize(&payload.0)?; + let expected_proof = bincode::serialize( + &self + .blob_store + .get::((l1_batch_number, payload.0.protocol_version)) + .await?, + )?; + + if serialized_proof != expected_proof { + return Err(ProcessorError::InvalidProof); + } + + Ok(()) + } +} diff --git a/core/node/node_framework/Cargo.toml b/core/node/node_framework/Cargo.toml index 640000c6a7d8..142d6cfa11ab 100644 --- a/core/node/node_framework/Cargo.toml +++ b/core/node/node_framework/Cargo.toml @@ -53,6 +53,7 @@ zksync_node_db_pruner.workspace = true zksync_base_token_adjuster.workspace = true zksync_node_storage_init.workspace = true zksync_external_price_api.workspace = true +zksync_external_proof_integration_api.workspace = true pin-project-lite.workspace = true tracing.workspace = true diff --git a/core/node/node_framework/src/implementations/layers/external_proof_integration_api.rs b/core/node/node_framework/src/implementations/layers/external_proof_integration_api.rs new file mode 100644 index 000000000000..7877bc6abbe3 --- /dev/null +++ b/core/node/node_framework/src/implementations/layers/external_proof_integration_api.rs @@ -0,0 +1,100 @@ +use std::sync::Arc; + +use zksync_config::configs::external_proof_integration_api::ExternalProofIntegrationApiConfig; +use zksync_dal::{ConnectionPool, Core}; +use zksync_object_store::ObjectStore; +use zksync_types::commitment::L1BatchCommitmentMode; + +use crate::{ + implementations::resources::{ + object_store::ObjectStoreResource, + pools::{PoolResource, ReplicaPool}, + }, + service::StopReceiver, + task::{Task, TaskId}, + wiring_layer::{WiringError, WiringLayer}, + FromContext, IntoContext, +}; + +/// Wiring layer for proof data handler server. +#[derive(Debug)] +pub struct ExternalProofIntegrationApiLayer { + external_proof_integration_api_config: ExternalProofIntegrationApiConfig, + commitment_mode: L1BatchCommitmentMode, +} + +#[derive(Debug, FromContext)] +#[context(crate = crate)] +pub struct Input { + pub master_pool: PoolResource, + pub object_store: ObjectStoreResource, +} + +#[derive(Debug, IntoContext)] +#[context(crate = crate)] +pub struct Output { + #[context(task)] + pub task: ProverApiTask, +} + +impl ExternalProofIntegrationApiLayer { + pub fn new( + external_proof_integration_api_config: ExternalProofIntegrationApiConfig, + commitment_mode: L1BatchCommitmentMode, + ) -> Self { + Self { + external_proof_integration_api_config, + commitment_mode, + } + } +} + +#[async_trait::async_trait] +impl WiringLayer for ExternalProofIntegrationApiLayer { + type Input = Input; + type Output = Output; + + fn layer_name(&self) -> &'static str { + "external_proof_integration_api_layer" + } + + async fn wire(self, input: Self::Input) -> Result { + let main_pool = input.master_pool.get().await.unwrap(); + let blob_store = input.object_store.0; + + let task = ProverApiTask { + external_proof_integration_api_config: self.external_proof_integration_api_config, + blob_store, + main_pool, + commitment_mode: self.commitment_mode, + }; + + Ok(Output { task }) + } +} + +#[derive(Debug)] +pub struct ProverApiTask { + external_proof_integration_api_config: ExternalProofIntegrationApiConfig, + blob_store: Arc, + main_pool: ConnectionPool, + commitment_mode: L1BatchCommitmentMode, +} + +#[async_trait::async_trait] +impl Task for ProverApiTask { + fn id(&self) -> TaskId { + "external_proof_integration_api".into() + } + + async fn run(self: Box, stop_receiver: StopReceiver) -> anyhow::Result<()> { + zksync_external_proof_integration_api::run_server( + self.external_proof_integration_api_config, + self.blob_store, + self.main_pool, + self.commitment_mode, + stop_receiver.0, + ) + .await + } +} diff --git a/core/node/node_framework/src/implementations/layers/mod.rs b/core/node/node_framework/src/implementations/layers/mod.rs index 99e50be8ae5d..6256f2d61043 100644 --- a/core/node/node_framework/src/implementations/layers/mod.rs +++ b/core/node/node_framework/src/implementations/layers/mod.rs @@ -9,6 +9,7 @@ pub mod contract_verification_api; pub mod da_dispatcher; pub mod eth_sender; pub mod eth_watch; +pub mod external_proof_integration_api; pub mod gas_adjuster; pub mod healtcheck_server; pub mod house_keeper; diff --git a/etc/env/base/external_proof_integration_api.toml b/etc/env/base/external_proof_integration_api.toml new file mode 100644 index 000000000000..5918a061be3a --- /dev/null +++ b/etc/env/base/external_proof_integration_api.toml @@ -0,0 +1,2 @@ +[external_proof_integration_api] +http_port = 3073 diff --git a/etc/env/file_based/general.yaml b/etc/env/file_based/general.yaml index 300138e9a867..97d29c45b0f7 100644 --- a/etc/env/file_based/general.yaml +++ b/etc/env/file_based/general.yaml @@ -41,7 +41,7 @@ api: estimate_gas_scale_factor: 1.3 estimate_gas_acceptable_overestimation: 5000 max_tx_size: 1000000 - api_namespaces: [en,eth,net,web3,zks,pubsub,debug] + api_namespaces: [ en,eth,net,web3,zks,pubsub,debug ] state_keeper: transaction_slots: 8192 max_allowed_l2_tx_gas_limit: 15000000000 @@ -104,7 +104,7 @@ eth: aggregated_block_execute_deadline: 10 timestamp_criteria_max_allowed_lag: 30 max_eth_tx_data_size: 120000 - aggregated_proof_sizes: [1] + aggregated_proof_sizes: [ 1 ] max_aggregated_tx_gas: 15000000 max_acceptable_priority_fee_in_gwei: 100000000000 pubdata_sending_mode: BLOBS @@ -302,7 +302,7 @@ prometheus: observability: log_format: plain - log_directives: "zksync_node_test_utils=info,zksync_state_keeper=info,zksync_reorg_detector=info,zksync_consistency_checker=info,zksync_metadata_calculator=info,zksync_node_sync=info,zksync_node_consensus=info,zksync_contract_verification_server=info,zksync_node_api_server=info,zksync_tee_verifier_input_producer=info,zksync_node_framework=info,zksync_block_reverter=info,zksync_commitment_generator=info,zksync_node_db_pruner=info,zksync_eth_sender=info,zksync_node_fee_model=info,zksync_node_genesis=info,zksync_house_keeper=info,zksync_proof_data_handler=info,zksync_shared_metrics=info,zksync_node_test_utils=info,zksync_vm_runner=info,zksync_consensus_bft=info,zksync_consensus_network=info,zksync_consensus_storage=info,zksync_core_leftovers=debug,zksync_server=debug,zksync_contract_verifier=debug,zksync_dal=info,zksync_db_connection=info,zksync_eth_client=info,zksync_eth_watch=debug,zksync_storage=info,zksync_db_manager=info,zksync_merkle_tree=info,zksync_state=debug,zksync_utils=debug,zksync_queued_job_processor=info,zksync_types=info,zksync_mempool=debug,loadnext=info,vm=info,zksync_object_store=info,zksync_external_node=info,zksync_witness_generator=info,zksync_prover_fri=info,zksync_witness_vector_generator=info,zksync_web3_decl=debug,zksync_health_check=debug,zksync_proof_fri_compressor=info,vise_exporter=error,snapshots_creator=debug,zksync_base_token_adjuster=debug,zksync_external_price_api=debug" + log_directives: "zksync_node_test_utils=info,zksync_state_keeper=info,zksync_reorg_detector=info,zksync_consistency_checker=info,zksync_metadata_calculator=info,zksync_node_sync=info,zksync_node_consensus=info,zksync_contract_verification_server=info,zksync_node_api_server=info,zksync_tee_verifier_input_producer=info,zksync_node_framework=info,zksync_block_reverter=info,zksync_commitment_generator=info,zksync_node_db_pruner=info,zksync_eth_sender=info,zksync_node_fee_model=info,zksync_node_genesis=info,zksync_house_keeper=info,zksync_proof_data_handler=info,zksync_shared_metrics=info,zksync_node_test_utils=info,zksync_vm_runner=info,zksync_consensus_bft=info,zksync_consensus_network=info,zksync_consensus_storage=info,zksync_core_leftovers=debug,zksync_server=debug,zksync_contract_verifier=debug,zksync_dal=info,zksync_db_connection=info,zksync_eth_client=info,zksync_eth_watch=debug,zksync_storage=info,zksync_db_manager=info,zksync_merkle_tree=info,zksync_state=debug,zksync_utils=debug,zksync_queued_job_processor=info,zksync_types=info,zksync_mempool=debug,loadnext=info,vm=info,zksync_object_store=info,zksync_external_node=info,zksync_witness_generator=info,zksync_prover_fri=info,zksync_witness_vector_generator=info,zksync_web3_decl=debug,zksync_health_check=debug,zksync_proof_fri_compressor=info,vise_exporter=error,snapshots_creator=debug,zksync_base_token_adjuster=debug,zksync_external_price_api=debug,zksync_external_proof_integration_api=info" # Uncomment only if needed # sentry: # url: unset @@ -354,3 +354,6 @@ da_dispatcher: polling_interval_ms: 5000 max_rows_to_dispatch: 100 max_retries: 5 + +external_proof_integration_api: + http_port: 3073