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