1use crate::bytecode_override::override_bytecodes;
2use crate::cli::{BuiltinNetwork, Cli, Command, ForkUrl, PeriodicStateDumper};
3use crate::utils::update_with_fork_details;
4use alloy::primitives::Bytes;
5use anvil_zksync_api_server::NodeServerBuilder;
6use anvil_zksync_common::shell::get_shell;
7use anvil_zksync_common::utils::predeploys::PREDEPLOYS;
8use anvil_zksync_common::{sh_eprintln, sh_err, sh_println, sh_warn};
9use anvil_zksync_config::constants::{
10 DEFAULT_ESTIMATE_GAS_PRICE_SCALE_FACTOR, DEFAULT_ESTIMATE_GAS_SCALE_FACTOR,
11 DEFAULT_FAIR_PUBDATA_PRICE, DEFAULT_L1_GAS_PRICE, DEFAULT_L2_GAS_PRICE,
12 EVM_EMULATOR_ENABLER_CALLDATA, LEGACY_RICH_WALLETS, PSEUDO_CALLER, RICH_WALLETS,
13 TEST_NODE_NETWORK_ID,
14};
15use anvil_zksync_config::types::SystemContractsOptions;
16use anvil_zksync_config::{ForkPrintInfo, L1Config};
17use anvil_zksync_core::filters::EthFilters;
18use anvil_zksync_core::node::fork::ForkClient;
19use anvil_zksync_core::node::{
20 BlockSealer, BlockSealerMode, ImpersonationManager, InMemoryNode, InMemoryNodeInner,
21 NodeExecutor, StorageKeyLayout, TestNodeFeeInputProvider, TxBatch, TxPool,
22};
23use anvil_zksync_core::observability::Observability;
24use anvil_zksync_core::system_contracts::SystemContractsBuilder;
25use anvil_zksync_l1_sidecar::L1Sidecar;
26use anvil_zksync_types::L2TxBuilder;
27use anyhow::Context;
28use clap::Parser;
29use indicatif::{ProgressBar, ProgressStyle};
30use std::fmt::Write;
31use std::fs::File;
32use std::future::Future;
33use std::path::PathBuf;
34use std::pin::Pin;
35use std::sync::Arc;
36use std::time::Duration;
37use std::{env, net::SocketAddr, str::FromStr};
38use tokio::sync::RwLock;
39use tower_http::cors::AllowOrigin;
40use tracing_subscriber::filter::LevelFilter;
41use zksync_error::anvil_zksync::gen::{generic_error, to_domain};
42use zksync_error::anvil_zksync::AnvilZksyncError;
43use zksync_error::{ICustomError, IError as _};
44use zksync_telemetry::{get_telemetry, init_telemetry, TelemetryProps};
45use zksync_types::fee_model::{FeeModelConfigV2, FeeParams};
46use zksync_types::{
47 L2BlockNumber, Nonce, CONTRACT_DEPLOYER_ADDRESS, EVM_PREDEPLOYS_MANAGER_ADDRESS, H160, U256,
48};
49
50mod bytecode_override;
51mod cli;
52mod utils;
53
54const POSTHOG_API_KEY: &str = "phc_TsD52JxwkT2OXPHA2oKX2Lc3mf30hItCBrE9s9g1MKe";
55const TELEMETRY_CONFIG_NAME: &str = "zksync-tooling";
56
57async fn start_program() -> Result<(), AnvilZksyncError> {
58 Cli::deprecated_config_option();
60
61 let opt = Cli::parse();
62 let debug_opt_string_repr = format!("{opt:#?}");
64
65 let command = opt.command.clone();
66
67 let telemetry = get_telemetry().expect("telemetry is not initialized");
69 let cli_telemetry_props = opt.clone().into_telemetry_props();
70 let _ = telemetry
71 .track_event(
72 "node_started",
73 TelemetryProps::new()
74 .insert("params", Some(cli_telemetry_props))
75 .take(),
76 )
77 .await;
78
79 let mut config = opt.into_test_node_config().map_err(to_domain)?;
80 {
82 let mut shell = get_shell();
83 shell.verbosity = config.verbosity;
84 }
85 let log_level_filter = LevelFilter::from(config.log_level);
86 let log_file = File::create(&config.log_file_path).map_err(|inner| {
87 zksync_error::anvil_zksync::env::LogFileAccessFailed {
88 log_file_path: config.log_file_path.to_string(),
89 wrapped_error: inner.to_string(),
90 }
91 })?;
92
93 let observability = Observability::init(
95 vec!["anvil_zksync".into()],
96 log_level_filter,
97 log_file,
98 config.silent,
99 )
100 .map_err(|error| zksync_error::anvil_zksync::env::GenericError {
101 message: format!(
102 "Internal error: Unable to set up observability. Please report. \n{error:#?}"
103 ),
104 })?;
105
106 let command = command.as_ref().unwrap_or(&Command::Run);
108 let (fork_client, transactions_to_replay) = match command {
109 Command::Run => {
110 if config.offline {
111 sh_warn!("Running in offline mode: default fee parameters will be used.");
112 config = config
113 .clone()
114 .with_l1_gas_price(config.l1_gas_price.or(Some(DEFAULT_L1_GAS_PRICE)))
115 .with_l2_gas_price(config.l2_gas_price.or(Some(DEFAULT_L2_GAS_PRICE)))
116 .with_price_scale(
117 config
118 .price_scale_factor
119 .or(Some(DEFAULT_ESTIMATE_GAS_PRICE_SCALE_FACTOR)),
120 )
121 .with_gas_limit_scale(
122 config
123 .limit_scale_factor
124 .or(Some(DEFAULT_ESTIMATE_GAS_SCALE_FACTOR)),
125 )
126 .with_l1_pubdata_price(
127 config.l1_pubdata_price.or(Some(DEFAULT_FAIR_PUBDATA_PRICE)),
128 )
129 .with_chain_id(config.chain_id.or(Some(TEST_NODE_NETWORK_ID)));
130 (None, Vec::new())
131 } else {
132 let client = ForkClient::at_block_number(
134 ForkUrl::Builtin(BuiltinNetwork::Era).to_config(),
135 None,
136 )
137 .await
138 .map_err(to_domain)?;
139 let fee = client.get_fee_params().await.map_err(to_domain)?;
140
141 match fee {
142 FeeParams::V2(fee_v2) => {
143 config = config
144 .clone()
145 .with_l1_gas_price(config.l1_gas_price.or(Some(fee_v2.l1_gas_price())))
146 .with_l2_gas_price(
147 config
148 .l2_gas_price
149 .or(Some(fee_v2.config().minimal_l2_gas_price)),
150 )
151 .with_price_scale(
152 config
153 .price_scale_factor
154 .or(Some(DEFAULT_ESTIMATE_GAS_PRICE_SCALE_FACTOR)),
155 )
156 .with_gas_limit_scale(
157 config
158 .limit_scale_factor
159 .or(Some(DEFAULT_ESTIMATE_GAS_SCALE_FACTOR)),
160 )
161 .with_l1_pubdata_price(
162 config.l1_pubdata_price.or(Some(fee_v2.l1_pubdata_price())),
163 )
164 .with_chain_id(config.chain_id.or(Some(TEST_NODE_NETWORK_ID)));
165 }
166 FeeParams::V1(_) => {
167 return Err(
168 generic_error!("Unsupported FeeParams::V1 in this context").into()
169 );
170 }
171 }
172
173 (None, Vec::new())
174 }
175 }
176 Command::Fork(fork) => {
177 let (fork_client, earlier_txs) = if let Some(tx_hash) = fork.fork_transaction_hash {
178 ForkClient::at_before_tx(fork.fork_url.to_config(), tx_hash)
180 .await
181 .map_err(to_domain)?
182 } else {
183 (
185 ForkClient::at_block_number(
186 fork.fork_url.to_config(),
187 fork.fork_block_number.map(|bn| L2BlockNumber(bn as u32)),
188 )
189 .await
190 .map_err(to_domain)?,
191 Vec::new(),
192 )
193 };
194
195 update_with_fork_details(&mut config, &fork_client.details).await;
196 (Some(fork_client), earlier_txs)
197 }
198 Command::ReplayTx(replay_tx) => {
199 let (fork_client, earlier_txs) =
200 ForkClient::at_before_tx(replay_tx.fork_url.to_config(), replay_tx.tx)
201 .await
202 .map_err(to_domain)?;
203
204 update_with_fork_details(&mut config, &fork_client.details).await;
205 (Some(fork_client), earlier_txs)
206 }
207 };
208
209 if config.system_contracts_options != SystemContractsOptions::Local
211 && config.system_contracts_path.is_some()
212 {
213 return Err(to_domain(generic_error!(
214 "The --system-contracts-path option can only be specified when --dev-system-contracts is set to 'local'."
215 )));
216 }
217 if let SystemContractsOptions::Local = config.system_contracts_options {
218 let path: Option<PathBuf> = config
221 .system_contracts_path
222 .clone()
223 .or_else(|| env::var_os("ZKSYNC_HOME").map(PathBuf::from));
224
225 if let Some(path) = path {
226 if !path.exists() || !path.is_dir() {
227 return Err(to_domain(generic_error!(
228 "The specified system contracts path '{}' does not exist or is not a directory.",
229 path.to_string_lossy()
230 )));
231 }
232 tracing::debug!("Reading local contracts from {:?}", path);
233 }
234 }
235
236 let fork_print_info = if let Some(fork_client) = &fork_client {
237 let fee_model_config_v2 = match &fork_client.details.fee_params {
238 FeeParams::V2(fee_params_v2) => {
239 let config = fee_params_v2.config();
240 FeeModelConfigV2 {
241 minimal_l2_gas_price: config.minimal_l2_gas_price,
242 compute_overhead_part: config.compute_overhead_part,
243 pubdata_overhead_part: config.pubdata_overhead_part,
244 batch_overhead_l1_gas: config.batch_overhead_l1_gas,
245 max_gas_per_batch: config.max_gas_per_batch,
246 max_pubdata_per_batch: config.max_pubdata_per_batch,
247 }
248 }
249 _ => {
250 return Err(to_domain(generic_error!(
251 "fork is using unsupported fee parameters: {:?}",
252 fork_client.details.fee_params
253 )))
254 }
255 };
256
257 Some(ForkPrintInfo {
258 network_rpc: fork_client.url.to_string(),
259 l1_block: fork_client.details.batch_number.to_string(),
260 l2_block: fork_client.details.block_number.to_string(),
261 block_timestamp: fork_client.details.block_timestamp.to_string(),
262 fork_block_hash: format!("{:#x}", fork_client.details.block_hash),
263 fee_model_config_v2,
264 })
265 } else {
266 None
267 };
268
269 let impersonation = ImpersonationManager::default();
270 if config.enable_auto_impersonate {
271 impersonation.set_auto_impersonation(true);
273 }
274 let pool = TxPool::new(impersonation.clone(), config.transaction_order);
275
276 let fee_input_provider = TestNodeFeeInputProvider::from_fork(
277 fork_client.as_ref().map(|f| &f.details),
278 &config.base_token_config,
279 );
280 let filters = Arc::new(RwLock::new(EthFilters::default()));
281
282 let system_contracts = SystemContractsBuilder::new()
284 .system_contracts_options(config.system_contracts_options)
285 .system_contracts_path(config.system_contracts_path.clone())
286 .protocol_version(config.protocol_version())
287 .with_evm_interpreter(config.use_evm_interpreter)
288 .with_boojum(config.boojum.clone())
289 .build();
290
291 let storage_key_layout = if config.boojum.use_boojum {
292 StorageKeyLayout::BoojumOs
293 } else {
294 StorageKeyLayout::ZkEra
295 };
296
297 let is_fork_mode = fork_client.is_some();
298 let (node_inner, storage, blockchain, time, fork, vm_runner) = InMemoryNodeInner::init(
299 fork_client,
300 fee_input_provider.clone(),
301 filters,
302 config.clone(),
303 impersonation.clone(),
304 system_contracts.clone(),
305 storage_key_layout,
306 config.l1_config.is_some(),
308 );
309
310 let mut node_service_tasks: Vec<Pin<Box<dyn Future<Output = anyhow::Result<()>>>>> = Vec::new();
311 let (node_executor, node_handle) =
312 NodeExecutor::new(node_inner.clone(), vm_runner, storage_key_layout);
313 let l1_sidecar = match config.l1_config.as_ref() {
314 Some(_) if fork_print_info.is_some() => {
315 return Err(zksync_error::anvil_zksync::env::InvalidArguments {
316 details: "Running L1 in forking mode is unsupported".into(),
317 arguments: debug_opt_string_repr,
318 }
319 .into())
320 }
321 Some(L1Config::Spawn { port }) => {
322 let (l1_sidecar, l1_sidecar_runner) = L1Sidecar::process(
323 config.protocol_version(),
324 *port,
325 blockchain.clone(),
326 node_handle.clone(),
327 pool.clone(),
328 config.auto_execute_l1,
329 )
330 .await
331 .map_err(to_domain)?;
332 node_service_tasks.push(Box::pin(l1_sidecar_runner.run()));
333 l1_sidecar
334 }
335 Some(L1Config::External { address }) => {
336 let (l1_sidecar, l1_sidecar_runner) = L1Sidecar::external(
337 config.protocol_version(),
338 address,
339 blockchain.clone(),
340 node_handle.clone(),
341 pool.clone(),
342 config.auto_execute_l1,
343 )
344 .await
345 .map_err(to_domain)?;
346 node_service_tasks.push(Box::pin(l1_sidecar_runner.run()));
347 l1_sidecar
348 }
349 None => L1Sidecar::none(),
350 };
351 let sealing_mode = if config.no_mining {
352 BlockSealerMode::noop()
353 } else if let Some(block_time) = config.block_time {
354 BlockSealerMode::fixed_time(config.max_transactions, block_time)
355 } else {
356 BlockSealerMode::immediate(config.max_transactions, pool.add_tx_listener())
357 };
358 let (block_sealer, block_sealer_state) =
359 BlockSealer::new(sealing_mode, pool.clone(), node_handle.clone());
360 node_service_tasks.push(Box::pin(block_sealer.run()));
361
362 let node: InMemoryNode = InMemoryNode::new(
363 node_inner,
364 blockchain,
365 storage,
366 fork,
367 node_handle.clone(),
368 Some(observability),
369 time,
370 impersonation,
371 pool,
372 block_sealer_state,
373 system_contracts,
374 storage_key_layout,
375 );
376
377 tokio::spawn(async move {
380 if let Err(err) = node_executor.run().await {
381 sh_err!("{err}");
382
383 let _ = telemetry.track_error(Box::new(&err)).await;
384 }
385 });
386
387 if config.use_evm_interpreter {
388 node.impersonate_account(PSEUDO_CALLER).unwrap();
391 node.set_rich_account(PSEUDO_CALLER, U256::from(1_000_000_000_000u64))
392 .await;
393 let chain_id = node.chain_id().await;
394 let mut txs = Vec::with_capacity(PREDEPLOYS.len() + 1);
395 txs.push(
396 L2TxBuilder::new(
397 PSEUDO_CALLER,
398 Nonce(0),
399 U256::from(300_000),
400 U256::from(u32::MAX),
401 chain_id,
402 )
403 .with_to(CONTRACT_DEPLOYER_ADDRESS)
404 .with_calldata(Bytes::from_static(EVM_EMULATOR_ENABLER_CALLDATA).to_vec())
405 .build_impersonated()
406 .into(),
407 );
408
409 if !is_fork_mode {
411 let mut nonce = Nonce(1);
412 for pd in PREDEPLOYS.iter() {
413 let data = pd.encode_manager_call().unwrap();
414 txs.push(
415 L2TxBuilder::new(
416 PSEUDO_CALLER,
417 nonce,
418 U256::from(10_000_000), U256::from(u32::MAX),
420 chain_id,
421 )
422 .with_to(EVM_PREDEPLOYS_MANAGER_ADDRESS)
423 .with_calldata(data)
424 .build_impersonated()
425 .into(),
426 );
427 nonce += 1;
428 }
429 }
430
431 node_handle
432 .seal_block_sync(TxBatch {
433 impersonating: true,
434 txs,
435 })
436 .await
437 .map_err(to_domain)?;
438 node.set_rich_account(PSEUDO_CALLER, U256::from(0)).await;
439 node.stop_impersonating_account(PSEUDO_CALLER).unwrap();
440 }
441
442 if let Some(ref bytecodes_dir) = config.override_bytecodes_dir {
443 override_bytecodes(&node, bytecodes_dir.to_string())
444 .await
445 .unwrap();
446 }
447
448 if !transactions_to_replay.is_empty() {
449 sh_println!("Executing transactions from the block.");
450 let total_txs = transactions_to_replay.len() as u64;
451 let pb = ProgressBar::new(total_txs);
452 pb.enable_steady_tick(std::time::Duration::from_secs(1));
453 pb.set_style(
454 ProgressStyle::with_template("{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {pos}/{len} tx ({eta})")
455 .unwrap()
456 .with_key("eta", |state: &indicatif::ProgressState, w: &mut dyn Write| {
457 write!(w, "{:.1}s", state.eta().as_secs_f64()).unwrap()
458 })
459 .progress_chars("#>-")
460 );
461
462 node.node_handle
463 .set_progress_report(Some(pb.clone()))
464 .await
465 .map_err(to_domain)?;
466
467 node.replay_txs(transactions_to_replay)
468 .await
469 .map_err(to_domain)?;
470
471 pb.finish_and_clear();
472 sh_println!("Done replaying transactions.");
473
474 return Ok(());
476 }
477
478 let rich_addresses = itertools::chain!(
480 config
481 .genesis_accounts
482 .iter()
483 .map(|acc| H160::from_slice(acc.address().as_ref())),
484 config
485 .signer_accounts
486 .iter()
487 .map(|acc| H160::from_slice(acc.address().as_ref())),
488 LEGACY_RICH_WALLETS
489 .iter()
490 .map(|(address, _)| H160::from_str(address).unwrap()),
491 RICH_WALLETS
492 .iter()
493 .map(|(address, _, _)| H160::from_str(address).unwrap()),
494 )
495 .collect::<Vec<_>>();
496 for address in rich_addresses {
497 node.set_rich_account(address, config.genesis_balance).await;
498 }
499
500 let mut server_builder = NodeServerBuilder::new(
501 node.clone(),
502 l1_sidecar,
503 AllowOrigin::exact(
504 config
505 .allow_origin
506 .parse()
507 .context("allow origin is malformed")
508 .map_err(to_domain)?,
509 ),
510 );
511 if config.health_check_endpoint {
512 server_builder.enable_health_api()
513 }
514 if !config.no_cors {
515 server_builder.enable_cors();
516 }
517 let mut server_handles = Vec::with_capacity(config.host.len());
518 for host in &config.host {
519 let mut addr = SocketAddr::new(*host, config.port);
520
521 match server_builder.clone().build(addr).await {
522 Ok(server) => {
523 config.port = server.local_addr().port();
524 server_handles.push(server.run());
525 }
526 Err(err) => {
527 let port_requested = config.port;
528 sh_eprintln!(
529 "Failed to bind to address {}:{}: {}. Retrying with a different port...",
530 host,
531 config.port,
532 err
533 );
534
535 addr.set_port(0);
537 match server_builder.clone().build(addr).await {
538 Ok(server) => {
539 config.port = server.local_addr().port();
540 tracing::info!(
541 "Successfully started server on port {} for host {}",
542 config.port,
543 host
544 );
545 server_handles.push(server.run());
546 }
547 Err(err) => {
548 return Err(zksync_error::anvil_zksync::env::ServerStartupFailed {
549 host_requested: host.to_string(),
550 port_requested: port_requested.into(),
551 details: err.to_string(),
552 }
553 .into());
554 }
555 }
556 }
557 }
558 }
559 let any_server_stopped =
560 futures::future::select_all(server_handles.into_iter().map(|h| Box::pin(h.stopped())));
561
562 if let Some(ref load_state_path) = config.load_state {
564 let bytes = std::fs::read(load_state_path).expect("Failed to read load state file");
565 node.load_state(zksync_types::web3::Bytes(bytes))
566 .await
567 .map_err(to_domain)?;
568 }
569 if let Some(ref state_path) = config.state {
570 let bytes = std::fs::read(state_path).expect("Failed to read load state file");
571 node.load_state(zksync_types::web3::Bytes(bytes))
572 .await
573 .map_err(to_domain)?;
574 }
575
576 let state_path = config.dump_state.clone().or_else(|| config.state.clone());
577 let dump_interval = config
578 .state_interval
579 .map(Duration::from_secs)
580 .unwrap_or(Duration::from_secs(60)); let preserve_historical_states = config.preserve_historical_states;
582 let node_for_dumper = node.clone();
583 let state_dumper = PeriodicStateDumper::new(
584 node_for_dumper,
585 state_path,
586 dump_interval,
587 preserve_historical_states,
588 );
589 node_service_tasks.push(Box::pin(state_dumper));
590
591 config.print(fork_print_info.as_ref());
592 let node_service_stopped = futures::future::select_all(node_service_tasks);
593
594 tokio::select! {
595 _ = tokio::signal::ctrl_c() => {
596 tracing::trace!("received shutdown signal, shutting down");
597 },
598 _ = any_server_stopped => {
599 tracing::trace!("node server was stopped")
600 },
601 (result, _, _) = node_service_stopped => {
602 result.map_err(to_domain)?;
604 tracing::trace!("node service was stopped")
605 }
606 }
607
608 Ok(())
609}
610
611#[tokio::main]
612async fn main() -> Result<(), AnvilZksyncError> {
613 init_telemetry(
614 env!("CARGO_PKG_NAME"),
615 env!("CARGO_PKG_VERSION"),
616 TELEMETRY_CONFIG_NAME,
617 Some(POSTHOG_API_KEY.into()),
618 None,
619 None,
620 )
621 .await
622 .map_err(|inner| zksync_error::anvil_zksync::env::GenericError {
623 message: format!("Failed to initialize telemetry collection subsystem: {inner}."),
624 })?;
625
626 if let Err(err) = start_program().await {
627 let telemetry = get_telemetry().expect("telemetry is not initialized");
628 let _ = telemetry.track_error(Box::new(&err.to_unified())).await;
629 sh_eprintln!("{}", err.to_unified().get_message());
630 return Err(err);
631 }
632 Ok(())
633}