Skip to content

Commit

Permalink
feat: Use config for max number of circuits (matter-labs#1573)
Browse files Browse the repository at this point in the history
## What ❔

⚠️ This PR requires a new config variable ⚠️ 

Use config for managing the maximal number of circuits

## Why ❔

<!-- Why are these changes done? What goal do they contribute to? What
are the principles behind them? -->
<!-- Example: PR templates ensure PR reviewers, observers, and future
iterators are in context about the evolution of repos. -->

## Checklist

<!-- Check your PR fulfills the following items. -->
<!-- For draft PRs check the boxes as you complete them. -->

- [ ] PR title corresponds to the body of PR (we generate changelog
entries from PRs).
- [ ] Tests for the changes have been added / updated.
- [ ] Documentation comments have been added / updated.
- [ ] Code has been formatted via `zk fmt` and `zk lint`.
- [ ] Spellcheck has been run via `zk spellcheck`.
- [ ] Linkcheck has been run via `zk linkcheck`.

---------

Co-authored-by: perekopskiy <[email protected]>
  • Loading branch information
StanislavBreadless and perekopskiy authored Apr 4, 2024
1 parent 90eb9d8 commit 9fcb87e
Show file tree
Hide file tree
Showing 10 changed files with 84 additions and 52 deletions.
6 changes: 6 additions & 0 deletions core/lib/config/src/configs/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,11 @@ pub struct StateKeeperConfig {
/// Number of keys that is processed by enum_index migration in State Keeper each L1 batch.
pub enum_index_migration_chunk_size: Option<usize>,

/// The maximal number of circuits that a batch can support.
/// Note, that this number corresponds to the "base layer" circuits, i.e. it does not include
/// the recursion layers' circuits.
pub max_circuits_per_batch: usize,

// Base system contract hashes, required only for generating genesis config.
// #PLA-811
#[deprecated(note = "Use GenesisConfig::bootloader_hash instead")]
Expand Down Expand Up @@ -199,6 +204,7 @@ impl StateKeeperConfig {
virtual_blocks_interval: 1,
virtual_blocks_per_miniblock: 1,
enum_index_migration_chunk_size: None,
max_circuits_per_batch: 24100,
bootloader_hash: None,
default_aa_hash: None,
l1_batch_commit_data_generator_mode: L1BatchCommitDataGeneratorMode::Rollup,
Expand Down
1 change: 1 addition & 0 deletions core/lib/config/src/testonly.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ impl Distribution<configs::chain::StateKeeperConfig> for EncodeDist {
virtual_blocks_interval: self.sample(rng),
virtual_blocks_per_miniblock: self.sample(rng),
enum_index_migration_chunk_size: self.sample(rng),
max_circuits_per_batch: self.sample(rng),
// These values are not involved into files serialization skip them
fee_account_addr: None,
bootloader_hash: None,
Expand Down
1 change: 1 addition & 0 deletions core/lib/env_config/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ mod tests {
"0x0100055b041eb28aff6e3a6e0f37c31fd053fc9ef142683b05e5f0aee6934066",
)),
l1_batch_commit_data_generator_mode,
max_circuits_per_batch: 24100,
}
}

Expand Down
20 changes: 20 additions & 0 deletions core/lib/multivm/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -387,3 +387,23 @@ pub fn get_max_batch_gas_limit(version: VmVersion) -> u64 {
VmVersion::Vm1_4_2 => crate::vm_1_4_2::constants::BLOCK_GAS_LIMIT as u64,
}
}

pub fn get_max_batch_base_layer_circuits(version: VmVersion) -> usize {
match version {
VmVersion::M5WithRefunds
| VmVersion::M5WithoutRefunds
| VmVersion::M6Initial
| VmVersion::M6BugWithCompressionFixed
| VmVersion::Vm1_3_2
| VmVersion::VmVirtualBlocks
| VmVersion::VmVirtualBlocksRefundsEnhancement
| VmVersion::VmBoojumIntegration
| VmVersion::Vm1_4_1
| VmVersion::Vm1_4_2 => {
// For pre-v1.4.2 the maximal number of circuits has not been calculated, but since
// these are used only for replaying transactions, we'll reuse the same value as for v1.4.2.
// We avoid providing `0` for the old versions to avoid potential errors when working with old versions.
crate::vm_1_4_2::constants::MAX_BASE_LAYER_CIRCUITS
}
}
}
2 changes: 2 additions & 0 deletions core/lib/multivm/src/versions/vm_1_4_2/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ pub(crate) const BOOTLOADER_BATCH_TIP_OVERHEAD: u32 = 170_000_000;
pub(crate) const BOOTLOADER_BATCH_TIP_CIRCUIT_STATISTICS_OVERHEAD: u32 = 5000;
pub(crate) const BOOTLOADER_BATCH_TIP_METRICS_SIZE_OVERHEAD: u32 = 1500;

pub(crate) const MAX_BASE_LAYER_CIRCUITS: usize = 24100;

/// The size of the bootloader memory in bytes which is used by the protocol.
/// While the maximal possible size is a lot higher, we restrict ourselves to a certain limit to reduce
/// the requirements on RAM.
Expand Down
4 changes: 4 additions & 0 deletions core/lib/protobuf_config/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ impl ProtoRepr for proto::StateKeeper {
.map(|x| x.try_into())
.transpose()
.context("enum_index_migration_chunk_size")?,
max_circuits_per_batch: required(&self.max_circuits_per_batch)
.and_then(|x| Ok((*x).try_into()?))
.context("max_circuits_per_batch")?,

// We need these values only for instantiating configs from environmental variables, so it's not
// needed during the initialization from files
Expand Down Expand Up @@ -122,6 +125,7 @@ impl ProtoRepr for proto::StateKeeper {
.enum_index_migration_chunk_size
.as_ref()
.map(|x| (*x).try_into().unwrap()),
max_circuits_per_batch: Some(this.max_circuits_per_batch.try_into().unwrap()),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions core/lib/protobuf_config/src/proto/chain.proto
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ message StateKeeper {
optional uint32 virtual_blocks_interval = 23; // required
optional uint32 virtual_blocks_per_miniblock = 24; // required
optional uint64 enum_index_migration_chunk_size = 26; // optional
optional uint64 max_circuits_per_batch = 27; // required
}

message OperationsManager {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,93 +1,85 @@
use std::fmt;

use multivm::utils::circuit_statistics_bootloader_batch_tip_overhead;
use multivm::utils::{
circuit_statistics_bootloader_batch_tip_overhead, get_max_batch_base_layer_circuits,
};
use zksync_config::configs::chain::StateKeeperConfig;
use zksync_types::{tx::tx_execution_info::ExecutionMetrics, ProtocolVersionId};
use zksync_types::ProtocolVersionId;

// Local uses
use crate::state_keeper::seal_criteria::{SealCriterion, SealData, SealResolution};

// Collected vm execution metrics should fit into geometry limits.
// Otherwise witness generation will fail and proof won't be generated.

#[derive(Debug, Default)]
/// Checks whether we should exclude the transaction because we don't have enough circuits for it.
#[derive(Debug)]
pub struct CircuitsCriterion;

trait MetricExtractor {
const PROM_METRIC_CRITERION_NAME: &'static str;
fn limit_per_block(protocol_version: ProtocolVersionId) -> usize;
fn extract(metric: &ExecutionMetrics) -> usize;
}

impl<T> SealCriterion for T
where
T: MetricExtractor + fmt::Debug + Send + Sync + 'static,
{
impl SealCriterion for CircuitsCriterion {
fn should_seal(
&self,
config: &StateKeeperConfig,
_block_open_timestamp_ms: u128,
_tx_count: usize,
block_data: &SealData,
tx_data: &SealData,
protocol_version_id: ProtocolVersionId,
protocol_version: ProtocolVersionId,
) -> SealResolution {
let reject_bound = (T::limit_per_block(protocol_version_id) as f64
let max_allowed_base_layer_circuits =
get_max_batch_base_layer_circuits(protocol_version.into());
assert!(
config.max_circuits_per_batch <= max_allowed_base_layer_circuits,
"Configured max_circuits_per_batch ({}) must be lower than the constant MAX_BASE_LAYER_CIRCUITS={} for protocol version {}",
config.max_circuits_per_batch, max_allowed_base_layer_circuits, protocol_version as u16
);

let batch_tip_circuit_overhead =
circuit_statistics_bootloader_batch_tip_overhead(protocol_version.into());

// Double checking that it is possible to seal batches
assert!(
batch_tip_circuit_overhead < config.max_circuits_per_batch,
"Invalid circuit criteria"
);

let reject_bound = (config.max_circuits_per_batch as f64
* config.reject_tx_at_geometry_percentage)
.round();
let close_bound = (T::limit_per_block(protocol_version_id) as f64
.round() as usize;
let include_and_seal_bound = (config.max_circuits_per_batch as f64
* config.close_block_at_geometry_percentage)
.round();
.round() as usize;

if T::extract(&tx_data.execution_metrics)
+ circuit_statistics_bootloader_batch_tip_overhead(protocol_version_id.into())
> reject_bound as usize
{
let used_circuits_tx = tx_data.execution_metrics.circuit_statistic.total();
let used_circuits_batch = block_data.execution_metrics.circuit_statistic.total();

if used_circuits_tx + batch_tip_circuit_overhead >= reject_bound {
SealResolution::Unexecutable("ZK proof cannot be generated for a transaction".into())
} else if T::extract(&block_data.execution_metrics)
+ circuit_statistics_bootloader_batch_tip_overhead(protocol_version_id.into())
>= T::limit_per_block(protocol_version_id)
} else if used_circuits_batch + batch_tip_circuit_overhead >= config.max_circuits_per_batch
{
SealResolution::ExcludeAndSeal
} else if T::extract(&block_data.execution_metrics)
+ circuit_statistics_bootloader_batch_tip_overhead(protocol_version_id.into())
> close_bound as usize
{
} else if used_circuits_batch + batch_tip_circuit_overhead >= include_and_seal_bound {
SealResolution::IncludeAndSeal
} else {
SealResolution::NoSeal
}
}

fn prom_criterion_name(&self) -> &'static str {
T::PROM_METRIC_CRITERION_NAME
}
}

impl MetricExtractor for CircuitsCriterion {
const PROM_METRIC_CRITERION_NAME: &'static str = "circuits";

fn limit_per_block(_protocol_version_id: ProtocolVersionId) -> usize {
const MAX_NUMBER_OF_CIRCUITS: usize = 24100;

MAX_NUMBER_OF_CIRCUITS
}

fn extract(metrics: &ExecutionMetrics) -> usize {
metrics.circuit_statistic.total()
"circuits_criterion"
}
}

#[cfg(test)]
mod tests {
use zksync_types::circuit::CircuitStatistic;
use zksync_types::{circuit::CircuitStatistic, tx::ExecutionMetrics};

use super::*;

const MAX_CIRCUITS_PER_BATCH: usize = 20_000;

fn get_config() -> StateKeeperConfig {
StateKeeperConfig {
close_block_at_geometry_percentage: 0.9,
reject_tx_at_geometry_percentage: 0.9,
max_circuits_per_batch: MAX_CIRCUITS_PER_BATCH,
..Default::default()
}
}
Expand Down Expand Up @@ -182,7 +174,7 @@ mod tests {
let protocol_version = ProtocolVersionId::latest();
let block_execution_metrics = ExecutionMetrics {
circuit_statistic: CircuitStatistic {
main_vm: (CircuitsCriterion::limit_per_block(protocol_version) / 2) as f32,
main_vm: (MAX_CIRCUITS_PER_BATCH / 2) as f32,
..CircuitStatistic::default()
},
..ExecutionMetrics::default()
Expand All @@ -195,7 +187,7 @@ mod tests {

let block_execution_metrics = ExecutionMetrics {
circuit_statistic: CircuitStatistic {
main_vm: (CircuitsCriterion::limit_per_block(protocol_version)
main_vm: (MAX_CIRCUITS_PER_BATCH
- 1
- circuit_statistics_bootloader_batch_tip_overhead(
ProtocolVersionId::latest().into(),
Expand All @@ -213,7 +205,7 @@ mod tests {

let block_execution_metrics = ExecutionMetrics {
circuit_statistic: CircuitStatistic {
main_vm: CircuitsCriterion::limit_per_block(protocol_version) as f32,
main_vm: MAX_CIRCUITS_PER_BATCH as f32,
..CircuitStatistic::default()
},
..ExecutionMetrics::default()
Expand All @@ -227,7 +219,7 @@ mod tests {

let tx_execution_metrics = ExecutionMetrics {
circuit_statistic: CircuitStatistic {
main_vm: CircuitsCriterion::limit_per_block(protocol_version) as f32
main_vm: MAX_CIRCUITS_PER_BATCH as f32
* config.reject_tx_at_geometry_percentage as f32
+ 1.0,
..CircuitStatistic::default()
Expand Down
4 changes: 4 additions & 0 deletions etc/env/base/chain.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ reject_tx_at_geometry_percentage = 0.95
# Configuration option for block to be sealed in case
# it takes more percentage of the max block capacity than this value.
reject_tx_at_eth_params_percentage = 0.95
# The maximal number of circuits that a batch can support.
# Note, that this number corresponds to the "base layer" circuits, i.e. it does not include
# the recursion layers' circuits.
max_circuits_per_batch = 24100

# Configuration option for block to be sealed in case
# it takes more percentage of the max block gas capacity than this value.
Expand Down
1 change: 1 addition & 0 deletions etc/env/file_based/general.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ state_keeper:
save_call_traces: true
virtual_blocks_interval: 1
virtual_blocks_per_miniblock: 1
max_circuits_per_batch: 24100
mempool:
delay_interval: 100
sync_interval_ms: 10
Expand Down

0 comments on commit 9fcb87e

Please sign in to comment.