diff --git a/.github/workflows/ci-core-reusable.yml b/.github/workflows/ci-core-reusable.yml index 15d26e0b4691..9dbd4202afd3 100644 --- a/.github/workflows/ci-core-reusable.yml +++ b/.github/workflows/ci-core-reusable.yml @@ -248,7 +248,6 @@ jobs: --server-db-name=zksync_server_localhost_validium \ --prover-db-url=postgres://postgres:notsecurepassword@localhost:5432 \ --prover-db-name=zksync_prover_localhost_validium \ - --port-offset 2000 \ --chain validium - name: Create and initialize chain with Custom Token @@ -272,7 +271,6 @@ jobs: --server-db-name=zksync_server_localhost_custom_token \ --prover-db-url=postgres://postgres:notsecurepassword@localhost:5432 \ --prover-db-name=zksync_prover_localhost_custom_token \ - --port-offset 3000 \ --chain custom_token - name: Create and register chain with transactions signed "offline" @@ -331,7 +329,6 @@ jobs: --server-db-name=zksync_server_localhost_consensus \ --prover-db-url=postgres://postgres:notsecurepassword@localhost:5432 \ --prover-db-name=zksync_prover_localhost_consensus \ - --port-offset 4000 \ --chain consensus - name: Build test dependencies diff --git a/etc/env/file_based/general.yaml b/etc/env/file_based/general.yaml index cdf02175458b..a4ba8c0201a6 100644 --- a/etc/env/file_based/general.yaml +++ b/etc/env/file_based/general.yaml @@ -80,7 +80,7 @@ operations_manager: contract_verifier: compilation_timeout: 240 polling_interval: 1000 - prometheus_port: 3314 + prometheus_port: 3318 port: 3070 url: http://127.0.0.1:3070 threads_per_server: 128 diff --git a/zk_toolbox/crates/config/src/general.rs b/zk_toolbox/crates/config/src/general.rs index 87eb3a7eb19b..a8e7407edd02 100644 --- a/zk_toolbox/crates/config/src/general.rs +++ b/zk_toolbox/crates/config/src/general.rs @@ -4,14 +4,14 @@ use anyhow::Context; use common::yaml::merge_yaml; use url::Url; use xshell::Shell; +use zksync_config::configs::object_store::ObjectStoreMode; pub use zksync_config::configs::GeneralConfig; -use zksync_config::configs::{consensus::Host, object_store::ObjectStoreMode}; use zksync_protobuf_config::{decode_yaml_repr, encode_yaml_repr}; use crate::{ consts::GENERAL_FILE, traits::{ConfigWithL2RpcUrl, FileConfigWithDefaultName, ReadConfig, SaveConfig}, - ChainConfig, DEFAULT_CONSENSUS_PORT, + ChainConfig, }; pub struct RocksDbs { @@ -113,68 +113,6 @@ pub fn set_file_artifacts(config: &mut GeneralConfig, file_artifacts: FileArtifa set_artifact_path!(config.core_object_store, file_artifacts.core_object_store); } -pub fn ports_config(config: &GeneralConfig) -> Option { - let api = config.api_config.as_ref()?; - let contract_verifier = config.contract_verifier.as_ref()?; - let consensus_port = if let Some(consensus_config) = config.clone().consensus_config { - consensus_config.server_addr.port() - } else { - DEFAULT_CONSENSUS_PORT - }; - - Some(PortsConfig { - web3_json_rpc_http_port: api.web3_json_rpc.http_port, - web3_json_rpc_ws_port: api.web3_json_rpc.ws_port, - healthcheck_port: api.healthcheck.port, - merkle_tree_port: api.merkle_tree.port, - prometheus_listener_port: api.prometheus.listener_port, - contract_verifier_port: contract_verifier.port, - consensus_port, - }) -} - -pub fn update_ports(config: &mut GeneralConfig, ports_config: &PortsConfig) -> anyhow::Result<()> { - let api = config - .api_config - .as_mut() - .context("Api config is not presented")?; - let contract_verifier = config - .contract_verifier - .as_mut() - .context("Contract Verifier config is not presented")?; - let prometheus = config - .prometheus_config - .as_mut() - .context("Prometheus config is not presented")?; - if let Some(consensus) = config.consensus_config.as_mut() { - consensus.server_addr.set_port(ports_config.consensus_port); - update_port_in_host(&mut consensus.public_addr, ports_config.consensus_port)?; - } - - api.web3_json_rpc.http_port = ports_config.web3_json_rpc_http_port; - update_port_in_url( - &mut api.web3_json_rpc.http_url, - ports_config.web3_json_rpc_http_port, - )?; - api.web3_json_rpc.ws_port = ports_config.web3_json_rpc_ws_port; - update_port_in_url( - &mut api.web3_json_rpc.ws_url, - ports_config.web3_json_rpc_ws_port, - )?; - contract_verifier.port = ports_config.contract_verifier_port; - update_port_in_url( - &mut contract_verifier.url, - ports_config.contract_verifier_port, - )?; - api.healthcheck.port = ports_config.healthcheck_port; - api.merkle_tree.port = ports_config.merkle_tree_port; - api.prometheus.listener_port = ports_config.prometheus_listener_port; - - prometheus.listener_port = ports_config.prometheus_listener_port; - - Ok(()) -} - pub fn override_config(shell: &Shell, path: PathBuf, chain: &ChainConfig) -> anyhow::Result<()> { let chain_config_path = chain.path_to_general_config(); let override_config = serde_yaml::from_str(&shell.read_file(path)?)?; @@ -184,60 +122,10 @@ pub fn override_config(shell: &Shell, path: PathBuf, chain: &ChainConfig) -> any Ok(()) } -fn update_port_in_url(http_url: &mut String, port: u16) -> anyhow::Result<()> { - let mut http_url_url = Url::parse(http_url)?; - if let Err(()) = http_url_url.set_port(Some(port)) { - anyhow::bail!("Wrong url, setting port is impossible"); - } - *http_url = http_url_url.to_string(); - Ok(()) -} - -fn update_port_in_host(host: &mut Host, port: u16) -> anyhow::Result<()> { - let url = Url::parse(&format!("http://{}", host.0))?; - let host_str = url.host_str().context("Failed to get host")?; - host.0 = format!("{host_str}:{port}"); - Ok(()) -} - impl FileConfigWithDefaultName for GeneralConfig { const FILE_NAME: &'static str = GENERAL_FILE; } -pub struct PortsConfig { - pub web3_json_rpc_http_port: u16, - pub web3_json_rpc_ws_port: u16, - pub healthcheck_port: u16, - pub merkle_tree_port: u16, - pub prometheus_listener_port: u16, - pub contract_verifier_port: u16, - pub consensus_port: u16, -} - -impl PortsConfig { - pub fn apply_offset(&mut self, offset: u16) { - self.web3_json_rpc_http_port += offset; - self.web3_json_rpc_ws_port += offset; - self.healthcheck_port += offset; - self.merkle_tree_port += offset; - self.prometheus_listener_port += offset; - self.contract_verifier_port += offset; - self.consensus_port += offset; - } - - pub fn next_empty_ports_config(&self) -> PortsConfig { - Self { - web3_json_rpc_http_port: self.web3_json_rpc_http_port + 100, - web3_json_rpc_ws_port: self.web3_json_rpc_ws_port + 100, - healthcheck_port: self.healthcheck_port + 100, - merkle_tree_port: self.merkle_tree_port + 100, - prometheus_listener_port: self.prometheus_listener_port + 100, - contract_verifier_port: self.contract_verifier_port + 100, - consensus_port: self.consensus_port + 100, - } - } -} - impl SaveConfig for GeneralConfig { fn save(&self, shell: &Shell, path: impl AsRef) -> anyhow::Result<()> { let bytes = diff --git a/zk_toolbox/crates/zk_inception/src/commands/chain/args/init.rs b/zk_toolbox/crates/zk_inception/src/commands/chain/args/init.rs index 9dd6c490bd78..24a0539f27d1 100644 --- a/zk_toolbox/crates/zk_inception/src/commands/chain/args/init.rs +++ b/zk_toolbox/crates/zk_inception/src/commands/chain/args/init.rs @@ -1,5 +1,3 @@ -use std::str::FromStr; - use clap::Parser; use common::{forge::ForgeScriptArgs, Prompt}; use config::ChainConfig; @@ -13,35 +11,10 @@ use crate::{ defaults::LOCAL_RPC_URL, messages::{ MSG_DEPLOY_PAYMASTER_PROMPT, MSG_GENESIS_ARGS_HELP, MSG_L1_RPC_URL_HELP, - MSG_L1_RPC_URL_INVALID_ERR, MSG_L1_RPC_URL_PROMPT, MSG_PORT_OFFSET_HELP, + MSG_L1_RPC_URL_INVALID_ERR, MSG_L1_RPC_URL_PROMPT, MSG_NO_PORT_REALLOCATION_HELP, }, }; -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct PortOffset(u16); - -impl PortOffset { - pub fn from_chain_id(chain_id: u16) -> Self { - Self((chain_id - 1) * 100) - } -} - -impl FromStr for PortOffset { - type Err = String; - - fn from_str(s: &str) -> Result { - s.parse::() - .map(PortOffset) - .map_err(|_| "Invalid port offset".to_string()) - } -} - -impl From for u16 { - fn from(port_offset: PortOffset) -> Self { - port_offset.0 - } -} - #[derive(Debug, Clone, Serialize, Deserialize, Parser)] pub struct InitArgs { /// All ethereum environment related arguments @@ -55,8 +28,8 @@ pub struct InitArgs { pub deploy_paymaster: Option, #[clap(long, help = MSG_L1_RPC_URL_HELP)] pub l1_rpc_url: Option, - #[clap(long, help = MSG_PORT_OFFSET_HELP)] - pub port_offset: Option, + #[clap(long, help = MSG_NO_PORT_REALLOCATION_HELP, default_value = "false", default_missing_value = "true", num_args = 0..=1)] + pub no_port_reallocation: bool, } impl InitArgs { @@ -86,10 +59,7 @@ impl InitArgs { genesis_args: self.genesis_args.fill_values_with_prompt(config), deploy_paymaster, l1_rpc_url, - port_offset: self - .port_offset - .unwrap_or(PortOffset::from_chain_id(config.id as u16)) - .into(), + no_port_reallocation: self.no_port_reallocation, } } } @@ -100,5 +70,5 @@ pub struct InitArgsFinal { pub genesis_args: GenesisArgsFinal, pub deploy_paymaster: bool, pub l1_rpc_url: String, - pub port_offset: u16, + pub no_port_reallocation: bool, } diff --git a/zk_toolbox/crates/zk_inception/src/commands/chain/init.rs b/zk_toolbox/crates/zk_inception/src/commands/chain/init.rs index fa2388a69be8..685e6165d258 100644 --- a/zk_toolbox/crates/zk_inception/src/commands/chain/init.rs +++ b/zk_toolbox/crates/zk_inception/src/commands/chain/init.rs @@ -1,8 +1,8 @@ -use anyhow::{bail, Context}; +use anyhow::Context; use common::{config::global_config, git, logger, spinner::Spinner}; use config::{ - copy_configs, ports_config, set_l1_rpc_url, traits::SaveConfigWithBasePath, - update_from_chain_config, update_ports, ChainConfig, EcosystemConfig, GeneralConfig, + copy_configs, set_l1_rpc_url, traits::SaveConfigWithBasePath, update_from_chain_config, + ChainConfig, EcosystemConfig, DEFAULT_CONSENSUS_PORT, }; use types::BaseToken; use xshell::Shell; @@ -20,14 +20,17 @@ use crate::{ }, portal::update_portal_config, }, + defaults::PORT_RANGE_END, messages::{ msg_initializing_chain, MSG_ACCEPTING_ADMIN_SPINNER, MSG_CHAIN_INITIALIZED, MSG_CHAIN_NOT_FOUND_ERR, MSG_DEPLOYING_PAYMASTER, MSG_GENESIS_DATABASE_ERR, - MSG_PORTAL_FAILED_TO_CREATE_CONFIG_ERR, MSG_PORTS_CONFIG_ERR, - MSG_REGISTERING_CHAIN_SPINNER, MSG_SELECTED_CONFIG, + MSG_PORTAL_FAILED_TO_CREATE_CONFIG_ERR, MSG_REGISTERING_CHAIN_SPINNER, MSG_SELECTED_CONFIG, MSG_UPDATING_TOKEN_MULTIPLIER_SETTER_SPINNER, MSG_WALLET_TOKEN_MULTIPLIER_SETTER_NOT_FOUND, }, - utils::consensus::{generate_consensus_keys, get_consensus_config, get_consensus_secrets}, + utils::{ + consensus::{generate_consensus_keys, get_consensus_config, get_consensus_secrets}, + ports::EcosystemPortsScanner, + }, }; pub(crate) async fn run(args: InitArgs, shell: &Shell) -> anyhow::Result<()> { @@ -54,15 +57,31 @@ pub async fn init( ecosystem_config: &EcosystemConfig, chain_config: &ChainConfig, ) -> anyhow::Result<()> { + let mut ecosystem_ports = EcosystemPortsScanner::scan(shell)?; copy_configs(shell, &ecosystem_config.link_to_code, &chain_config.configs)?; + if !init_args.no_port_reallocation { + ecosystem_ports.allocate_ports_in_yaml( + shell, + &chain_config.path_to_general_config(), + chain_config.id, + )?; + } let mut general_config = chain_config.get_general_config()?; - apply_port_offset(init_args.port_offset, &mut general_config)?; - let ports = ports_config(&general_config).context(MSG_PORTS_CONFIG_ERR)?; + + // TODO: This is a temporary solution. We should allocate consensus port using `EcosystemPorts::allocate_ports_in_yaml` + let offset = ((chain_config.id - 1) * 100) as u16; + let consensus_port_range = DEFAULT_CONSENSUS_PORT + offset..PORT_RANGE_END; + let consensus_port = + ecosystem_ports.allocate_port(consensus_port_range, "Consensus".to_string())?; let consensus_keys = generate_consensus_keys(); - let consensus_config = - get_consensus_config(chain_config, ports, Some(consensus_keys.clone()), None)?; + let consensus_config = get_consensus_config( + chain_config, + consensus_port, + Some(consensus_keys.clone()), + None, + )?; general_config.consensus_config = Some(consensus_config); general_config.save_with_base_path(shell, &chain_config.configs)?; @@ -176,15 +195,3 @@ pub async fn init( Ok(()) } - -fn apply_port_offset(port_offset: u16, general_config: &mut GeneralConfig) -> anyhow::Result<()> { - let Some(mut ports_config) = ports_config(general_config) else { - bail!("Missing ports config"); - }; - - ports_config.apply_offset(port_offset); - - update_ports(general_config, &ports_config)?; - - Ok(()) -} diff --git a/zk_toolbox/crates/zk_inception/src/commands/ecosystem/args/init.rs b/zk_toolbox/crates/zk_inception/src/commands/ecosystem/args/init.rs index 2411e4b95588..7898f8d254a8 100644 --- a/zk_toolbox/crates/zk_inception/src/commands/ecosystem/args/init.rs +++ b/zk_toolbox/crates/zk_inception/src/commands/ecosystem/args/init.rs @@ -12,7 +12,8 @@ use crate::{ messages::{ MSG_DEPLOY_ECOSYSTEM_PROMPT, MSG_DEPLOY_ERC20_PROMPT, MSG_DEPLOY_PAYMASTER_PROMPT, MSG_DEV_ARG_HELP, MSG_GENESIS_ARGS_HELP, MSG_L1_RPC_URL_HELP, MSG_L1_RPC_URL_INVALID_ERR, - MSG_L1_RPC_URL_PROMPT, MSG_OBSERVABILITY_HELP, MSG_OBSERVABILITY_PROMPT, + MSG_L1_RPC_URL_PROMPT, MSG_NO_PORT_REALLOCATION_HELP, MSG_OBSERVABILITY_HELP, + MSG_OBSERVABILITY_PROMPT, }, }; @@ -92,6 +93,8 @@ pub struct EcosystemInitArgs { pub dev: bool, #[clap(long, short = 'o', help = MSG_OBSERVABILITY_HELP, default_missing_value = "true", num_args = 0..=1)] pub observability: Option, + #[clap(long, help = MSG_NO_PORT_REALLOCATION_HELP, default_value = "false", default_missing_value = "true", num_args = 0..=1)] + pub no_port_reallocation: bool, } impl EcosystemInitArgs { @@ -129,6 +132,7 @@ impl EcosystemInitArgs { forge_args: self.forge_args.clone(), dev: self.dev, observability, + no_port_reallocation: self.no_port_reallocation, } } } @@ -141,4 +145,5 @@ pub struct EcosystemInitArgsFinal { pub forge_args: ForgeScriptArgs, pub dev: bool, pub observability: bool, + pub no_port_reallocation: bool, } diff --git a/zk_toolbox/crates/zk_inception/src/commands/ecosystem/init.rs b/zk_toolbox/crates/zk_inception/src/commands/ecosystem/init.rs index 2d31aad10336..80efc48f732b 100644 --- a/zk_toolbox/crates/zk_inception/src/commands/ecosystem/init.rs +++ b/zk_toolbox/crates/zk_inception/src/commands/ecosystem/init.rs @@ -31,7 +31,7 @@ use super::{ use crate::{ accept_ownership::{accept_admin, accept_owner}, commands::{ - chain::{self, args::init::PortOffset}, + chain::{self}, ecosystem::create_configs::{ create_erc20_deployment_config, create_initial_deployments_config, }, @@ -112,7 +112,7 @@ pub async fn run(args: EcosystemInitArgs, shell: &Shell) -> anyhow::Result<()> { genesis_args: genesis_args.clone().fill_values_with_prompt(&chain_config), deploy_paymaster: final_ecosystem_args.deploy_paymaster, l1_rpc_url: final_ecosystem_args.ecosystem.l1_rpc_url.clone(), - port_offset: PortOffset::from_chain_id(chain_config.id as u16).into(), + no_port_reallocation: final_ecosystem_args.no_port_reallocation, }; chain::init::init( diff --git a/zk_toolbox/crates/zk_inception/src/commands/explorer/init.rs b/zk_toolbox/crates/zk_inception/src/commands/explorer/init.rs index 43700d91a0df..5c8e10ba2d81 100644 --- a/zk_toolbox/crates/zk_inception/src/commands/explorer/init.rs +++ b/zk_toolbox/crates/zk_inception/src/commands/explorer/init.rs @@ -11,7 +11,6 @@ use url::Url; use xshell::Shell; use crate::{ - commands::chain::args::init::PortOffset, consts::L2_BASE_TOKEN_ADDRESS, defaults::{generate_explorer_db_name, DATABASE_EXPLORER_URL}, messages::{ @@ -19,6 +18,7 @@ use crate::{ msg_explorer_initializing_database_for, MSG_EXPLORER_FAILED_TO_DROP_DATABASE_ERR, MSG_EXPLORER_INITIALIZED, }, + utils::ports::{EcosystemPorts, EcosystemPortsScanner}, }; pub(crate) async fn run(shell: &Shell) -> anyhow::Result<()> { @@ -28,6 +28,7 @@ pub(crate) async fn run(shell: &Shell) -> anyhow::Result<()> { Some(ref chain_name) => vec![chain_name.clone()], None => ecosystem_config.list_of_chains(), }; + let mut ports = EcosystemPortsScanner::scan(shell)?; // Initialize chains one by one let mut explorer_config = ExplorerConfig::read_or_create_default(shell)?; for chain_name in chains_enabled.iter() { @@ -36,7 +37,7 @@ pub(crate) async fn run(shell: &Shell) -> anyhow::Result<()> { .load_chain(Some(chain_name.clone())) .context(msg_chain_load_err(chain_name))?; // Build backend config - parameters required to create explorer backend services - let backend_config = build_backend_config(&chain_config); + let backend_config = build_backend_config(&mut ports, &chain_config)?; // Initialize explorer database initialize_explorer_database(&backend_config.database_url).await?; // Create explorer backend docker compose file @@ -58,16 +59,23 @@ pub(crate) async fn run(shell: &Shell) -> anyhow::Result<()> { Ok(()) } -fn build_backend_config(chain_config: &ChainConfig) -> ExplorerBackendConfig { +fn build_backend_config( + ports: &mut EcosystemPorts, + chain_config: &ChainConfig, +) -> anyhow::Result { // Prompt explorer database name logger::info(msg_explorer_initializing_database_for(&chain_config.name)); let db_config = fill_database_values_with_prompt(chain_config); // Allocate ports for backend services - let backend_ports = allocate_explorer_services_ports(chain_config); + let mut backend_ports = ExplorerBackendPorts::default(); + ports.allocate_ports_with_offset_from_defaults(&mut backend_ports, chain_config.id)?; // Build explorer backend config - ExplorerBackendConfig::new(db_config.full_url(), &backend_ports) + Ok(ExplorerBackendConfig::new( + db_config.full_url(), + &backend_ports, + )) } async fn initialize_explorer_database(db_url: &Url) -> anyhow::Result<()> { @@ -92,12 +100,6 @@ fn fill_database_values_with_prompt(config: &ChainConfig) -> db::DatabaseConfig db::DatabaseConfig::new(explorer_db_url, explorer_db_name) } -fn allocate_explorer_services_ports(chain_config: &ChainConfig) -> ExplorerBackendPorts { - // Try to allocate intuitive ports with an offset from the defaults - let offset: u16 = PortOffset::from_chain_id(chain_config.id as u16).into(); - ExplorerBackendPorts::default().with_offset(offset) -} - fn build_explorer_chain_config( chain_config: &ChainConfig, backend_config: &ExplorerBackendConfig, diff --git a/zk_toolbox/crates/zk_inception/src/commands/external_node/prepare_configs.rs b/zk_toolbox/crates/zk_inception/src/commands/external_node/prepare_configs.rs index 38523db4dacc..bc741a6eb385 100644 --- a/zk_toolbox/crates/zk_inception/src/commands/external_node/prepare_configs.rs +++ b/zk_toolbox/crates/zk_inception/src/commands/external_node/prepare_configs.rs @@ -3,8 +3,10 @@ use std::{collections::BTreeMap, path::Path, str::FromStr}; use anyhow::Context; use common::{config::global_config, logger}; use config::{ - external_node::ENConfig, ports_config, set_rocks_db_config, traits::SaveConfigWithBasePath, - update_ports, ChainConfig, EcosystemConfig, SecretsConfig, + external_node::ENConfig, + set_rocks_db_config, + traits::{FileConfigWithDefaultName, SaveConfigWithBasePath}, + ChainConfig, EcosystemConfig, GeneralConfig, SecretsConfig, DEFAULT_CONSENSUS_PORT, }; use xshell::Shell; use zksync_basic_types::url::SensitiveUrl; @@ -17,13 +19,15 @@ use zksync_consensus_roles as roles; use crate::{ commands::external_node::args::prepare_configs::{PrepareConfigArgs, PrepareConfigFinal}, + defaults::PORT_RANGE_END, messages::{ msg_preparing_en_config_is_done, MSG_CHAIN_NOT_INITIALIZED, MSG_CONSENSUS_CONFIG_MISSING_ERR, MSG_CONSENSUS_SECRETS_MISSING_ERR, - MSG_CONSENSUS_SECRETS_NODE_KEY_MISSING_ERR, MSG_PORTS_CONFIG_ERR, MSG_PREPARING_EN_CONFIGS, + MSG_CONSENSUS_SECRETS_NODE_KEY_MISSING_ERR, MSG_PREPARING_EN_CONFIGS, }, utils::{ consensus::{get_consensus_config, node_public_key}, + ports::EcosystemPortsScanner, rocks_db::{recreate_rocksdb_dirs, RocksDBDirOption}, }, }; @@ -55,6 +59,7 @@ fn prepare_configs( en_configs_path: &Path, args: PrepareConfigFinal, ) -> anyhow::Result<()> { + let mut ports = EcosystemPortsScanner::scan(shell)?; let genesis = config.get_genesis_config()?; let general = config.get_general_config()?; let en_config = ENConfig { @@ -74,16 +79,20 @@ fn prepare_configs( gateway_url: None, }; let mut general_en = general.clone(); - let next_empty_ports_config = ports_config(&general) - .context(MSG_PORTS_CONFIG_ERR)? - .next_empty_ports_config(); - update_ports(&mut general_en, &next_empty_ports_config)?; - // Set consensus config let main_node_consensus_config = general .consensus_config .context(MSG_CONSENSUS_CONFIG_MISSING_ERR)?; + // TODO: This is a temporary solution. We should allocate consensus port using `EcosystemPorts::allocate_ports_in_yaml` + ports.add_port_info( + main_node_consensus_config.server_addr.port(), + "Main node consensus".to_string(), + ); + let offset = ((config.id - 1) * 100) as u16; + let consensus_port_range = DEFAULT_CONSENSUS_PORT + offset..PORT_RANGE_END; + let consensus_port = ports.allocate_port(consensus_port_range, "Consensus".to_string())?; + let mut gossip_static_outbound = BTreeMap::new(); let main_node_public_key = node_public_key( &config @@ -95,12 +104,8 @@ fn prepare_configs( gossip_static_outbound.insert(main_node_public_key, main_node_consensus_config.public_addr); - let en_consensus_config = get_consensus_config( - config, - next_empty_ports_config, - None, - Some(gossip_static_outbound), - )?; + let en_consensus_config = + get_consensus_config(config, consensus_port, None, Some(gossip_static_outbound))?; general_en.consensus_config = Some(en_consensus_config.clone()); en_consensus_config.save_with_base_path(shell, en_configs_path)?; @@ -129,5 +134,11 @@ fn prepare_configs( general_en.save_with_base_path(shell, en_configs_path)?; en_config.save_with_base_path(shell, en_configs_path)?; + ports.allocate_ports_in_yaml( + shell, + &GeneralConfig::get_path_with_base_path(en_configs_path), + 0, // This is zero because general_en ports already have a chain offset + )?; + Ok(()) } diff --git a/zk_toolbox/crates/zk_inception/src/defaults.rs b/zk_toolbox/crates/zk_inception/src/defaults.rs index 6c3821eed856..2b43009f5594 100644 --- a/zk_toolbox/crates/zk_inception/src/defaults.rs +++ b/zk_toolbox/crates/zk_inception/src/defaults.rs @@ -11,6 +11,12 @@ lazy_static! { Url::parse("postgres://postgres:notsecurepassword@localhost:5432").unwrap(); } +pub const DEFAULT_OBSERVABILITY_PORT: u16 = 3000; + +// Default port range +pub const PORT_RANGE_START: u16 = 3000; +pub const PORT_RANGE_END: u16 = 5000; + pub const ROCKS_DB_STATE_KEEPER: &str = "state_keeper"; pub const ROCKS_DB_TREE: &str = "tree"; pub const ROCKS_DB_PROTECTIVE_READS: &str = "protective_reads"; diff --git a/zk_toolbox/crates/zk_inception/src/messages.rs b/zk_toolbox/crates/zk_inception/src/messages.rs index 1bf3b0d598aa..621441ae8d49 100644 --- a/zk_toolbox/crates/zk_inception/src/messages.rs +++ b/zk_toolbox/crates/zk_inception/src/messages.rs @@ -52,7 +52,7 @@ pub(super) fn msg_path_to_zksync_does_not_exist_err(path: &str) -> String { /// Ecosystem and chain init related messages pub(super) const MSG_L1_RPC_URL_HELP: &str = "L1 RPC URL"; -pub(super) const MSG_PORT_OFFSET_HELP: &str = "Add a costant offset to the ports exposed by the components. Useful when running multiple chains on the same machine"; +pub(super) const MSG_NO_PORT_REALLOCATION_HELP: &str = "Do not reallocate ports"; pub(super) const MSG_GENESIS_ARGS_HELP: &str = "Genesis options"; pub(super) const MSG_DEV_ARG_HELP: &str = "Deploy ecosystem using all defaults. Suitable for local development"; @@ -330,8 +330,6 @@ pub(super) const MSG_CONSENSUS_CONFIG_MISSING_ERR: &str = "Consensus config is m pub(super) const MSG_CONSENSUS_SECRETS_MISSING_ERR: &str = "Consensus secrets config is missing"; pub(super) const MSG_CONSENSUS_SECRETS_NODE_KEY_MISSING_ERR: &str = "Consensus node key is missing"; -pub(super) const MSG_PORTS_CONFIG_ERR: &str = "Failed to get ports config"; - pub(super) const MSG_STARTING_EN: &str = "Starting external node"; /// Prover related messages diff --git a/zk_toolbox/crates/zk_inception/src/utils/consensus.rs b/zk_toolbox/crates/zk_inception/src/utils/consensus.rs index 387455759000..2979b4df0c19 100644 --- a/zk_toolbox/crates/zk_inception/src/utils/consensus.rs +++ b/zk_toolbox/crates/zk_inception/src/utils/consensus.rs @@ -3,9 +3,8 @@ use std::{ net::SocketAddr, }; -/// Code duplicated from `zksync_node_consensus::config`. use anyhow::Context as _; -use config::{ChainConfig, PortsConfig}; +use config::ChainConfig; use secrecy::{ExposeSecret, Secret}; use zksync_config::configs::consensus::{ AttesterPublicKey, AttesterSecretKey, ConsensusConfig, ConsensusSecrets, GenesisSpec, Host, @@ -51,15 +50,15 @@ pub struct ConsensusPublicKeys { pub fn get_consensus_config( chain_config: &ChainConfig, - ports: PortsConfig, + consensus_port: u16, consensus_keys: Option, gossip_static_outbound: Option>, ) -> anyhow::Result { let genesis_spec = consensus_keys.map(|consensus_keys| get_genesis_specs(chain_config, &consensus_keys)); - let public_addr = SocketAddr::new(CONSENSUS_PUBLIC_ADDRESS_HOST, ports.consensus_port); - let server_addr = SocketAddr::new(CONSENSUS_SERVER_ADDRESS_HOST, ports.consensus_port); + let public_addr = SocketAddr::new(CONSENSUS_PUBLIC_ADDRESS_HOST, consensus_port); + let server_addr = SocketAddr::new(CONSENSUS_SERVER_ADDRESS_HOST, consensus_port); Ok(ConsensusConfig { server_addr, diff --git a/zk_toolbox/crates/zk_inception/src/utils/mod.rs b/zk_toolbox/crates/zk_inception/src/utils/mod.rs index 229d3908dc3a..cf7a7ef48182 100644 --- a/zk_toolbox/crates/zk_inception/src/utils/mod.rs +++ b/zk_toolbox/crates/zk_inception/src/utils/mod.rs @@ -1,3 +1,4 @@ pub mod consensus; pub mod forge; +pub mod ports; pub mod rocks_db; diff --git a/zk_toolbox/crates/zk_inception/src/utils/ports.rs b/zk_toolbox/crates/zk_inception/src/utils/ports.rs new file mode 100644 index 000000000000..0462c0ea40fb --- /dev/null +++ b/zk_toolbox/crates/zk_inception/src/utils/ports.rs @@ -0,0 +1,580 @@ +use std::{ + collections::{HashMap, HashSet}, + fmt, + ops::Range, + path::Path, +}; + +use anyhow::{bail, Context, Result}; +use config::{ + explorer_compose::ExplorerBackendPorts, EcosystemConfig, DEFAULT_EXPLORER_API_PORT, + DEFAULT_EXPLORER_DATA_FETCHER_PORT, DEFAULT_EXPLORER_WORKER_PORT, +}; +use serde_yaml::Value; +use url::Url; +use xshell::Shell; + +use crate::defaults::{DEFAULT_OBSERVABILITY_PORT, PORT_RANGE_END, PORT_RANGE_START}; + +pub struct EcosystemPorts { + pub ports: HashMap>, +} + +impl EcosystemPorts { + pub fn get_assigned_ports(&self) -> HashSet { + self.ports.keys().cloned().collect() + } + + pub fn is_port_assigned(&self, port: u16) -> bool { + self.ports.contains_key(&port) + } + + pub fn add_port_info(&mut self, port: u16, info: String) { + self.ports.entry(port).or_default().push(info); + } + + pub fn allocate_port(&mut self, range: Range, info: String) -> anyhow::Result { + for port in range { + if !self.is_port_assigned(port) { + self.add_port_info(port, info.to_string()); + return Ok(port); + } + } + anyhow::bail!(format!( + "No available ports in the given range. Failed to allocate port for: {}", + info + )); + } + + pub fn allocate_ports_with_offset_from_defaults( + &mut self, + config: &mut T, + chain_number: u32, + ) -> Result<()> { + let offset = ((chain_number - 1) as u16) * 100; + let port_range = (PORT_RANGE_START + offset)..PORT_RANGE_END; + + let mut new_ports = HashMap::new(); + for (desc, port) in config.get_default_ports()? { + let mut new_port = port + offset; + if self.is_port_assigned(new_port) { + new_port = self.allocate_port(port_range.clone(), desc.clone())?; + } else { + self.add_port_info(new_port, desc.to_string()); + } + new_ports.insert(desc, new_port); + } + config.set_ports(new_ports)?; + Ok(()) + } + + pub fn allocate_ports_in_yaml( + &mut self, + shell: &Shell, + file_path: &Path, + chain_number: u32, + ) -> Result<()> { + let file_contents = shell.read_file(file_path)?; + let mut value: Value = serde_yaml::from_str(&file_contents)?; + let offset = (chain_number - 1) * 100; + self.traverse_allocate_ports_in_yaml(&mut value, offset)?; + let new_contents = serde_yaml::to_string(&value)?; + if new_contents != file_contents { + shell.write_file(file_path, new_contents)?; + } + Ok(()) + } + + fn traverse_allocate_ports_in_yaml( + &mut self, + value: &mut Value, + offset: u32, + ) -> anyhow::Result<()> { + match value { + Value::Mapping(map) => { + let mut updated_ports = HashMap::new(); + for (key, val) in &mut *map { + if key.as_str().map(|s| s.ends_with("port")).unwrap_or(false) { + if let Some(port) = val.as_u64().and_then(|p| u16::try_from(p).ok()) { + let new_port = self.allocate_port( + (port + offset as u16)..PORT_RANGE_END, + "".to_string(), + )?; + *val = Value::Number(serde_yaml::Number::from(new_port)); + updated_ports.insert(port, new_port); + } + } + } + // Update ports in URLs + for (key, val) in &mut *map { + if key.as_str().map(|s| s.ends_with("url")).unwrap_or(false) { + let mut url = Url::parse(val.as_str().unwrap())?; + if let Some(port) = url.port() { + if let Some(new_port) = updated_ports.get(&port) { + if let Err(()) = url.set_port(Some(*new_port)) { + bail!("Failed to update port in URL {}", url); + } else { + *val = Value::String(url.to_string()); + } + } + } + } + } + // Continue traversing + for (_, val) in map { + self.traverse_allocate_ports_in_yaml(val, offset)?; + } + } + Value::Sequence(seq) => { + for val in seq { + self.traverse_allocate_ports_in_yaml(val, offset)?; + } + } + _ => {} + } + + Ok(()) + } +} + +impl fmt::Display for EcosystemPorts { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut port_vec: Vec<_> = self.ports.iter().collect(); + port_vec.sort_by_key(|&(key, _)| key); + for (port, port_infos) in port_vec { + for port_info in port_infos { + writeln!(f, "{} > {}", port_info, port)?; + } + } + Ok(()) + } +} + +impl Default for EcosystemPorts { + fn default() -> Self { + let mut ports = HashMap::new(); + ports.insert( + DEFAULT_OBSERVABILITY_PORT, + vec!["Observability".to_string()], + ); + Self { + ports: HashMap::new(), + } + } +} + +pub struct EcosystemPortsScanner; + +impl EcosystemPortsScanner { + /// Scans the ecosystem directory for YAML files and extracts port information. + /// Specifically, it looks for keys ending with "port" or "ports" and collects their values. + pub fn scan(shell: &Shell) -> Result { + let ecosystem_config = EcosystemConfig::from_file(shell)?; + + // Create a list of directories to scan: + // - Ecosystem configs directory + // - Chain configs directories + // - External node directories + // - Ecosystem directory (docker-compose files) + let mut dirs = vec![ecosystem_config.config.clone()]; + for chain in ecosystem_config.list_of_chains() { + if let Some(chain_config) = ecosystem_config.load_chain(Some(chain)) { + dirs.push(chain_config.configs.clone()); + if let Some(external_node_config_path) = &chain_config.external_node_config_path { + dirs.push(external_node_config_path.clone()); + } + } + } + dirs.push(shell.current_dir()); // Ecosystem directory + + let mut ecosystem_ports = EcosystemPorts::default(); + for dir in dirs { + if dir.is_dir() { + Self::scan_yaml_files(shell, &dir, &mut ecosystem_ports) + .context(format!("Failed to scan directory {:?}", dir))?; + } + } + + Ok(ecosystem_ports) + } + + /// Scans the given directory for YAML files in the immediate directory only (non-recursive). + /// Processes each YAML file found and updates the EcosystemPorts accordingly. + fn scan_yaml_files( + shell: &Shell, + dir: &Path, + ecosystem_ports: &mut EcosystemPorts, + ) -> Result<()> { + for path in shell.read_dir(dir)? { + if !path.is_file() { + continue; + } + if let Some(extension) = path.extension() { + if extension == "yaml" || extension == "yml" { + Self::process_yaml_file(shell, &path, ecosystem_ports) + .context(format!("Error processing YAML file {:?}", path))?; + } + } + } + Ok(()) + } + + fn process_yaml_file( + shell: &Shell, + file_path: &Path, + ecosystem_ports: &mut EcosystemPorts, + ) -> Result<()> { + let contents = shell.read_file(file_path)?; + let value: Value = serde_yaml::from_str(&contents)?; + Self::traverse_yaml(&value, "", file_path, ecosystem_ports); + Ok(()) + } + + fn traverse_yaml( + value: &Value, + path: &str, + file_path: &Path, + ecosystem_ports: &mut EcosystemPorts, + ) { + match value { + Value::Mapping(map) => { + for (key, val) in map { + let new_path = if path.is_empty() { + key.as_str().unwrap_or_default().to_string() + } else { + format!("{}:{}", path, key.as_str().unwrap_or_default()) + }; + + if key.as_str() == Some("ports") { + Self::process_docker_compose_ports( + val, + &new_path, + file_path, + ecosystem_ports, + ); + } else if key.as_str().map(|s| s.ends_with("port")).unwrap_or(false) { + Self::process_general_config_port( + val, + &new_path, + file_path, + ecosystem_ports, + ); + } + + Self::traverse_yaml(val, &new_path, file_path, ecosystem_ports); + } + } + Value::Sequence(seq) => { + for (index, val) in seq.iter().enumerate() { + let new_path = format!("{}:{}", path, index); + Self::traverse_yaml(val, &new_path, file_path, ecosystem_ports); + } + } + _ => {} + } + } + + fn process_general_config_port( + value: &Value, + path: &str, + file_path: &Path, + ecosystem_ports: &mut EcosystemPorts, + ) { + if let Some(port) = value.as_u64().and_then(|p| u16::try_from(p).ok()) { + let description = format!("[{}] {}", file_path.display(), path); + ecosystem_ports.add_port_info(port, description); + } + } + + fn process_docker_compose_ports( + value: &Value, + path: &str, + file_path: &Path, + ecosystem_ports: &mut EcosystemPorts, + ) { + if let Value::Sequence(ports) = value { + for port_entry in ports { + if let Some(port_str) = port_entry.as_str() { + if let Some(port) = Self::parse_host_port(port_str) { + Self::add_parsed_port(port, path, file_path, ecosystem_ports); + } + } + } + } + } + + fn parse_host_port(port_str: &str) -> Option { + let parts: Vec<&str> = port_str.split(':').collect(); + if parts.len() > 1 { + if let Some(host_port_str) = parts.get(parts.len() - 2) { + if let Ok(port) = host_port_str.parse::() { + return Some(port); + } + } + } + None + } + + fn add_parsed_port( + port: u16, + path: &str, + file_path: &Path, + ecosystem_ports: &mut EcosystemPorts, + ) { + let description = format!("[{}] {}", file_path.display(), path); + ecosystem_ports.add_port_info(port, description); + } +} + +pub trait ConfigWithChainPorts { + fn get_default_ports(&self) -> anyhow::Result>; + fn set_ports(&mut self, ports: HashMap) -> Result<()>; +} + +impl ConfigWithChainPorts for ExplorerBackendPorts { + fn get_default_ports(&self) -> anyhow::Result> { + Ok(HashMap::from([ + ("api_http_port".to_string(), DEFAULT_EXPLORER_API_PORT), + ( + "data_fetcher_http_port".to_string(), + DEFAULT_EXPLORER_DATA_FETCHER_PORT, + ), + ("worker_http_port".to_string(), DEFAULT_EXPLORER_WORKER_PORT), + ])) + } + + fn set_ports(&mut self, ports: HashMap) -> anyhow::Result<()> { + if ports.len() != self.get_default_ports()?.len() { + bail!("Incorrect number of ports provided"); + } + for (desc, port) in ports { + match desc.as_str() { + "api_http_port" => self.api_http_port = port, + "data_fetcher_http_port" => self.data_fetcher_http_port = port, + "worker_http_port" => self.worker_http_port = port, + _ => bail!("Unknown port descriptor: {}", desc), + } + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use crate::utils::ports::{EcosystemPorts, EcosystemPortsScanner}; + + #[test] + fn test_traverse_yaml() { + let yaml_content = r#" + api: + web3_json_rpc: + http_port: 3050 + ws_port: 3051 + api_namespaces: + - eth + gas_price_scale_factor: 1.5 + prometheus: + listener_port: 3412 + push_interval_ms: 100 + contract_verifier: + port: 3070 + prometheus: + listener_port: 3412 + reth: + image: "ghcr.io/paradigmxyz/reth:v1.0.6" + ports: + - 127.0.0.1:8546:8545 + postgres: + image: "postgres:14" + ports: + - "5433:5432" + + "#; + + let value = serde_yaml::from_str(yaml_content).unwrap(); + let mut ecosystem_ports = EcosystemPorts::default(); + let file_path = PathBuf::from("test_config.yaml"); + + EcosystemPortsScanner::traverse_yaml(&value, "", &file_path, &mut ecosystem_ports); + + // Assigned ports: + assert!(ecosystem_ports.is_port_assigned(3050)); + assert!(ecosystem_ports.is_port_assigned(3051)); + assert!(ecosystem_ports.is_port_assigned(3070)); + assert!(ecosystem_ports.is_port_assigned(3412)); + assert!(ecosystem_ports.is_port_assigned(8546)); + assert!(ecosystem_ports.is_port_assigned(5433)); + + // Free ports: + assert!(!ecosystem_ports.is_port_assigned(3150)); + assert!(!ecosystem_ports.is_port_assigned(3151)); + assert!(!ecosystem_ports.is_port_assigned(8545)); + assert!(!ecosystem_ports.is_port_assigned(5432)); + assert!(!ecosystem_ports.is_port_assigned(100)); + + // Check description: + let port_3050_info = ecosystem_ports.ports.get(&3050).unwrap(); + assert_eq!(port_3050_info.len(), 1); + assert_eq!( + port_3050_info[0], + "[test_config.yaml] api:web3_json_rpc:http_port" + ); + + let port_3412_info = ecosystem_ports.ports.get(&3412).unwrap(); + assert_eq!(port_3412_info.len(), 2); + assert_eq!( + port_3412_info[0], + "[test_config.yaml] api:prometheus:listener_port" + ); + assert_eq!( + port_3412_info[1], + "[test_config.yaml] prometheus:listener_port" + ); + } + + #[test] + fn test_process_port_value() { + let yaml_content = r#" + web3_json_rpc: + http_port: 3050 + "#; + + let value: serde_yaml::Value = serde_yaml::from_str(yaml_content).unwrap(); + let mut ecosystem_ports = EcosystemPorts::default(); + let file_path = PathBuf::from("test_config.yaml"); + + EcosystemPortsScanner::process_general_config_port( + &value["web3_json_rpc"]["http_port"], + "web3_json_rpc:http_port", + &file_path, + &mut ecosystem_ports, + ); + + assert!(ecosystem_ports.is_port_assigned(3050)); + let port_info = ecosystem_ports.ports.get(&3050).unwrap(); + assert_eq!(port_info[0], "[test_config.yaml] web3_json_rpc:http_port"); + } + + #[test] + fn test_parse_process_docker_compose_ports() { + assert_eq!( + EcosystemPortsScanner::parse_host_port("127.0.0.1:8546:8545"), + Some(8546) + ); + assert_eq!( + EcosystemPortsScanner::parse_host_port("5433:5432"), + Some(5433) + ); + assert_eq!(EcosystemPortsScanner::parse_host_port("localhost:80"), None); + assert_eq!(EcosystemPortsScanner::parse_host_port("8080"), None); + } + + #[test] + fn test_add_parsed_port() { + let mut ecosystem_ports = EcosystemPorts::default(); + let file_path = PathBuf::from("test_config.yaml"); + + EcosystemPortsScanner::add_parsed_port( + 8546, + "reth:ports", + &file_path, + &mut ecosystem_ports, + ); + + assert!(ecosystem_ports.is_port_assigned(8546)); + let port_info = ecosystem_ports.ports.get(&8546).unwrap(); + assert_eq!(port_info[0], "[test_config.yaml] reth:ports"); + } + + #[test] + fn test_traverse_allocate_ports_in_yaml_with_chain_number_zero() { + let yaml_content = r#" + api: + web3_json_rpc: + http_port: 3050 + http_url: http://127.0.0.1:3050 + ws_port: 3051 + ws_url: ws://127.0.0.1:3051 + gas_price_scale_factor: 1.5 + prometheus: + listener_port: 3412 + pushgateway_url: http://127.0.0.1:9091 + push_interval_ms: 100 + "#; + + let mut value = serde_yaml::from_str(yaml_content).unwrap(); + let mut ecosystem_ports = EcosystemPorts::default(); + let chain_number = 0; + let offset = chain_number * 100; + ecosystem_ports + .traverse_allocate_ports_in_yaml(&mut value, offset) + .unwrap(); + + let api = value["api"].as_mapping().unwrap(); + let web3_json_rpc = api["web3_json_rpc"].as_mapping().unwrap(); + let prometheus = api["prometheus"].as_mapping().unwrap(); + + assert_eq!(web3_json_rpc["http_port"].as_u64().unwrap(), 3050); + assert_eq!(web3_json_rpc["ws_port"].as_u64().unwrap(), 3051); + assert_eq!(prometheus["listener_port"].as_u64().unwrap(), 3412); + assert_eq!( + web3_json_rpc["http_url"].as_str().unwrap(), + "http://127.0.0.1:3050/" + ); + assert_eq!( + web3_json_rpc["ws_url"].as_str().unwrap(), + "ws://127.0.0.1:3051/" + ); + assert_eq!( + prometheus["pushgateway_url"].as_str().unwrap(), + "http://127.0.0.1:9091" + ); + } + + #[test] + fn test_traverse_allocate_ports_in_yaml_with_chain_number_one() { + let yaml_content = r#" + api: + web3_json_rpc: + http_port: 3050 + http_url: http://127.0.0.1:3050 + ws_port: 3051 + ws_url: ws://127.0.0.1:3051 + gas_price_scale_factor: 1.5 + prometheus: + listener_port: 3412 + pushgateway_url: http://127.0.0.1:9091 + push_interval_ms: 100 + "#; + + let mut value = serde_yaml::from_str(yaml_content).unwrap(); + let mut ecosystem_ports = EcosystemPorts::default(); + let chain_number = 1; + let offset = chain_number * 100; + ecosystem_ports + .traverse_allocate_ports_in_yaml(&mut value, offset) + .unwrap(); + + let api = value["api"].as_mapping().unwrap(); + let web3_json_rpc = api["web3_json_rpc"].as_mapping().unwrap(); + let prometheus = api["prometheus"].as_mapping().unwrap(); + + assert_eq!(web3_json_rpc["http_port"].as_u64().unwrap(), 3150); + assert_eq!(web3_json_rpc["ws_port"].as_u64().unwrap(), 3151); + assert_eq!(prometheus["listener_port"].as_u64().unwrap(), 3512); + assert_eq!( + web3_json_rpc["http_url"].as_str().unwrap(), + "http://127.0.0.1:3150/" + ); + assert_eq!( + web3_json_rpc["ws_url"].as_str().unwrap(), + "ws://127.0.0.1:3151/" + ); + assert_eq!( + prometheus["pushgateway_url"].as_str().unwrap(), + "http://127.0.0.1:9091" + ); + } +}