diff --git a/zk_toolbox/crates/common/src/git.rs b/zk_toolbox/crates/common/src/git.rs index 7ebedf0f6283..ea6540c20b29 100644 --- a/zk_toolbox/crates/common/src/git.rs +++ b/zk_toolbox/crates/common/src/git.rs @@ -29,3 +29,12 @@ pub fn submodule_update(shell: &Shell, link_to_code: PathBuf) -> anyhow::Result< .run()?; Ok(()) } + +pub fn pull(shell: &Shell, link_to_code: PathBuf) -> anyhow::Result<()> { + let _dir_guard = shell.push_dir(link_to_code); + let res = Cmd::new(cmd!(shell, "git rev-parse --abbrev-ref HEAD")).run_with_output()?; + let current_branch = String::from_utf8(res.stdout)?; + let current_branch = current_branch.trim_end(); + Cmd::new(cmd!(shell, "git pull origin {current_branch}")).run()?; + Ok(()) +} diff --git a/zk_toolbox/crates/config/src/chain.rs b/zk_toolbox/crates/config/src/chain.rs index e8b6df00644d..d8cc53954352 100644 --- a/zk_toolbox/crates/config/src/chain.rs +++ b/zk_toolbox/crates/config/src/chain.rs @@ -9,7 +9,10 @@ use xshell::Shell; use zksync_basic_types::L2ChainId; use crate::{ - consts::{CONFIG_NAME, GENERAL_FILE, L1_CONTRACTS_FOUNDRY, SECRETS_FILE, WALLETS_FILE}, + consts::{ + CONFIG_NAME, CONTRACTS_FILE, EN_CONFIG_FILE, GENERAL_FILE, GENESIS_FILE, + L1_CONTRACTS_FOUNDRY, SECRETS_FILE, WALLETS_FILE, + }, create_localhost_wallets, traits::{ FileConfigWithDefaultName, ReadConfig, ReadConfigWithBasePath, SaveConfig, @@ -101,6 +104,18 @@ impl ChainConfig { self.configs.join(GENERAL_FILE) } + pub fn path_to_external_node_config(&self) -> PathBuf { + self.configs.join(EN_CONFIG_FILE) + } + + pub fn path_to_genesis_config(&self) -> PathBuf { + self.configs.join(GENESIS_FILE) + } + + pub fn path_to_contracts_config(&self) -> PathBuf { + self.configs.join(CONTRACTS_FILE) + } + pub fn path_to_secrets_config(&self) -> PathBuf { self.configs.join(SECRETS_FILE) } diff --git a/zk_toolbox/crates/config/src/consts.rs b/zk_toolbox/crates/config/src/consts.rs index fecb6e78c9a5..0fd55ebe0db6 100644 --- a/zk_toolbox/crates/config/src/consts.rs +++ b/zk_toolbox/crates/config/src/consts.rs @@ -1,23 +1,23 @@ /// Name of the main configuration file pub(crate) const CONFIG_NAME: &str = "ZkStack.yaml"; /// Name of the wallets file -pub(crate) const WALLETS_FILE: &str = "wallets.yaml"; +pub const WALLETS_FILE: &str = "wallets.yaml"; /// Name of the secrets config file -pub(crate) const SECRETS_FILE: &str = "secrets.yaml"; +pub const SECRETS_FILE: &str = "secrets.yaml"; /// Name of the general config file -pub(crate) const GENERAL_FILE: &str = "general.yaml"; +pub const GENERAL_FILE: &str = "general.yaml"; /// Name of the genesis config file -pub(crate) const GENESIS_FILE: &str = "genesis.yaml"; +pub const GENESIS_FILE: &str = "genesis.yaml"; // Name of external node specific config -pub(crate) const EN_CONFIG_FILE: &str = "external_node.yaml"; +pub const EN_CONFIG_FILE: &str = "external_node.yaml"; pub(crate) const ERC20_CONFIGS_FILE: &str = "erc20.yaml"; /// Name of the initial deployments config file pub(crate) const INITIAL_DEPLOYMENT_FILE: &str = "initial_deployments.yaml"; /// Name of the erc20 deployments config file pub(crate) const ERC20_DEPLOYMENT_FILE: &str = "erc20_deployments.yaml"; /// Name of the contracts file -pub(crate) const CONTRACTS_FILE: &str = "contracts.yaml"; +pub const CONTRACTS_FILE: &str = "contracts.yaml"; /// Main repository for the ZKsync project pub const ZKSYNC_ERA_GIT_REPO: &str = "https://github.com/matter-labs/zksync-era"; /// Name of the docker-compose file inside zksync repository diff --git a/zk_toolbox/crates/config/src/lib.rs b/zk_toolbox/crates/config/src/lib.rs index 47d4040eb6bf..e2d366aeb869 100644 --- a/zk_toolbox/crates/config/src/lib.rs +++ b/zk_toolbox/crates/config/src/lib.rs @@ -1,5 +1,5 @@ pub use chain::*; -pub use consts::{DOCKER_COMPOSE_FILE, ZKSYNC_ERA_GIT_REPO}; +pub use consts::*; pub use contracts::*; pub use ecosystem::*; pub use file_config::*; diff --git a/zk_toolbox/crates/zk_inception/src/commands/args/mod.rs b/zk_toolbox/crates/zk_inception/src/commands/args/mod.rs index 7b21015691b9..2d58d2ef3bb9 100644 --- a/zk_toolbox/crates/zk_inception/src/commands/args/mod.rs +++ b/zk_toolbox/crates/zk_inception/src/commands/args/mod.rs @@ -1,3 +1,5 @@ pub use run_server::*; +pub use update::*; mod run_server; +mod update; diff --git a/zk_toolbox/crates/zk_inception/src/commands/args/update.rs b/zk_toolbox/crates/zk_inception/src/commands/args/update.rs new file mode 100644 index 000000000000..b6c980163fe6 --- /dev/null +++ b/zk_toolbox/crates/zk_inception/src/commands/args/update.rs @@ -0,0 +1,9 @@ +use clap::Parser; + +use crate::messages::MSG_UPDATE_ONLY_CONFIG_HELP; + +#[derive(Debug, Parser)] +pub struct UpdateArgs { + #[clap(long, short = 'c', help = MSG_UPDATE_ONLY_CONFIG_HELP)] + pub only_config: bool, +} diff --git a/zk_toolbox/crates/zk_inception/src/commands/mod.rs b/zk_toolbox/crates/zk_inception/src/commands/mod.rs index 5cba51265981..5eea6e8a5a1a 100644 --- a/zk_toolbox/crates/zk_inception/src/commands/mod.rs +++ b/zk_toolbox/crates/zk_inception/src/commands/mod.rs @@ -6,3 +6,4 @@ pub mod ecosystem; pub mod external_node; pub mod prover; pub mod server; +pub mod update; diff --git a/zk_toolbox/crates/zk_inception/src/commands/update.rs b/zk_toolbox/crates/zk_inception/src/commands/update.rs new file mode 100644 index 000000000000..cc4fe13312cd --- /dev/null +++ b/zk_toolbox/crates/zk_inception/src/commands/update.rs @@ -0,0 +1,495 @@ +use std::path::Path; + +use anyhow::{Context, Ok}; +use common::{ + git::{pull, submodule_update}, + logger, + spinner::Spinner, +}; +use config::{ + ChainConfig, EcosystemConfig, CONTRACTS_FILE, EN_CONFIG_FILE, GENERAL_FILE, GENESIS_FILE, + SECRETS_FILE, +}; +use xshell::Shell; + +use super::args::UpdateArgs; +use crate::messages::{ + msg_diff_contracts_config, msg_diff_genesis_config, msg_diff_secrets, msg_updating_chain, + MSG_CHAIN_NOT_FOUND_ERR, MSG_DIFF_EN_CONFIG, MSG_DIFF_EN_GENERAL_CONFIG, + MSG_DIFF_GENERAL_CONFIG, MSG_INVALID_KEY_TYPE_ERR, MSG_PULLING_ZKSYNC_CODE_SPINNER, + MSG_UPDATING_SUBMODULES_SPINNER, MSG_UPDATING_ZKSYNC, MSG_ZKSYNC_UPDATED, +}; + +/// Holds the differences between two YAML configurations. +#[derive(Default)] +struct ConfigDiff { + /// Fields that have different values between the two configurations + /// This contains the new values + pub differing_values: serde_yaml::Mapping, + + /// Fields that are present in the new configuration but not in the old one. + pub new_fields: serde_yaml::Mapping, +} + +impl ConfigDiff { + fn print(&self, msg: &str, is_warning: bool) { + if self.new_fields.is_empty() { + return; + } + + if is_warning { + logger::warn(msg); + logger::warn(logger::object_to_string(&self.new_fields)); + } else { + logger::info(msg); + logger::info(logger::object_to_string(&self.new_fields)); + } + } +} + +pub fn run(shell: &Shell, args: UpdateArgs) -> anyhow::Result<()> { + logger::info(MSG_UPDATING_ZKSYNC); + let ecosystem = EcosystemConfig::from_file(shell)?; + + if !args.only_config { + update_repo(shell, &ecosystem)?; + } + + let general_config_path = ecosystem.get_default_configs_path().join(GENERAL_FILE); + let external_node_config_path = ecosystem.get_default_configs_path().join(EN_CONFIG_FILE); + let genesis_config_path = ecosystem.get_default_configs_path().join(GENESIS_FILE); + let contracts_config_path = ecosystem.get_default_configs_path().join(CONTRACTS_FILE); + let secrets_path = ecosystem.get_default_configs_path().join(SECRETS_FILE); + + for chain in ecosystem.list_of_chains() { + logger::step(msg_updating_chain(&chain)); + let chain = ecosystem + .load_chain(Some(chain)) + .context(MSG_CHAIN_NOT_FOUND_ERR)?; + update_chain( + shell, + &chain, + &general_config_path, + &external_node_config_path, + &genesis_config_path, + &contracts_config_path, + &secrets_path, + )?; + } + + logger::outro(MSG_ZKSYNC_UPDATED); + + Ok(()) +} + +fn update_repo(shell: &Shell, ecosystem: &EcosystemConfig) -> anyhow::Result<()> { + let link_to_code = ecosystem.link_to_code.clone(); + + let spinner = Spinner::new(MSG_PULLING_ZKSYNC_CODE_SPINNER); + pull(shell, link_to_code.clone())?; + spinner.finish(); + let spinner = Spinner::new(MSG_UPDATING_SUBMODULES_SPINNER); + submodule_update(shell, link_to_code.clone())?; + spinner.finish(); + + Ok(()) +} + +fn save_updated_config( + shell: &Shell, + config: serde_yaml::Value, + path: &Path, + diff: ConfigDiff, + msg: &str, +) -> anyhow::Result<()> { + if diff.new_fields.is_empty() { + return Ok(()); + } + + diff.print(msg, false); + + let general_config = serde_yaml::to_string(&config)?; + shell.write_file(path, general_config)?; + + Ok(()) +} + +fn update_config( + shell: Shell, + original_config_path: &Path, + chain_config_path: &Path, + save_config: bool, + msg: &str, +) -> anyhow::Result<()> { + let original_config = serde_yaml::from_str(&shell.read_file(original_config_path)?)?; + let mut chain_config = serde_yaml::from_str(&shell.read_file(chain_config_path)?)?; + let diff = merge_yaml(&mut chain_config, original_config)?; + if save_config { + save_updated_config(&shell, chain_config, chain_config_path, diff, msg)?; + } else { + diff.print(msg, true); + } + + Ok(()) +} + +fn update_chain( + shell: &Shell, + chain: &ChainConfig, + general: &Path, + external_node: &Path, + genesis: &Path, + contracts: &Path, + secrets: &Path, +) -> anyhow::Result<()> { + update_config( + shell.clone(), + general, + &chain.path_to_general_config(), + true, + MSG_DIFF_GENERAL_CONFIG, + )?; + + update_config( + shell.clone(), + external_node, + &chain.path_to_external_node_config(), + true, + MSG_DIFF_EN_CONFIG, + )?; + + update_config( + shell.clone(), + genesis, + &chain.path_to_genesis_config(), + false, + &msg_diff_genesis_config(&chain.name), + )?; + + update_config( + shell.clone(), + contracts, + &chain.path_to_contracts_config(), + false, + &msg_diff_contracts_config(&chain.name), + )?; + + update_config( + shell.clone(), + secrets, + &chain.path_to_secrets_config(), + false, + &msg_diff_secrets(&chain.name, &chain.path_to_secrets_config(), secrets), + )?; + + if let Some(external_node_config_path) = chain.external_node_config_path.clone() { + let external_node_general_config_path = external_node_config_path.join(GENERAL_FILE); + if !shell.path_exists(external_node_general_config_path.clone()) { + return Ok(()); + } + update_config( + shell.clone(), + general, + &external_node_general_config_path, + true, + MSG_DIFF_EN_GENERAL_CONFIG, + )?; + } + + Ok(()) +} + +fn merge_yaml_internal( + a: &mut serde_yaml::Value, + b: serde_yaml::Value, + current_key: String, + diff: &mut ConfigDiff, +) -> anyhow::Result<()> { + match (a, b) { + (serde_yaml::Value::Mapping(a), serde_yaml::Value::Mapping(b)) => { + for (key, value) in b { + let k = key.as_str().context(MSG_INVALID_KEY_TYPE_ERR)?.to_string(); + let current_key = if current_key.is_empty() { + k.clone() + } else { + format!("{}.{}", current_key, k) + }; + + if a.contains_key(&key) { + merge_yaml_internal(a.get_mut(&key).unwrap(), value, current_key, diff)?; + } else { + a.insert(key.clone(), value.clone()); + diff.new_fields.insert(current_key.into(), value); + } + } + } + (a, b) => { + if a != &b { + diff.differing_values.insert(current_key.into(), b); + } + } + } + Ok(()) +} + +fn merge_yaml(a: &mut serde_yaml::Value, b: serde_yaml::Value) -> anyhow::Result { + let mut diff = ConfigDiff::default(); + merge_yaml_internal(a, b, "".into(), &mut diff)?; + Ok(diff) +} + +#[cfg(test)] +mod tests { + #[test] + fn test_merge_yaml_both_are_equal_returns_no_diff() { + let mut a = serde_yaml::from_str( + r#" + key1: value1 + key2: value2 + key3: + key4: value4 + "#, + ) + .unwrap(); + let b: serde_yaml::Value = serde_yaml::from_str( + r#" + key1: value1 + key2: value2 + key3: + key4: value4 + "#, + ) + .unwrap(); + let expected: serde_yaml::Value = serde_yaml::from_str( + r#" + key1: value1 + key2: value2 + key3: + key4: value4 + "#, + ) + .unwrap(); + let diff = super::merge_yaml(&mut a, b).unwrap(); + assert!(diff.differing_values.is_empty()); + assert!(diff.new_fields.is_empty()); + assert_eq!(a, expected); + } + + #[test] + fn test_merge_yaml_b_has_extra_field_returns_diff() { + let mut a = serde_yaml::from_str( + r#" + key1: value1 + key2: value2 + key3: + key4: value4 + "#, + ) + .unwrap(); + let b: serde_yaml::Value = serde_yaml::from_str( + r#" + key1: value1 + key2: value2 + key3: + key4: value4 + key5: value5 + "#, + ) + .unwrap(); + + let expected: serde_yaml::Value = serde_yaml::from_str( + r#" + key1: value1 + key2: value2 + key3: + key4: value4 + key5: value5 + "#, + ) + .unwrap(); + + let diff = super::merge_yaml(&mut a, b.clone()).unwrap(); + assert!(diff.differing_values.is_empty()); + assert_eq!(diff.new_fields.len(), 1); + assert_eq!( + diff.new_fields.get::("key5".into()).unwrap(), + b.clone().get("key5").unwrap() + ); + assert_eq!(a, expected); + } + + #[test] + fn test_merge_yaml_a_has_extra_field_no_diff() { + let mut a = serde_yaml::from_str( + r#" + key1: value1 + key2: value2 + key3: + key4: value4 + key5: value5 + "#, + ) + .unwrap(); + let b: serde_yaml::Value = serde_yaml::from_str( + r#" + key1: value1 + key2: value2 + key3: + key4: value4 + "#, + ) + .unwrap(); + + let expected: serde_yaml::Value = serde_yaml::from_str( + r#" + key1: value1 + key2: value2 + key3: + key4: value4 + key5: value5 + "#, + ) + .unwrap(); + + let diff = super::merge_yaml(&mut a, b).unwrap(); + assert!(diff.differing_values.is_empty()); + assert!(diff.new_fields.is_empty()); + assert_eq!(a, expected); + } + + #[test] + fn test_merge_yaml_a_has_extra_field_and_b_has_extra_field_returns_diff() { + let mut a = serde_yaml::from_str( + r#" + key1: value1 + key2: value2 + key3: + key4: value4 + key5: value5 + "#, + ) + .unwrap(); + let b: serde_yaml::Value = serde_yaml::from_str( + r#" + key1: value1 + key2: value2 + key3: + key4: value4 + key6: value6 + "#, + ) + .unwrap(); + + let expected: serde_yaml::Value = serde_yaml::from_str( + r#" + key1: value1 + key2: value2 + key3: + key4: value4 + key5: value5 + key6: value6 + "#, + ) + .unwrap(); + + let diff = super::merge_yaml(&mut a, b.clone()).unwrap(); + assert_eq!(diff.differing_values.len(), 0); + assert_eq!(diff.new_fields.len(), 1); + assert_eq!( + diff.new_fields.get::("key6".into()).unwrap(), + b.clone().get("key6").unwrap() + ); + assert_eq!(a, expected); + } + + #[test] + fn test_merge_yaml_a_has_different_value_returns_diff() { + let mut a = serde_yaml::from_str( + r#" + key1: value1 + key2: value2 + key3: + key4: value4 + "#, + ) + .unwrap(); + let b: serde_yaml::Value = serde_yaml::from_str( + r#" + key1: value1 + key2: value2 + key3: + key4: value5 + "#, + ) + .unwrap(); + + let expected: serde_yaml::Value = serde_yaml::from_str( + r#" + key1: value1 + key2: value2 + key3: + key4: value4 + "#, + ) + .unwrap(); + + let diff = super::merge_yaml(&mut a, b.clone()).unwrap(); + assert_eq!(diff.differing_values.len(), 1); + assert_eq!( + diff.differing_values + .get::("key3.key4".into()) + .unwrap(), + b.get("key3").unwrap().get("key4").unwrap() + ); + assert_eq!(a, expected); + } + + #[test] + fn test_merge_yaml_a_has_different_value_and_b_has_extra_field_returns_diff() { + let mut a = serde_yaml::from_str( + r#" + key1: value1 + key2: value2 + key3: + key4: value4 + "#, + ) + .unwrap(); + let b: serde_yaml::Value = serde_yaml::from_str( + r#" + key1: value1 + key2: value2 + key3: + key4: value5 + key5: value5 + "#, + ) + .unwrap(); + + let expected: serde_yaml::Value = serde_yaml::from_str( + r#" + key1: value1 + key2: value2 + key3: + key4: value4 + key5: value5 + "#, + ) + .unwrap(); + + let diff = super::merge_yaml(&mut a, b.clone()).unwrap(); + assert_eq!(diff.differing_values.len(), 1); + assert_eq!( + diff.differing_values + .get::("key3.key4".into()) + .unwrap(), + b.get("key3").unwrap().get("key4").unwrap() + ); + assert_eq!(diff.new_fields.len(), 1); + assert_eq!( + diff.new_fields.get::("key5".into()).unwrap(), + b.get("key5").unwrap() + ); + assert_eq!(a, expected); + } +} diff --git a/zk_toolbox/crates/zk_inception/src/main.rs b/zk_toolbox/crates/zk_inception/src/main.rs index dd10e9494627..e68dec4d3ed7 100644 --- a/zk_toolbox/crates/zk_inception/src/main.rs +++ b/zk_toolbox/crates/zk_inception/src/main.rs @@ -1,5 +1,5 @@ use clap::{command, Parser, Subcommand}; -use commands::contract_verifier::ContractVerifierCommands; +use commands::{args::UpdateArgs, contract_verifier::ContractVerifierCommands}; use common::{ check_general_prerequisites, config::{global_config, init_global_config, GlobalConfig}, @@ -53,6 +53,9 @@ pub enum InceptionSubcommands { /// Run contract verifier #[command(subcommand)] ContractVerifier(ContractVerifierCommands), + /// Update zkSync + #[command(alias = "u")] + Update(UpdateArgs), } #[derive(Parser, Debug)] @@ -110,6 +113,7 @@ async fn run_subcommand(inception_args: Inception, shell: &Shell) -> anyhow::Res InceptionSubcommands::ContractVerifier(args) => { commands::contract_verifier::run(shell, args).await? } + InceptionSubcommands::Update(args) => commands::update::run(shell, args)?, } Ok(()) } diff --git a/zk_toolbox/crates/zk_inception/src/messages.rs b/zk_toolbox/crates/zk_inception/src/messages.rs index 555aade78cbb..428b06516921 100644 --- a/zk_toolbox/crates/zk_inception/src/messages.rs +++ b/zk_toolbox/crates/zk_inception/src/messages.rs @@ -319,3 +319,44 @@ pub(super) fn msg_binary_already_exists(name: &str, version: &str) -> String { pub(super) fn msg_downloading_binary_spinner(name: &str, version: &str) -> String { format!("Downloading {} {} binary", name, version) } + +/// Update related messages + +pub(super) const MSG_UPDATE_ONLY_CONFIG_HELP: &str = "Update only the config files"; +pub(super) const MSG_UPDATING_ZKSYNC: &str = "Updating ZkSync"; +pub(super) const MSG_ZKSYNC_UPDATED: &str = "ZkSync updated successfully"; +pub(super) const MSG_PULLING_ZKSYNC_CODE_SPINNER: &str = "Pulling zksync-era repo..."; +pub(super) const MSG_UPDATING_SUBMODULES_SPINNER: &str = "Updating submodules..."; +pub(super) const MSG_DIFF_GENERAL_CONFIG: &str = + "Added the following fields to the general config:"; +pub(super) const MSG_DIFF_EN_CONFIG: &str = + "Added the following fields to the external node config:"; +pub(super) const MSG_DIFF_EN_GENERAL_CONFIG: &str = + "Added the following fields to the external node generalconfig:"; +pub(super) const MSG_INVALID_KEY_TYPE_ERR: &str = "Invalid key type"; + +pub(super) fn msg_diff_genesis_config(chain: &str) -> String { + format!( + "Found differences between chain {chain} and era genesis configs. Consider updating the chain {chain} genesis config and re-running genesis. Diff:" + ) +} + +pub(super) fn msg_diff_contracts_config(chain: &str) -> String { + format!( + "Found differences between chain {chain} and era contracts configs. Consider updating the chain {chain} contracts config and re-running genesis. Diff:" + ) +} + +pub(super) fn msg_diff_secrets( + chain: &str, + current_secrets_path: &Path, + era_secret_path: &Path, +) -> String { + format!( + "Found differences between chain {chain} and era secrets configs. Consider updating the chain {chain} secrets config at {current_secrets_path:?} using the file {era_secret_path:?} as reference. Diff:" + ) +} + +pub(super) fn msg_updating_chain(chain: &str) -> String { + format!("Updating chain: {}", chain) +}