Skip to content

Commit

Permalink
feat(contract-verifier): Adapt contract verifier API for EVM bytecodes (
Browse files Browse the repository at this point in the history
#3234)

## What ❔

- Adapts contract verifier APIs to work with EVM bytecodes; adds
corresponding request correctness checks.
- Brushes up the verifier API server in general.

## Why ❔

Part of the efforts to support EVM bytecode verification.

## Checklist

- [x] PR title corresponds to the body of PR (we generate changelog
entries from PRs).
- [x] Tests for the changes have been added / updated.
- [x] Documentation comments have been added / updated.
- [x] Code has been formatted via `zkstack dev fmt` and `zkstack dev
lint`.
  • Loading branch information
slowli authored Nov 8, 2024
1 parent fd27507 commit 4509179
Show file tree
Hide file tree
Showing 24 changed files with 923 additions and 298 deletions.
12 changes: 6 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ google-cloud-storage = "0.20.0"
governor = "0.4.2"
hex = "0.4"
http = "1.1"
http-body-util = "0.1.2"
httpmock = "0.7.0"
hyper = "1.3"
insta = "1.29.0"
Expand Down
5 changes: 1 addition & 4 deletions core/bin/contract-verifier/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ publish = false

[dependencies]
zksync_dal.workspace = true
zksync_env_config.workspace = true
zksync_config = { workspace = true, features = ["observability_ext"] }
zksync_contract_verifier_lib.workspace = true
zksync_queued_job_processor.workspace = true
Expand All @@ -21,8 +20,6 @@ zksync_vlog.workspace = true
zksync_core_leftovers.workspace = true

anyhow.workspace = true
clap = { workspace = true, features = ["derive"] }
tokio = { workspace = true, features = ["full"] }
futures.workspace = true
ctrlc.workspace = true
structopt.workspace = true
tracing.workspace = true
53 changes: 18 additions & 35 deletions core/bin/contract-verifier/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use std::{cell::RefCell, time::Duration};
use std::{path::PathBuf, time::Duration};

use anyhow::Context;
use futures::{channel::mpsc, executor::block_on, SinkExt, StreamExt};
use structopt::StructOpt;
use anyhow::Context as _;
use clap::Parser;
use tokio::sync::watch;
use zksync_config::configs::PrometheusConfig;
use zksync_contract_verifier_lib::ContractVerifier;
Expand All @@ -12,27 +11,31 @@ use zksync_queued_job_processor::JobProcessor;
use zksync_utils::wait_for_tasks::ManagedTasks;
use zksync_vlog::prometheus::PrometheusExporterConfig;

#[derive(StructOpt)]
#[structopt(name = "ZKsync contract code verifier", author = "Matter Labs")]
#[derive(Debug, Parser)]
#[command(name = "ZKsync contract code verifier", author = "Matter Labs")]
struct Opt {
/// Number of jobs to process. If None, runs indefinitely.
#[structopt(long)]
#[arg(long)]
jobs_number: Option<usize>,
/// Path to the configuration file.
#[structopt(long)]
config_path: Option<std::path::PathBuf>,
#[arg(long)]
config_path: Option<PathBuf>,
/// Path to the secrets file.
#[structopt(long)]
secrets_path: Option<std::path::PathBuf>,
#[arg(long)]
secrets_path: Option<PathBuf>,
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
let opt = Opt::from_args();
let opt = Opt::parse();

let general_config = load_general_config(opt.config_path).context("general config")?;
let database_secrets = load_database_secrets(opt.secrets_path).context("database secrets")?;
let observability_config = general_config
.observability
.context("ObservabilityConfig")?;
let _observability_guard = observability_config.install()?;

let database_secrets = load_database_secrets(opt.secrets_path).context("database secrets")?;
let verifier_config = general_config
.contract_verifier
.context("ContractVerifierConfig")?;
Expand All @@ -46,33 +49,13 @@ async fn main() -> anyhow::Result<()> {
.context("Master DB URL is absent")?,
)
.build()
.await
.unwrap();

let observability_config = general_config
.observability
.context("ObservabilityConfig")?;

let _observability_guard = observability_config.install()?;
.await?;

let (stop_sender, stop_receiver) = watch::channel(false);
let (stop_signal_sender, mut stop_signal_receiver) = mpsc::channel(256);
{
let stop_signal_sender = RefCell::new(stop_signal_sender.clone());
ctrlc::set_handler(move || {
let mut sender = stop_signal_sender.borrow_mut();
block_on(sender.send(true)).expect("Ctrl+C signal send");
})
.expect("Error setting Ctrl+C handler");
}

let contract_verifier = ContractVerifier::new(verifier_config.compilation_timeout(), pool)
.await
.context("failed initializing contract verifier")?;
let tasks = vec![
// TODO PLA-335: Leftovers after the prover DB split.
// The prover connection pool is not used by the contract verifier, but we need to pass it
// since `JobProcessor` trait requires it.
tokio::spawn(contract_verifier.run(stop_receiver.clone(), opt.jobs_number)),
tokio::spawn(
PrometheusExporterConfig::pull(prometheus_config.listener_port).run(stop_receiver),
Expand All @@ -82,7 +65,7 @@ async fn main() -> anyhow::Result<()> {
let mut tasks = ManagedTasks::new(tasks);
tokio::select! {
() = tasks.wait_single() => {},
_ = stop_signal_receiver.next() => {
_ = tokio::signal::ctrl_c() => {
tracing::info!("Stop signal received, shutting down");
},
};
Expand Down
112 changes: 87 additions & 25 deletions core/lib/contract_verifier/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use zksync_dal::{contract_verification_dal::DeployedContractData, ConnectionPool
use zksync_queued_job_processor::{async_trait, JobProcessor};
use zksync_types::{
contract_verification_api::{
CompilationArtifacts, CompilerType, VerificationIncomingRequest, VerificationInfo,
self as api, CompilationArtifacts, VerificationIncomingRequest, VerificationInfo,
VerificationRequest,
},
Address, CONTRACT_DEPLOYER_ADDRESS,
Expand All @@ -35,6 +35,65 @@ mod resolver;
#[cfg(test)]
mod tests;

#[derive(Debug)]
struct ZkCompilerVersions {
/// Version of the base / non-ZK compiler.
pub base: String,
/// Version of the ZK compiler.
pub zk: String,
}

/// Internal counterpart of `ContractVersions` from API that encompasses all supported compilation modes.
#[derive(Debug)]
enum VersionedCompiler {
Solc(String),
#[allow(dead_code)] // TODO (EVM-864): add vyper support
Vyper(String),
ZkSolc(ZkCompilerVersions),
ZkVyper(ZkCompilerVersions),
}

impl From<api::CompilerVersions> for VersionedCompiler {
fn from(versions: api::CompilerVersions) -> Self {
match versions {
api::CompilerVersions::Solc {
compiler_solc_version,
compiler_zksolc_version: None,
} => Self::Solc(compiler_solc_version),

api::CompilerVersions::Solc {
compiler_solc_version,
compiler_zksolc_version: Some(zk),
} => Self::ZkSolc(ZkCompilerVersions {
base: compiler_solc_version,
zk,
}),

api::CompilerVersions::Vyper {
compiler_vyper_version,
compiler_zkvyper_version: None,
} => Self::Vyper(compiler_vyper_version),

api::CompilerVersions::Vyper {
compiler_vyper_version,
compiler_zkvyper_version: Some(zk),
} => Self::ZkVyper(ZkCompilerVersions {
base: compiler_vyper_version,
zk,
}),
}
}
}

impl VersionedCompiler {
fn expected_bytecode_kind(&self) -> BytecodeMarker {
match self {
Self::Solc(_) | Self::Vyper(_) => BytecodeMarker::Evm,
Self::ZkSolc(_) | Self::ZkVyper(_) => BytecodeMarker::EraVm,
}
}
}

enum ConstructorArgs {
Check(Vec<u8>),
Ignore,
Expand Down Expand Up @@ -112,19 +171,19 @@ impl ContractVerifier {
let mut transaction = storage.start_transaction().await?;
transaction
.contract_verification_dal()
.set_zksolc_versions(supported_versions.zksolc)
.set_zksolc_versions(&supported_versions.zksolc)
.await?;
transaction
.contract_verification_dal()
.set_solc_versions(supported_versions.solc)
.set_solc_versions(&supported_versions.solc)
.await?;
transaction
.contract_verification_dal()
.set_zkvyper_versions(supported_versions.zkvyper)
.set_zkvyper_versions(&supported_versions.zkvyper)
.await?;
transaction
.contract_verification_dal()
.set_vyper_versions(supported_versions.vyper)
.set_vyper_versions(&supported_versions.vyper)
.await?;
transaction.commit().await?;
Ok(())
Expand Down Expand Up @@ -214,13 +273,11 @@ impl ContractVerifier {

async fn compile_zksolc(
&self,
version: &ZkCompilerVersions,
req: VerificationIncomingRequest,
) -> Result<CompilationArtifacts, ContractVerifierError> {
let zksolc = self
.compiler_resolver
.resolve_zksolc(&req.compiler_versions)
.await?;
tracing::debug!(?zksolc, ?req.compiler_versions, "resolved compiler");
let zksolc = self.compiler_resolver.resolve_zksolc(version).await?;
tracing::debug!(?zksolc, ?version, "resolved compiler");
let input = ZkSolc::build_input(req)?;

time::timeout(self.compilation_timeout, zksolc.compile(input))
Expand All @@ -230,13 +287,11 @@ impl ContractVerifier {

async fn compile_zkvyper(
&self,
version: &ZkCompilerVersions,
req: VerificationIncomingRequest,
) -> Result<CompilationArtifacts, ContractVerifierError> {
let zkvyper = self
.compiler_resolver
.resolve_zkvyper(&req.compiler_versions)
.await?;
tracing::debug!(?zkvyper, ?req.compiler_versions, "resolved compiler");
let zkvyper = self.compiler_resolver.resolve_zkvyper(version).await?;
tracing::debug!(?zkvyper, ?version, "resolved compiler");
let input = ZkVyper::build_input(req)?;
time::timeout(self.compilation_timeout, zkvyper.compile(input))
.await
Expand All @@ -245,12 +300,10 @@ impl ContractVerifier {

async fn compile_solc(
&self,
version: &str,
req: VerificationIncomingRequest,
) -> Result<CompilationArtifacts, ContractVerifierError> {
let solc = self
.compiler_resolver
.resolve_solc(req.compiler_versions.compiler_version())
.await?;
let solc = self.compiler_resolver.resolve_solc(version).await?;
tracing::debug!(?solc, ?req.compiler_versions, "resolved compiler");
let input = Solc::build_input(req)?;

Expand All @@ -276,15 +329,24 @@ impl ContractVerifier {
return Err(err.into());
}

match (bytecode_marker, compiler_type) {
(BytecodeMarker::EraVm, CompilerType::Solc) => self.compile_zksolc(req).await,
(BytecodeMarker::EraVm, CompilerType::Vyper) => self.compile_zkvyper(req).await,
(BytecodeMarker::Evm, CompilerType::Solc) => self.compile_solc(req).await,
(BytecodeMarker::Evm, CompilerType::Vyper) => {
// TODO: add vyper support
let compiler = VersionedCompiler::from(req.compiler_versions.clone());
if compiler.expected_bytecode_kind() != bytecode_marker {
let err = anyhow::anyhow!(
"bytecode kind expected by compiler {compiler:?} differs from the actual bytecode kind \
of the verified contract ({bytecode_marker:?})",
);
return Err(err.into());
}

match &compiler {
VersionedCompiler::Solc(version) => self.compile_solc(version, req).await,
VersionedCompiler::Vyper(_) => {
// TODO (EVM-864): add vyper support
let err = anyhow::anyhow!("vyper toolchain is not yet supported for EVM contracts");
return Err(err.into());
}
VersionedCompiler::ZkSolc(version) => self.compile_zksolc(version, req).await,
VersionedCompiler::ZkVyper(version) => self.compile_zkvyper(version, req).await,
}
}

Expand Down
Loading

0 comments on commit 4509179

Please sign in to comment.