From e5cd460e2b29b833ad912b3038ead610bec9362d Mon Sep 17 00:00:00 2001 From: Dustin Brickwood Date: Wed, 27 Nov 2024 07:16:50 -0600 Subject: [PATCH] feat: adds init and timestamp genesis cli params (#441) --- src/config/cli.rs | 16 +++++++++--- src/config/constants.rs | 2 ++ src/config/mod.rs | 56 ++++++++++++++++++++++++++++++++++++++++- src/node/eth.rs | 5 ++-- src/node/in_memory.rs | 56 +++++++++++++++++++++++++++++++++++------ src/utils.rs | 9 +++++++ 6 files changed, 131 insertions(+), 13 deletions(-) diff --git a/src/config/cli.rs b/src/config/cli.rs index ec0b8a32..082665eb 100644 --- a/src/config/cli.rs +++ b/src/config/cli.rs @@ -5,15 +5,15 @@ use clap::{arg, command, Parser, Subcommand}; use rand::{rngs::StdRng, SeedableRng}; use zksync_types::{H256, U256}; +use super::DEFAULT_DISK_CACHE_DIR; use crate::config::constants::{DEFAULT_MNEMONIC, TEST_NODE_NETWORK_ID}; use crate::config::{ - AccountGenerator, CacheConfig, CacheType, ShowCalls, ShowGasDetails, ShowStorageLogs, + AccountGenerator, CacheConfig, CacheType, Genesis, ShowCalls, ShowGasDetails, ShowStorageLogs, ShowVMDetails, TestNodeConfig, }; use crate::observability::LogLevel; use crate::system_contracts::Options as SystemContractsOptions; - -use super::DEFAULT_DISK_CACHE_DIR; +use crate::utils::parse_genesis_file; use alloy_signer_local::coins_bip39::{English, Mnemonic}; use std::net::IpAddr; @@ -171,6 +171,14 @@ pub struct Cli { )] pub balance: u64, + /// The timestamp of the genesis block. + #[arg(long, value_name = "NUM")] + pub timestamp: Option, + + /// Initialize the genesis block with the given `genesis.json` file. + #[arg(long, value_name = "PATH", value_parser= parse_genesis_file)] + pub init: Option, + /// BIP39 mnemonic phrase used for generating accounts. /// Cannot be used if `mnemonic_random` or `mnemonic_seed` are used. #[arg(long, short, conflicts_with_all = &["mnemonic_seed", "mnemonic_random"], help_heading = "Account Configuration")] @@ -338,6 +346,8 @@ impl Cli { }, } })) + .with_genesis_timestamp(self.timestamp) + .with_genesis(self.init) .with_chain_id(self.chain_id) .set_config_out(self.config_out) .with_host(self.host) diff --git a/src/config/constants.rs b/src/config/constants.rs index 9ff4fadb..fa3cb2d7 100644 --- a/src/config/constants.rs +++ b/src/config/constants.rs @@ -20,6 +20,8 @@ pub const DERIVATION_PATH: &str = "m/44'/60'/0'/0/0"; pub const DEFAULT_LOG_FILE_PATH: &str = "era_test_node.log"; /// Default mnemonic phrase for the test node pub const DEFAULT_MNEMONIC: &str = "test test test test test test test test test test test junk"; +/// Timestamp of the first block (if not running in fork mode). +pub const NON_FORK_FIRST_BLOCK_TIMESTAMP: u64 = 1_000; /// Default account balance for the dev accounts #[cfg(test)] pub const DEFAULT_ACCOUNT_BALANCE: u128 = 1_000 * 10u128.pow(18); diff --git a/src/config/mod.rs b/src/config/mod.rs index b9196381..e8fdc443 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -2,6 +2,8 @@ use crate::fork::ForkDetails; use crate::{observability, system_contracts}; use anyhow::anyhow; use std::net::{IpAddr, Ipv4Addr}; +use zksync_multivm::interface::L1BatchEnv; +use zksync_types::api::TransactionVariant; use crate::config::{ cache::{CacheConfig, CacheType}, @@ -23,7 +25,7 @@ use std::collections::HashMap; use std::fs::File; use std::time::Duration; use zksync_types::fee_model::FeeModelConfigV2; -use zksync_types::U256; +use zksync_types::{Bloom, H256, U256}; pub mod cache; pub mod cli; @@ -101,6 +103,10 @@ pub struct TestNodeConfig { pub account_generator: Option, /// Signer accounts that can sign messages/transactions pub signer_accounts: Vec, + /// The genesis to use to initialize the node + pub genesis: Option, + /// Genesis block timestamp + pub genesis_timestamp: Option, /// Enable auto impersonation of accounts on startup pub enable_auto_impersonate: bool, /// Whether the node operates in offline mode @@ -156,6 +162,8 @@ impl Default for TestNodeConfig { enable_auto_impersonate: false, // 100ETH default balance genesis_balance: U256::from(100u128 * 10u128.pow(18)), + genesis_timestamp: Some(NON_FORK_FIRST_BLOCK_TIMESTAMP), + genesis: None, // Offline mode disabled by default offline: false, @@ -298,6 +306,11 @@ impl TestNodeConfig { ); println!("\n"); + tracing::info!("Genesis Timestamp"); + tracing::info!("========================"); + tracing::info!("{}", self.get_genesis_timestamp().to_string().green()); + println!("\n"); + tracing::info!("Node Configuration"); tracing::info!("========================"); tracing::info!("Port: {}", self.port); @@ -678,6 +691,26 @@ impl TestNodeConfig { .with_genesis_accounts(accounts) } + /// Sets the genesis timestamp + #[must_use] + pub fn with_genesis_timestamp(mut self, timestamp: Option) -> Self { + self.genesis_timestamp = timestamp; + self + } + + /// Returns the genesis timestamp to use + pub fn get_genesis_timestamp(&self) -> u64 { + self.genesis_timestamp + .unwrap_or(NON_FORK_FIRST_BLOCK_TIMESTAMP) + } + + /// Sets the init genesis (genesis.json) + #[must_use] + pub fn with_genesis(mut self, genesis: Option) -> Self { + self.genesis = genesis; + self + } + /// Sets whether to enable autoImpersonate #[must_use] pub fn with_auto_impersonate(mut self, enable_auto_impersonate: bool) -> Self { @@ -834,3 +867,24 @@ impl AccountGenerator { .collect() } } + +/// Genesis +#[derive(Deserialize, Clone, Debug)] +pub struct Genesis { + /// The hash of the genesis block. If not provided, it can be computed. + pub hash: Option, + /// The parent hash of the genesis block. Usually zero. + pub parent_hash: Option, + /// The block number of the genesis block. Usually zero. + pub block_number: Option, + /// The timestamp of the genesis block. + pub timestamp: Option, + /// The L1 batch environment. + pub l1_batch_env: Option, + /// The transactions included in the genesis block. + pub transactions: Option>, + /// The amount of gas used. + pub gas_used: Option, + /// The logs bloom filter. + pub logs_bloom: Option, +} diff --git a/src/node/eth.rs b/src/node/eth.rs index 55ba5ed2..7ad014ee 100644 --- a/src/node/eth.rs +++ b/src/node/eth.rs @@ -1453,11 +1453,12 @@ impl EthTestNod #[cfg(test)] mod tests { use super::*; - use crate::node::NON_FORK_FIRST_BLOCK_TIMESTAMP; use crate::{ config::{ cache::CacheConfig, - constants::{DEFAULT_ACCOUNT_BALANCE, DEFAULT_L2_GAS_PRICE}, + constants::{ + DEFAULT_ACCOUNT_BALANCE, DEFAULT_L2_GAS_PRICE, NON_FORK_FIRST_BLOCK_TIMESTAMP, + }, }, fork::ForkDetails, http_fork_source::HttpForkSource, diff --git a/src/node/in_memory.rs b/src/node/in_memory.rs index 7a723495..230de7aa 100644 --- a/src/node/in_memory.rs +++ b/src/node/in_memory.rs @@ -53,9 +53,9 @@ use crate::{ bootloader_debug::{BootloaderDebug, BootloaderDebugTracer}, config::{ cache::CacheConfig, - constants::{LEGACY_RICH_WALLETS, RICH_WALLETS}, + constants::{LEGACY_RICH_WALLETS, NON_FORK_FIRST_BLOCK_TIMESTAMP, RICH_WALLETS}, show_details::{ShowCalls, ShowGasDetails, ShowStorageLogs, ShowVMDetails}, - TestNodeConfig, + Genesis, TestNodeConfig, }, console_log::ConsoleLogHandler, deps::{storage_view::StorageView, InMemoryStorage}, @@ -73,8 +73,6 @@ use crate::{ /// Max possible size of an ABI encoded tx (in bytes). pub const MAX_TX_SIZE: usize = 1_000_000; -/// Timestamp of the first block (if not running in fork mode). -pub const NON_FORK_FIRST_BLOCK_TIMESTAMP: u64 = 1_000; /// Acceptable gas overestimation limit. pub const ESTIMATE_GAS_ACCEPTABLE_OVERESTIMATION: u64 = 1_000; /// The maximum number of previous blocks to store the state for. @@ -91,8 +89,45 @@ pub fn compute_hash<'a>(block_number: u64, tx_hashes: impl IntoIterator(timestamp: u64) -> Block { +pub fn create_genesis_from_json( + genesis: &Genesis, + timestamp: Option, +) -> Block { + let hash = genesis.hash.unwrap_or_else(|| compute_hash(0, [])); + let timestamp = timestamp + .or(genesis.timestamp) + .unwrap_or(NON_FORK_FIRST_BLOCK_TIMESTAMP); + + let l1_batch_env = genesis.l1_batch_env.clone().unwrap_or_else(|| L1BatchEnv { + previous_batch_hash: None, + number: L1BatchNumber(0), + timestamp, + fee_input: BatchFeeInput::pubdata_independent(0, 0, 0), + fee_account: Address::zero(), + enforced_base_fee: None, + first_l2_block: L2BlockEnv { + number: 0, + timestamp, + prev_block_hash: H256::zero(), + max_virtual_blocks_to_create: 0, + }, + }); + + create_block( + &l1_batch_env, + hash, + genesis.parent_hash.unwrap_or_else(H256::zero), + genesis.block_number.unwrap_or(0), + timestamp, + genesis.transactions.clone().unwrap_or_default(), + genesis.gas_used.unwrap_or_else(U256::zero), + genesis.logs_bloom.unwrap_or_else(Bloom::zero), + ) +} + +pub fn create_genesis(timestamp: Option) -> Block { let hash = compute_hash(0, []); + let timestamp = timestamp.unwrap_or(NON_FORK_FIRST_BLOCK_TIMESTAMP); let batch_env = L1BatchEnv { previous_batch_hash: None, number: L1BatchNumber(0), @@ -300,7 +335,14 @@ impl InMemoryNodeInner { let block_hash = compute_hash(0, []); block_hashes.insert(0, block_hash); let mut blocks = HashMap::>::new(); - blocks.insert(block_hash, create_genesis(NON_FORK_FIRST_BLOCK_TIMESTAMP)); + let genesis_block: Block = if let Some(ref genesis) = config.genesis + { + create_genesis_from_json(genesis, config.genesis_timestamp) + } else { + create_genesis(config.genesis_timestamp) + }; + + blocks.insert(block_hash, genesis_block); let fee_input_provider = TestNodeFeeInputProvider::default(); time.set_last_timestamp_unchecked(NON_FORK_FIRST_BLOCK_TIMESTAMP); @@ -2003,7 +2045,7 @@ mod tests { #[tokio::test] async fn test_create_genesis_creates_block_with_hash_and_zero_parent_hash() { - let first_block = create_genesis::(1000); + let first_block = create_genesis::(Some(1000)); assert_eq!(first_block.hash, compute_hash(0, [])); assert_eq!(first_block.parent_hash, H256::zero()); diff --git a/src/utils.rs b/src/utils.rs index 76696da4..06725520 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,9 +1,11 @@ +use crate::config::Genesis; use anyhow::Context; use chrono::{DateTime, Utc}; use futures::Future; use jsonrpc_core::{Error, ErrorCode}; use serde::{Deserialize, Serialize}; use std::convert::TryFrom; +use std::fs; use std::{convert::TryInto, fmt, pin::Pin}; use zksync_multivm::interface::{Call, CallType, ExecutionResult, VmExecutionResultAndLogs}; use zksync_types::{ @@ -28,6 +30,13 @@ where { } +/// Parses the genesis file from the given path. +pub fn parse_genesis_file(path: &str) -> Result { + let file_content = + fs::read_to_string(path).map_err(|err| format!("Failed to read file: {err}"))?; + serde_json::from_str(&file_content).map_err(|err| format!("Failed to parse JSON: {err}")) +} + /// Takes long integers and returns them in human friendly format with "_". /// For example: 12_334_093 pub fn to_human_size(input: U256) -> String {