Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(vm): Improve snapshot management in batch executor #2513

Merged
5 changes: 2 additions & 3 deletions core/bin/external_node/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ use zksync_reorg_detector::ReorgDetector;
use zksync_shared_metrics::rustc::RUST_METRICS;
use zksync_state::{PostgresStorageCaches, RocksdbStorageOptions};
use zksync_state_keeper::{
seal_criteria::NoopSealer, AsyncRocksdbCache, BatchExecutor, MainBatchExecutor, OutputHandler,
seal_criteria::NoopSealer, AsyncRocksdbCache, BatchExecutor, OutputHandler,
StateKeeperPersistence, TreeWritesPersistence, ZkSyncStateKeeper,
};
use zksync_storage::RocksDB;
Expand Down Expand Up @@ -94,8 +94,7 @@ async fn build_state_keeper(
stop_receiver_clone.changed().await?;
result
}));
let batch_executor_base: Box<dyn BatchExecutor> =
Box::new(MainBatchExecutor::new(save_call_traces, true));
let batch_executor_base = BatchExecutor::new(save_call_traces, true);

let io = ExternalIO::new(
connection_pool,
Expand Down
4 changes: 2 additions & 2 deletions core/bin/external_node/src/node_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ use zksync_node_framework::{
reorg_detector::ReorgDetectorLayer,
sigint::SigintHandlerLayer,
state_keeper::{
external_io::ExternalIOLayer, main_batch_executor::MainBatchExecutorLayer,
batch_executor::BatchExecutorLayer, external_io::ExternalIOLayer,
output_handler::OutputHandlerLayer, StateKeeperLayer,
},
sync_state_updater::SyncStateUpdaterLayer,
Expand Down Expand Up @@ -201,7 +201,7 @@ impl ExternalNodeBuilder {
.api_namespaces()
.contains(&Namespace::Debug);
let main_node_batch_executor_builder_layer =
MainBatchExecutorLayer::new(save_call_traces, OPTIONAL_BYTECODE_COMPRESSION);
BatchExecutorLayer::new(save_call_traces, OPTIONAL_BYTECODE_COMPRESSION);

let rocksdb_options = RocksdbStorageOptions {
block_cache_capacity: self
Expand Down
4 changes: 2 additions & 2 deletions core/bin/zksync_server/src/node_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ use zksync_node_framework::{
query_eth_client::QueryEthClientLayer,
sigint::SigintHandlerLayer,
state_keeper::{
main_batch_executor::MainBatchExecutorLayer, mempool_io::MempoolIOLayer,
batch_executor::BatchExecutorLayer, mempool_io::MempoolIOLayer,
output_handler::OutputHandlerLayer, RocksdbStorageOptions, StateKeeperLayer,
},
tee_verifier_input_producer::TeeVerifierInputProducerLayer,
Expand Down Expand Up @@ -232,7 +232,7 @@ impl MainNodeBuilder {
);
let db_config = try_load_config!(self.configs.db_config);
let main_node_batch_executor_builder_layer =
MainBatchExecutorLayer::new(sk_config.save_call_traces, OPTIONAL_BYTECODE_COMPRESSION);
BatchExecutorLayer::new(sk_config.save_call_traces, OPTIONAL_BYTECODE_COMPRESSION);

let rocksdb_options = RocksdbStorageOptions {
block_cache_capacity: db_config
Expand Down
14 changes: 13 additions & 1 deletion core/lib/multivm/src/interface/traits/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,13 +147,25 @@ pub trait VmFactory<S>: VmInterface {
}

/// Methods of VM requiring history manipulations.
///
/// # Snapshot workflow
///
/// External callers must follow the following snapshot workflow:
///
/// - Each new snapshot created using `make_snapshot()` must be either popped or rolled back before creating the following snapshot.
/// OTOH, it's not required to call either of these methods by the end of VM execution.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this mean? Seems to contradict the line above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the contradiction? We don't create a snapshot at the end of the VM execution, so it may have either 0 or 1 snapshot at the end.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's not required to call either of these methods by the end of VM execution.

This confused me (and still does) but I can understand now that it means that you can have multiple TX after your snapshot and then rollback/forget.

/// - `pop_snapshot_no_rollback()` may be called spuriously, when no snapshot was created. It is a no-op in this case.
///
/// These rules guarantee that at each given moment, a VM instance has <=1 snapshot (unless the VM makes snapshots internally),
slowli marked this conversation as resolved.
Show resolved Hide resolved
/// which may allow additional VM optimizations.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that a low amount of snapshots just avoids allocating a Vec for them. Zero snapshots allow forgetting history, which is important for memory use.

pub trait VmInterfaceHistoryEnabled: VmInterface {
/// Create a snapshot of the current VM state and push it into memory.
fn make_snapshot(&mut self);

/// Roll back VM state to the latest snapshot and destroy the snapshot.
fn rollback_to_the_latest_snapshot(&mut self);

/// Pop the latest snapshot from memory and destroy it.
/// Pop the latest snapshot from memory and destroy it. If there are no snapshots, this should be a no-op
/// (i.e., the VM must not panic in this case).
fn pop_snapshot_no_rollback(&mut self);
}
11 changes: 11 additions & 0 deletions core/lib/multivm/src/versions/shadow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ pub struct ShadowVm<S, T> {
shadow: vm_fast::Vm<ImmutableStorageView<S>>,
}

impl<S, T> ShadowVm<S, T>
where
S: ReadStorage,
T: VmFactory<StorageView<S>>,
{
#[doc(hidden)] // used in batch executor tests
pub fn snapshot_count(&self) -> usize {
slowli marked this conversation as resolved.
Show resolved Hide resolved
self.shadow.snapshot_count()
}
}

impl<S, T> VmFactory<StorageView<S>> for ShadowVm<S, T>
where
S: ReadStorage,
Expand Down
2 changes: 1 addition & 1 deletion core/lib/multivm/src/versions/vm_1_3_2/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,6 @@ impl<S: WriteStorage> VmInterfaceHistoryEnabled for Vm<S, crate::vm_latest::Hist
}

fn pop_snapshot_no_rollback(&mut self) {
self.vm.pop_snapshot_no_rollback()
self.vm.pop_snapshot_no_rollback();
}
}
2 changes: 1 addition & 1 deletion core/lib/multivm/src/versions/vm_1_3_2/vm_instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ impl<H: HistoryMode, S: WriteStorage> VmInstance<S, H> {
/// Removes the latest snapshot without rolling it back.
/// This function expects that there is at least one snapshot present.
pub fn pop_snapshot_no_rollback(&mut self) {
self.snapshots.pop().unwrap();
self.snapshots.pop();
}

/// Returns the amount of gas remaining to the VM.
Expand Down
6 changes: 1 addition & 5 deletions core/lib/multivm/src/versions/vm_1_4_1/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,6 @@ impl<S: WriteStorage> VmInterfaceHistoryEnabled for Vm<S, crate::vm_latest::Hist
self.make_snapshot_inner()
}

/// Rollback vm state to the latest snapshot and destroy the snapshot
fn rollback_to_the_latest_snapshot(&mut self) {
let snapshot = self
.snapshots
Expand All @@ -189,10 +188,7 @@ impl<S: WriteStorage> VmInterfaceHistoryEnabled for Vm<S, crate::vm_latest::Hist
self.rollback_to_snapshot(snapshot);
}

/// Pop the latest snapshot from the memory and destroy it
fn pop_snapshot_no_rollback(&mut self) {
self.snapshots
.pop()
.expect("Snapshot should be created before rolling it back");
self.snapshots.pop();
}
}
10 changes: 2 additions & 8 deletions core/lib/multivm/src/versions/vm_1_4_2/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,14 +178,11 @@ impl<S: WriteStorage, H: HistoryMode> VmFactory<S> for Vm<S, H> {
}
}

/// Methods of vm, which required some history manipulations
impl<S: WriteStorage> VmInterfaceHistoryEnabled for Vm<S, crate::vm_latest::HistoryEnabled> {
/// Create snapshot of current vm state and push it into the memory
fn make_snapshot(&mut self) {
self.make_snapshot_inner()
self.make_snapshot_inner();
}

/// Rollback vm state to the latest snapshot and destroy the snapshot
fn rollback_to_the_latest_snapshot(&mut self) {
let snapshot = self
.snapshots
Expand All @@ -194,10 +191,7 @@ impl<S: WriteStorage> VmInterfaceHistoryEnabled for Vm<S, crate::vm_latest::Hist
self.rollback_to_snapshot(snapshot);
}

/// Pop the latest snapshot from the memory and destroy it
fn pop_snapshot_no_rollback(&mut self) {
self.snapshots
.pop()
.expect("Snapshot should be created before rolling it back");
self.snapshots.pop();
}
}
10 changes: 2 additions & 8 deletions core/lib/multivm/src/versions/vm_boojum_integration/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,14 +173,11 @@ impl<S: WriteStorage, H: HistoryMode> VmFactory<S> for Vm<S, H> {
}
}

/// Methods of vm, which required some history manipulations
impl<S: WriteStorage> VmInterfaceHistoryEnabled for Vm<S, crate::vm_latest::HistoryEnabled> {
/// Create snapshot of current vm state and push it into the memory
fn make_snapshot(&mut self) {
self.make_snapshot_inner()
self.make_snapshot_inner();
}

/// Rollback vm state to the latest snapshot and destroy the snapshot
fn rollback_to_the_latest_snapshot(&mut self) {
let snapshot = self
.snapshots
Expand All @@ -189,10 +186,7 @@ impl<S: WriteStorage> VmInterfaceHistoryEnabled for Vm<S, crate::vm_latest::Hist
self.rollback_to_snapshot(snapshot);
}

/// Pop the latest snapshot from the memory and destroy it
fn pop_snapshot_no_rollback(&mut self) {
self.snapshots
.pop()
.expect("Snapshot should be created before rolling it back");
self.snapshots.pop();
}
}
7 changes: 4 additions & 3 deletions core/lib/multivm/src/versions/vm_fast/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,17 @@ pub struct Vm<S> {
pub(crate) inner: VirtualMachine,
suspended_at: u16,
gas_for_account_validation: u32,

pub(crate) bootloader_state: BootloaderState,

pub(crate) batch_env: L1BatchEnv,
pub(crate) system_env: SystemEnv,

snapshots: Vec<VmSnapshot>,
}

impl<S: ReadStorage> Vm<S> {
pub(crate) fn snapshot_count(&self) -> usize {
self.snapshots.len()
}

fn run(
&mut self,
execution_mode: VmExecutionMode,
Expand Down
8 changes: 1 addition & 7 deletions core/lib/multivm/src/versions/vm_latest/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,14 +229,11 @@ impl<S: WriteStorage, H: HistoryMode> Vm<S, H> {
}
}

/// Methods of vm, which required some history manipulations
impl<S: WriteStorage> VmInterfaceHistoryEnabled for Vm<S, HistoryEnabled> {
/// Create snapshot of current vm state and push it into the memory
fn make_snapshot(&mut self) {
self.make_snapshot_inner()
}

/// Rollback vm state to the latest snapshot and destroy the snapshot
fn rollback_to_the_latest_snapshot(&mut self) {
let snapshot = self
.snapshots
Expand All @@ -245,10 +242,7 @@ impl<S: WriteStorage> VmInterfaceHistoryEnabled for Vm<S, HistoryEnabled> {
self.rollback_to_snapshot(snapshot);
}

/// Pop the latest snapshot from the memory and destroy it
fn pop_snapshot_no_rollback(&mut self) {
self.snapshots
.pop()
.expect("Snapshot should be created before rolling it back");
self.snapshots.pop();
}
}
2 changes: 1 addition & 1 deletion core/lib/multivm/src/versions/vm_m6/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,6 @@ impl<S: Storage> VmInterfaceHistoryEnabled for Vm<S, crate::vm_latest::HistoryEn
}

fn pop_snapshot_no_rollback(&mut self) {
self.vm.pop_snapshot_no_rollback()
self.vm.pop_snapshot_no_rollback();
}
}
3 changes: 1 addition & 2 deletions core/lib/multivm/src/versions/vm_m6/vm_instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -396,9 +396,8 @@ impl<H: HistoryMode, S: Storage> VmInstance<S, H> {
}

/// Removes the latest snapshot without rolling back to it.
/// This function expects that there is at least one snapshot present.
pub fn pop_snapshot_no_rollback(&mut self) {
self.snapshots.pop().unwrap();
self.snapshots.pop();
}

/// Returns the amount of gas remaining to the VM.
Expand Down
9 changes: 2 additions & 7 deletions core/lib/multivm/src/versions/vm_refunds_enhancement/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,14 +146,12 @@ impl<S: WriteStorage, H: HistoryMode> VmFactory<S> for Vm<S, H> {
}
}

/// Methods of vm, which required some history manipulations
impl<S: WriteStorage> VmInterfaceHistoryEnabled for Vm<S, HistoryEnabled> {
/// Create snapshot of current vm state and push it into the memory
fn make_snapshot(&mut self) {
self.make_snapshot_inner()
self.make_snapshot_inner();
}

/// Rollback vm state to the latest snapshot and destroy the snapshot
fn rollback_to_the_latest_snapshot(&mut self) {
let snapshot = self
.snapshots
Expand All @@ -162,10 +160,7 @@ impl<S: WriteStorage> VmInterfaceHistoryEnabled for Vm<S, HistoryEnabled> {
self.rollback_to_snapshot(snapshot);
}

/// Pop the latest snapshot from the memory and destroy it
fn pop_snapshot_no_rollback(&mut self) {
self.snapshots
.pop()
.expect("Snapshot should be created before rolling it back");
self.snapshots.pop();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When is it necessary to pop a nonexistent snapshot?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It happens here on the first transaction, or after the previous transaction was rolled back.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you could just always pop the snapshot if it isn't rolled back in the same place where you may roll it back.

}
}
8 changes: 1 addition & 7 deletions core/lib/multivm/src/versions/vm_virtual_blocks/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,14 +146,11 @@ impl<S: WriteStorage, H: HistoryMode> VmFactory<S> for Vm<S, H> {
}
}

/// Methods of vm, which required some history manipulations
impl<S: WriteStorage> VmInterfaceHistoryEnabled for Vm<S, HistoryEnabled> {
/// Create snapshot of current vm state and push it into the memory
fn make_snapshot(&mut self) {
self.make_snapshot_inner()
}

/// Rollback vm state to the latest snapshot and destroy the snapshot
fn rollback_to_the_latest_snapshot(&mut self) {
let snapshot = self
.snapshots
Expand All @@ -162,10 +159,7 @@ impl<S: WriteStorage> VmInterfaceHistoryEnabled for Vm<S, HistoryEnabled> {
self.rollback_to_snapshot(snapshot);
}

/// Pop the latest snapshot from the memory and destroy it
fn pop_snapshot_no_rollback(&mut self) {
self.snapshots
.pop()
.expect("Snapshot should be created before rolling it back");
self.snapshots.pop();
}
}
6 changes: 6 additions & 0 deletions core/lib/state/src/rocksdb/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,12 @@ impl RocksdbStorageBuilder {
) -> anyhow::Result<()> {
self.0.revert(storage, last_l1_batch_to_keep).await
}

/// Returns the underlying storage without synchronizing it or checking that it's up-to-date.
/// Should not be used other than for tests.
pub fn build_unchecked(self) -> RocksdbStorage {
slowli marked this conversation as resolved.
Show resolved Hide resolved
self.0
}
}

impl RocksdbStorage {
Expand Down
14 changes: 7 additions & 7 deletions core/node/consensus/src/testonly.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,10 @@ use zksync_state_keeper::{
io::{IoCursor, L1BatchParams, L2BlockParams},
seal_criteria::NoopSealer,
testonly::{
fund, l1_transaction, l2_transaction, test_batch_executor::MockReadStorageFactory,
MockBatchExecutor,
fund, l1_transaction, l2_transaction, test_batch_vm::MockReadStorageFactory, MockBatchVm,
},
AsyncRocksdbCache, MainBatchExecutor, OutputHandler, StateKeeperPersistence,
TreeWritesPersistence, ZkSyncStateKeeper,
AsyncRocksdbCache, BatchExecutor, OutputHandler, StateKeeperPersistence, TreeWritesPersistence,
ZkSyncStateKeeper,
};
use zksync_test_account::Account;
use zksync_types::{
Expand Down Expand Up @@ -547,7 +546,7 @@ impl StateKeeperRunner {
ZkSyncStateKeeper::new(
stop_recv,
Box::new(io),
Box::new(MainBatchExecutor::new(false, false)),
BatchExecutor::new(false, false),
OutputHandler::new(Box::new(persistence.with_tx_insertion()))
.with_handler(Box::new(self.sync_state.clone())),
Arc::new(NoopSealer),
Expand Down Expand Up @@ -628,12 +627,13 @@ impl StateKeeperRunner {
ZkSyncStateKeeper::new(
stop_recv,
Box::new(io),
Box::new(MockBatchExecutor),
BatchExecutor::new(false, false)
.with_vm_factory(Arc::<MockBatchVm>::default()),
OutputHandler::new(Box::new(persistence.with_tx_insertion()))
.with_handler(Box::new(tree_writes_persistence))
.with_handler(Box::new(self.sync_state.clone())),
Arc::new(NoopSealer),
Arc::new(MockReadStorageFactory),
Arc::<MockReadStorageFactory>::default(),
)
.run()
.await
Expand Down
4 changes: 2 additions & 2 deletions core/node/node_framework/examples/main_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ use zksync_node_framework::{
query_eth_client::QueryEthClientLayer,
sigint::SigintHandlerLayer,
state_keeper::{
main_batch_executor::MainBatchExecutorLayer, mempool_io::MempoolIOLayer,
batch_executor::BatchExecutorLayer, mempool_io::MempoolIOLayer,
output_handler::OutputHandlerLayer, StateKeeperLayer,
},
web3_api::{
Expand Down Expand Up @@ -161,7 +161,7 @@ impl MainNodeBuilder {
wallets.state_keeper.context("State keeper wallets")?,
);
let main_node_batch_executor_builder_layer =
MainBatchExecutorLayer::new(StateKeeperConfig::from_env()?.save_call_traces, true);
BatchExecutorLayer::new(StateKeeperConfig::from_env()?.save_call_traces, true);
let db_config = DBConfig::from_env()?;

let rocksdb_options = RocksdbStorageOptions {
Expand Down
Loading
Loading