Skip to content

Commit

Permalink
Cache supported compiler versions
Browse files Browse the repository at this point in the history
  • Loading branch information
slowli committed Nov 6, 2024
1 parent 4a2564f commit 411bcad
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 25 deletions.
5 changes: 5 additions & 0 deletions core/node/contract_verification_server/src/api_decl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,23 @@ use std::sync::Arc;
use tower_http::cors::CorsLayer;
use zksync_dal::{ConnectionPool, Core};

use crate::cache::SupportedCompilersCache;

#[derive(Debug, Clone)]
pub(crate) struct RestApi {
pub(crate) master_connection_pool: ConnectionPool<Core>,
pub(crate) replica_connection_pool: ConnectionPool<Core>,
pub(crate) supported_compilers: Arc<SupportedCompilersCache>,
}

impl RestApi {
pub fn new(
master_connection_pool: ConnectionPool<Core>,
replica_connection_pool: ConnectionPool<Core>,
) -> Self {
let supported_compilers = SupportedCompilersCache::new(replica_connection_pool.clone());
Self {
supported_compilers: Arc::new(supported_compilers),
master_connection_pool,
replica_connection_pool,
}
Expand Down
51 changes: 26 additions & 25 deletions core/node/contract_verification_server/src/api_impl.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{iter, sync::Arc};
use std::{collections::HashSet, iter, sync::Arc};

use anyhow::Context as _;
use axum::{
Expand All @@ -21,6 +21,7 @@ use super::{api_decl::RestApi, metrics::METRICS};
#[derive(Debug)]
pub(crate) enum ApiError {
IncorrectCompilerVersions,
UnsupportedCompilerVersions,
MissingZkCompilerVersion,
BogusZkCompilerVersion,
NoDeployedContract,
Expand All @@ -45,6 +46,7 @@ impl ApiError {
pub fn message(&self) -> &'static str {
match self {
Self::IncorrectCompilerVersions => "incorrect compiler versions",
Self::UnsupportedCompilerVersions => "unsupported compiler versions",
Self::MissingZkCompilerVersion => "missing zk compiler version for EraVM bytecode",
Self::BogusZkCompilerVersion => "zk compiler version specified for EVM bytecode",
Self::NoDeployedContract => "There is no deployed contract on this address",
Expand All @@ -59,10 +61,13 @@ impl IntoResponse for ApiError {
fn into_response(self) -> Response {
let status_code = match &self {
Self::IncorrectCompilerVersions
| Self::UnsupportedCompilerVersions
| Self::MissingZkCompilerVersion
| Self::BogusZkCompilerVersion
| Self::NoDeployedContract => StatusCode::BAD_REQUEST,

Self::RequestNotFound | Self::VerificationInfoNotFound => StatusCode::NOT_FOUND,

Self::Internal(err) => {
// Do not expose the error details to the client, but log it.
tracing::warn!("Internal error: {err:#}");
Expand Down Expand Up @@ -111,6 +116,14 @@ impl RestApi {
let method_latency = METRICS.call[&"contract_verification"].start();
Self::validate_contract_verification_query(&request)?;

let is_compilation_supported = self_
.supported_compilers
.get(|supported| supported.contain(&request.compiler_versions))
.await?;
if !is_compilation_supported {
return Err(ApiError::UnsupportedCompilerVersions);
}

let mut storage = self_
.master_connection_pool
.connection_tagged("api")
Expand Down Expand Up @@ -158,56 +171,44 @@ impl RestApi {
}

#[tracing::instrument(skip(self_))]
pub async fn zksolc_versions(State(self_): State<Arc<Self>>) -> ApiResult<Vec<String>> {
pub async fn zksolc_versions(State(self_): State<Arc<Self>>) -> ApiResult<HashSet<String>> {
let method_latency = METRICS.call[&"contract_verification_zksolc_versions"].start();
let versions = self_
.replica_connection_pool
.connection_tagged("api")
.await?
.contract_verification_dal()
.get_zksolc_versions()
.supported_compilers
.get(|supported| supported.zksolc.clone())
.await?;
method_latency.observe();
Ok(Json(versions))
}

#[tracing::instrument(skip(self_))]
pub async fn solc_versions(State(self_): State<Arc<Self>>) -> ApiResult<Vec<String>> {
pub async fn solc_versions(State(self_): State<Arc<Self>>) -> ApiResult<HashSet<String>> {
let method_latency = METRICS.call[&"contract_verification_solc_versions"].start();
let versions = self_
.replica_connection_pool
.connection_tagged("api")
.await?
.contract_verification_dal()
.get_solc_versions()
.supported_compilers
.get(|supported| supported.solc.clone())
.await?;
method_latency.observe();
Ok(Json(versions))
}

#[tracing::instrument(skip(self_))]
pub async fn zkvyper_versions(State(self_): State<Arc<Self>>) -> ApiResult<Vec<String>> {
pub async fn zkvyper_versions(State(self_): State<Arc<Self>>) -> ApiResult<HashSet<String>> {
let method_latency = METRICS.call[&"contract_verification_zkvyper_versions"].start();
let versions = self_
.replica_connection_pool
.connection_tagged("api")
.await?
.contract_verification_dal()
.get_zkvyper_versions()
.supported_compilers
.get(|supported| supported.zkvyper.clone())
.await?;
method_latency.observe();
Ok(Json(versions))
}

#[tracing::instrument(skip(self_))]
pub async fn vyper_versions(State(self_): State<Arc<Self>>) -> ApiResult<Vec<String>> {
pub async fn vyper_versions(State(self_): State<Arc<Self>>) -> ApiResult<HashSet<String>> {
let method_latency = METRICS.call[&"contract_verification_vyper_versions"].start();
let versions = self_
.replica_connection_pool
.connection_tagged("api")
.await?
.contract_verification_dal()
.get_vyper_versions()
.supported_compilers
.get(|supported| supported.vyper.clone())
.await?;
method_latency.observe();
Ok(Json(versions))
Expand Down
122 changes: 122 additions & 0 deletions core/node/contract_verification_server/src/cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
use std::{
collections::HashSet,
time::{Duration, Instant},
};

use tokio::sync::RwLock;
use zksync_dal::{Connection, ConnectionPool, Core, CoreDal, DalError};
use zksync_types::contract_verification_api::CompilerVersions;

/// Compiler versions supported by the contract verifier.
#[derive(Debug, Clone)]
pub(crate) struct SupportedCompilerVersions {
pub solc: HashSet<String>,
pub zksolc: HashSet<String>,
pub vyper: HashSet<String>,
pub zkvyper: HashSet<String>,
}

impl SupportedCompilerVersions {
/// Checks whether the supported compilers include ones specified in a request.
pub fn contain(&self, versions: &CompilerVersions) -> bool {
match versions {
CompilerVersions::Solc {
compiler_solc_version,
compiler_zksolc_version,
} => {
self.solc.contains(compiler_solc_version)
&& compiler_zksolc_version
.as_ref()
.map_or(true, |ver| self.zksolc.contains(ver))
}
CompilerVersions::Vyper {
compiler_vyper_version,
compiler_zkvyper_version,
} => {
self.vyper.contains(compiler_vyper_version)
&& compiler_zkvyper_version
.as_ref()
.map_or(true, |ver| self.zkvyper.contains(ver))
}
}
}
}

impl SupportedCompilerVersions {
async fn new(connection: &mut Connection<'_, Core>) -> Result<Self, DalError> {
let solc = connection
.contract_verification_dal()
.get_solc_versions()
.await?;
let zksolc = connection
.contract_verification_dal()
.get_zksolc_versions()
.await?;
let vyper = connection
.contract_verification_dal()
.get_vyper_versions()
.await?;
let zkvyper = connection
.contract_verification_dal()
.get_zkvyper_versions()
.await?;
Ok(Self {
solc: solc.into_iter().collect(),
zksolc: zksolc.into_iter().collect(),
vyper: vyper.into_iter().collect(),
zkvyper: zkvyper.into_iter().collect(),
})
}
}

/// Cache for compiler versions supported by the contract verifier.
#[derive(Debug)]
pub(crate) struct SupportedCompilersCache {
connection_pool: ConnectionPool<Core>,
inner: RwLock<Option<(SupportedCompilerVersions, Instant)>>,
}

impl SupportedCompilersCache {
const CACHE_UPDATE_INTERVAL: Duration = Duration::from_secs(10);

pub fn new(connection_pool: ConnectionPool<Core>) -> Self {
Self {
connection_pool,
inner: RwLock::new(None),
}
}

fn get_cached<R>(
cache: Option<&(SupportedCompilerVersions, Instant)>,
action: impl FnOnce(&SupportedCompilerVersions) -> R,
) -> Option<R> {
cache.and_then(|(versions, updated_at)| {
(updated_at.elapsed() <= Self::CACHE_UPDATE_INTERVAL).then(|| action(versions))
})
}

pub async fn get<R>(
&self,
action: impl Fn(&SupportedCompilerVersions) -> R,
) -> Result<R, DalError> {
let output = Self::get_cached(self.inner.read().await.as_ref(), &action);
if let Some(output) = output {
return Ok(output);
}

// We don't want to hold an exclusive lock while querying Postgres.
let supported = {
let mut connection = self.connection_pool.connection_tagged("api").await?;
let mut db_transaction = connection
.transaction_builder()?
.set_readonly()
.build()
.await?;
SupportedCompilerVersions::new(&mut db_transaction).await?
};
let output = action(&supported);
// Another task may have written to the cache already, but we should be fine with updating it again.
*self.inner.write().await = Some((supported, Instant::now()));
Ok(output)
}
}
1 change: 1 addition & 0 deletions core/node/contract_verification_server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use self::api_decl::RestApi;

mod api_decl;
mod api_impl;
mod cache;
mod metrics;
#[cfg(test)]
mod tests;
Expand Down

0 comments on commit 411bcad

Please sign in to comment.