Skip to content

Commit

Permalink
feat(merkle tree): Expose Merkle tree API (#209)
Browse files Browse the repository at this point in the history
# What ❔

Expose API allowing to query Merkle proofs for a list of hashed keys at
a specific tree version (= L1 batch number).

## Why ❔

- This is one of components for recovering a node from a snapshot; it
will be used to authenticate snapshot chunks.
- In the future, it could be used for `eth_getProof`.

## 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.
- [ ] Documentation comments have been added / updated.
- [x] Code has been formatted via `zk fmt` and `zk lint`.
  • Loading branch information
slowli committed Oct 17, 2023
1 parent f79ee0c commit 4010c7e
Show file tree
Hide file tree
Showing 13 changed files with 679 additions and 118 deletions.
32 changes: 27 additions & 5 deletions core/lib/config/src/configs/api.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
/// External uses
use anyhow::Context as _;
use serde::Deserialize;
/// Built-in uses
use std::net::SocketAddr;
use std::time::Duration;
// Local uses

use std::{net::SocketAddr, time::Duration};

use super::envy_load;
pub use crate::configs::PrometheusConfig;
use zksync_basic_types::H256;
Expand All @@ -20,6 +18,8 @@ pub struct ApiConfig {
pub prometheus: PrometheusConfig,
/// Configuration options for the Health check.
pub healthcheck: HealthCheckConfig,
/// Configuration options for Merkle tree API.
pub merkle_tree: MerkleTreeApiConfig,
}

impl ApiConfig {
Expand All @@ -30,6 +30,7 @@ impl ApiConfig {
.context("ContractVerificationApiConfig")?,
prometheus: PrometheusConfig::from_env().context("PrometheusConfig")?,
healthcheck: HealthCheckConfig::from_env().context("HealthCheckConfig")?,
merkle_tree: MerkleTreeApiConfig::from_env().context("MerkleTreeApiConfig")?,
})
}
}
Expand Down Expand Up @@ -225,6 +226,25 @@ impl ContractVerificationApiConfig {
}
}

/// Configuration for the Merkle tree API.
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct MerkleTreeApiConfig {
/// Port to bind the Merkle tree API server to.
#[serde(default = "MerkleTreeApiConfig::default_port")]
pub port: u16,
}

impl MerkleTreeApiConfig {
const fn default_port() -> u16 {
3_072
}

/// Loads configuration from env variables.
pub fn from_env() -> anyhow::Result<Self> {
envy_load("merkle_tree_api", "API_MERKLE_TREE_")
}
}

#[cfg(test)]
mod tests {
use std::net::IpAddr;
Expand Down Expand Up @@ -280,6 +300,7 @@ mod tests {
push_interval_ms: Some(100),
},
healthcheck: HealthCheckConfig { port: 8081 },
merkle_tree: MerkleTreeApiConfig { port: 8082 },
}
}

Expand Down Expand Up @@ -321,6 +342,7 @@ mod tests {
API_PROMETHEUS_PUSHGATEWAY_URL="http://127.0.0.1:9091"
API_PROMETHEUS_PUSH_INTERVAL_MS=100
API_HEALTHCHECK_PORT=8081
API_MERKLE_TREE_PORT=8082
"#;
lock.set_env(config);

Expand Down
58 changes: 56 additions & 2 deletions core/lib/merkle_tree/src/domain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use rayon::{ThreadPool, ThreadPoolBuilder};

use crate::{
storage::{MerkleTreeColumnFamily, PatchSet, Patched, RocksDBWrapper},
types::{Key, Root, TreeInstruction, TreeLogEntry, ValueHash, TREE_DEPTH},
BlockOutput, HashTree, MerkleTree,
types::{Key, Root, TreeEntryWithProof, TreeInstruction, TreeLogEntry, ValueHash, TREE_DEPTH},
BlockOutput, HashTree, MerkleTree, NoVersionError,
};
use zksync_crypto::hasher::blake2::Blake2Hasher;
use zksync_storage::RocksDB;
Expand Down Expand Up @@ -98,6 +98,13 @@ impl ZkSyncTree {
}
}

/// Returns a readonly handle to the tree. The handle **does not** see uncommitted changes to the tree,
/// only ones flushed to RocksDB.
pub fn reader(&self) -> ZkSyncTreeReader {
let db = self.tree.db.inner().clone();
ZkSyncTreeReader(MerkleTree::new(db))
}

/// Sets the chunk size for multi-get operations. The requested keys will be split
/// into chunks of this size and requested in parallel using `rayon`. Setting chunk size
/// to a large value (e.g., `usize::MAX`) will effectively disable parallelism.
Expand Down Expand Up @@ -373,3 +380,50 @@ impl ZkSyncTree {
self.tree.db.reset();
}
}

/// Readonly handle to a [`ZkSyncTree`].
#[derive(Debug)]
pub struct ZkSyncTreeReader(MerkleTree<'static, RocksDBWrapper>);

// While cloning `MerkleTree` is logically unsound, cloning a reader is reasonable since it is readonly.
impl Clone for ZkSyncTreeReader {
fn clone(&self) -> Self {
Self(MerkleTree::new(self.0.db.clone()))
}
}

impl ZkSyncTreeReader {
/// Returns the current root hash of this tree.
pub fn root_hash(&self) -> ValueHash {
self.0.latest_root_hash()
}

/// Returns the next L1 batch number that should be processed by the tree.
#[allow(clippy::missing_panics_doc)]
pub fn next_l1_batch_number(&self) -> L1BatchNumber {
let number = self.0.latest_version().map_or(0, |version| {
u32::try_from(version + 1).expect("integer overflow for L1 batch number")
});
L1BatchNumber(number)
}

/// Returns the number of leaves in the tree.
pub fn leaf_count(&self) -> u64 {
self.0.latest_root().leaf_count()
}

/// Reads entries together with Merkle proofs with the specified keys from the tree. The entries are returned
/// in the same order as requested.
///
/// # Errors
///
/// Returns an error if the tree `version` is missing.
pub fn entries_with_proofs(
&self,
l1_batch_number: L1BatchNumber,
keys: &[Key],
) -> Result<Vec<TreeEntryWithProof>, NoVersionError> {
let version = u64::from(l1_batch_number.0);
self.0.entries_with_proofs(version, keys)
}
}
5 changes: 5 additions & 0 deletions core/lib/merkle_tree/src/storage/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,11 @@ impl<DB: Database> Patched<DB> {
.map_or_else(Vec::new, |patch| patch.roots.keys().copied().collect())
}

/// Provides readonly access to the wrapped DB.
pub(crate) fn inner(&self) -> &DB {
&self.inner
}

/// Provides access to the wrapped DB. Should not be used to mutate DB data.
pub(crate) fn inner_mut(&mut self) -> &mut DB {
&mut self.inner
Expand Down
2 changes: 2 additions & 0 deletions core/lib/zksync_core/src/api_server/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Everywhere in this module the word "block" actually means "miniblock".

pub mod contract_verification;
pub mod execution_sandbox;
pub mod healthcheck;
pub mod tree;
pub mod tx_sender;
pub mod web3;
24 changes: 24 additions & 0 deletions core/lib/zksync_core/src/api_server/tree/metrics.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//! Metrics for the Merkle tree API.

use vise::{Buckets, EncodeLabelSet, EncodeLabelValue, Family, Histogram, Metrics, Unit};

use std::time::Duration;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EncodeLabelValue, EncodeLabelSet)]
#[metrics(label = "method", rename_all = "snake_case")]
pub(super) enum MerkleTreeApiMethod {
Info,
GetProofs,
}

/// Metrics for Merkle tree API.
#[derive(Debug, Metrics)]
#[metrics(prefix = "server_merkle_tree_api")]
pub(super) struct MerkleTreeApiMetrics {
/// Server latency of the Merkle tree API methods.
#[metrics(buckets = Buckets::LATENCIES, unit = Unit::Seconds)]
pub latency: Family<MerkleTreeApiMethod, Histogram<Duration>>,
}

#[vise::register]
pub(super) static API_METRICS: vise::Global<MerkleTreeApiMetrics> = vise::Global::new();
Loading

0 comments on commit 4010c7e

Please sign in to comment.