diff --git a/.github/workflows/ci_l2.yaml b/.github/workflows/ci_l2.yaml index 9e889cdc71..02135658ae 100644 --- a/.github/workflows/ci_l2.yaml +++ b/.github/workflows/ci_l2.yaml @@ -29,5 +29,5 @@ jobs: - name: Run L2 integration test run: | cd crates/l2 - cp config_example.toml config.toml + cp configs/config_example.toml configs/config.toml make ci_test diff --git a/cmd/ethrex/initializers.rs b/cmd/ethrex/initializers.rs index 2bd93eae73..a8bffded7f 100644 --- a/cmd/ethrex/initializers.rs +++ b/cmd/ethrex/initializers.rs @@ -30,7 +30,11 @@ use tracing_subscriber::{filter::Directive, EnvFilter, FmtSubscriber}; #[cfg(feature = "l2")] use crate::cli::L2Options; #[cfg(feature = "l2")] -use ::{ethrex_common::Address, ethrex_l2::utils::config::read_env_file, secp256k1::SecretKey}; +use ::{ + ethrex_common::Address, + ethrex_l2::utils::config::{read_env_file_by_config, ConfigMode}, + secp256k1::SecretKey, +}; #[cfg(feature = "based")] use crate::cli::BasedOptions; @@ -378,7 +382,7 @@ pub fn get_sponsor_pk(opts: &L2Options) -> SecretKey { warn!("Sponsor private key not provided. Trying to read from the .env file."); - if let Err(e) = read_env_file() { + if let Err(e) = read_env_file_by_config(ConfigMode::Sequencer) { panic!("Failed to read .env file: {e}"); } let pk = std::env::var("L1_WATCHER_L2_PROPOSER_PRIVATE_KEY").unwrap_or_default(); diff --git a/crates/l2/.gitignore b/crates/l2/.gitignore index 476c46cd05..5953d14e70 100644 --- a/crates/l2/.gitignore +++ b/crates/l2/.gitignore @@ -1,3 +1,7 @@ -.env -config.toml +.env* + +# Ignore all config files that doesn't end with _example.toml +/configs +!*_example.toml + solc_out diff --git a/crates/l2/Makefile b/crates/l2/Makefile index 4feb2ba461..d345b19af9 100644 --- a/crates/l2/Makefile +++ b/crates/l2/Makefile @@ -53,6 +53,9 @@ ethrex_METRICS_OVERRIDES_L1_DOCKER_COMPOSE_PATH=$(ethrex_PATH)/crates/blockchain ethrex_METRICS_OVERRIDES_L2_DOCKER_COMPOSE_PATH=$(ethrex_PATH)/crates/blockchain/metrics/docker-compose-metrics-l2.overrides.yaml CI_ETHREX_WORKDIR := /usr/local/bin +ethrex_L2_CONFIGS_PATH=$(shell pwd)/configs +ethrex_L2_SEQUENCER_CONFIG_FILE=config.toml +ethrex_L2_PROVER_CLIENT_CONFIG_FILE=prover_client_config.toml ethrex_L2_CONTRACTS_PATH=./contracts L1_RPC_URL=http://localhost:8545 L1_PRIVATE_KEY=0x385c546456b6a603a1cfcaa9ec9494ba4832da08dd6bcf4de9a71e4a01b74924 @@ -68,6 +71,8 @@ L2_AUTH_PORT=8552 # Matches the ports used by the blockchain/metrics dir L2_PROMETHEUS_METRICS_PORT = 3702 +PROVER_ENV_FILE ?= ".env.prover" + # Local L1 init-local-l1: ## ๐Ÿš€ Initializes an L1 Lambda ethrex Client with Docker (Used with make init) docker compose -f ${ethrex_DEV_DOCKER_COMPOSE_PATH} -f ${ethrex_METRICS_OVERRIDES_L1_DOCKER_COMPOSE_PATH} up -d @@ -111,14 +116,23 @@ clean-contract-deps: ## ๐Ÿงน Cleans the dependencies for the L1 contracts. restart-contract-deps: clean-contract-deps ## ๐Ÿ”„ Restarts the dependencies for the L1 contracts. deploy-l1: ## ๐Ÿ“œ Deploys the L1 contracts - DEPLOYER_CONTRACTS_PATH=contracts CONFIG_FILE=config.toml cargo run --release --bin ethrex_l2_l1_deployer --manifest-path ${ethrex_L2_CONTRACTS_PATH}/Cargo.toml -- --deposit_rich + DEPLOYER_CONTRACTS_PATH=contracts \ + CONFIGS_PATH=${ethrex_L2_CONFIGS_PATH} \ + SEQUENCER_CONFIG_FILE=${ethrex_L2_SEQUENCER_CONFIG_FILE} \ + cargo run --release --bin ethrex_l2_l1_deployer --manifest-path ${ethrex_L2_CONTRACTS_PATH}/Cargo.toml -- --deposit_rich ## Same as deploy-l1 but does not do deposits for rich accounts since that doesn't make sense for deployments to devnets/testnets i.e Sepolia deploy-l1-testnet: ## ๐Ÿ“œ Deploys the L1 contracts - DEPLOYER_CONTRACTS_PATH=contracts CONFIG_FILE=config.toml cargo run --release --bin ethrex_l2_l1_deployer --manifest-path ${ethrex_L2_CONTRACTS_PATH}/Cargo.toml + DEPLOYER_CONTRACTS_PATH=contracts \ + CONFIGS_PATH=${ethrex_L2_CONFIGS_PATH} \ + SEQUENCER_CONFIG_FILE=${ethrex_L2_SEQUENCER_CONFIG_FILE} \ + cargo run --release --bin ethrex_l2_l1_deployer --manifest-path ${ethrex_L2_CONTRACTS_PATH}/Cargo.toml update-system-contracts: - DEPLOYER_CONTRACTS_PATH=contracts CONFIG_FILE=config.toml cargo run --release --bin ethrex_l2_system_contracts_updater --manifest-path ${ethrex_L2_CONTRACTS_PATH}/Cargo.toml -- ${L2_GENESIS_FILE_PATH} + DEPLOYER_CONTRACTS_PATH=contracts \ + CONFIGS_PATH=${ethrex_L2_CONFIGS_PATH} \ + SEQUENCER_CONFIG_FILE=${ethrex_L2_SEQUENCER_CONFIG_FILE} \ + cargo run --release --bin ethrex_l2_system_contracts_updater --manifest-path ${ethrex_L2_CONTRACTS_PATH}/Cargo.toml -- ${L2_GENESIS_FILE_PATH} # L2 init-l2: init-metrics init-l2-no-metrics ## ๐Ÿš€ Initializes an L2 Lambda ethrex Client with metrics @@ -154,7 +168,7 @@ init-prover: ## ๐Ÿš€ Initializes the Prover @if [ -z "$$T" ]; then \ echo "Error: ProverType (T) is missing."; \ echo "Please provide it as an argument:"; \ - echo "make init-prover T= ."; \ + echo "make init-prover T= ."; \ echo "The prover can also be run with GPU (G)"; \ exit 1; \ fi; \ @@ -164,7 +178,13 @@ init-prover: ## ๐Ÿš€ Initializes the Prover else \ GPU=",gpu"; \ fi; \ - cargo run --release --features "$$T$$GPU,l2" --manifest-path ./prover/Cargo.toml --bin ethrex_prover -- $$T + + CONFIGS_PATH=${ethrex_L2_CONFIGS_PATH} \ + PROVER_CLIENT_CONFIG_FILE=${ethrex_L2_PROVER_CLIENT_CONFIG_FILE} \ + PROVER_ENV_FILE=${PROVER_ENV_FILE} \ + cargo run --release --features "$$T$$GPU,l2" \ + --manifest-path ./prover/Cargo.toml \ + --bin ethrex_prover -- $$T rm-db-l2: ## ๐Ÿ›‘ Removes the DB used by the L2 cargo run --release --manifest-path ../../Cargo.toml --bin ethrex -- removedb --datadir ${ethrex_L2_DEV_LIBMDBX} @@ -195,7 +215,7 @@ ci_test: ## ๐Ÿšง Runs the L2's integration test, used by the github's CI cargo test state_reconstruct --release test: ## ๐Ÿšง Runs the L2's integration test, run `make init` and in a new terminal make test - CONFIG_FILE=config.toml cargo test l2 --release -- --nocapture --test-threads=1 || (echo "The tests have failed.\n Is the L2 running? To start it, run:\n make rm-db-l1; make rm-db-l2; make restart" ; exit 1) + cargo test l2 --release -- --nocapture --test-threads=1 || (echo "The tests have failed.\n Is the L2 running? To start it, run:\n make rm-db-l1; make rm-db-l2; make restart" ; exit 1) # Purge L2's state diff --git a/crates/l2/config_example.toml b/crates/l2/configs/config_example.toml similarity index 95% rename from crates/l2/config_example.toml rename to crates/l2/configs/config_example.toml index 75f3ac308b..4beb2b3ea9 100644 --- a/crates/l2/config_example.toml +++ b/crates/l2/configs/config_example.toml @@ -42,11 +42,8 @@ interval_ms = 5000 # 1 Gwei arbitrary_base_blob_gas_price = 1000000000 -[prover.client] -prover_server_endpoint = "localhost:3900" -interval_ms = 5000 - -[prover.server] +[prover_server] +# set it to 0.0.0.0 to allow connections from other machines listen_ip = "127.0.0.1" listen_port = 3900 # Not the same account as the [committer] l1 Account diff --git a/crates/l2/configs/prover_client_config_example.toml b/crates/l2/configs/prover_client_config_example.toml new file mode 100644 index 0000000000..1e54bcc32d --- /dev/null +++ b/crates/l2/configs/prover_client_config_example.toml @@ -0,0 +1,8 @@ +[prover_client] +prover_server_endpoint = "localhost:3900" +interval_ms = 5000 +# mock, cpu, cuda +sp1_prover = "mock" +# 1 for dev_mode +# 0 for real_proofs +risc0_dev_mode = 1 diff --git a/crates/l2/contracts/deployer.rs b/crates/l2/contracts/deployer.rs index 19478aea59..79734bcf22 100644 --- a/crates/l2/contracts/deployer.rs +++ b/crates/l2/contracts/deployer.rs @@ -3,7 +3,10 @@ use colored::Colorize; use ethereum_types::{Address, H160, H256}; use ethrex_common::U256; use ethrex_l2::utils::config::errors; -use ethrex_l2::utils::config::{read_env_as_lines, read_env_file, write_env_file}; +use ethrex_l2::utils::config::{ + read_env_as_lines_by_config, read_env_file_by_config, toml_parser::parse_configs, + write_env_file_by_config, ConfigMode, +}; use ethrex_l2::utils::test_data_io::read_genesis_file; use ethrex_l2_sdk::calldata::{encode_calldata, Value}; use ethrex_l2_sdk::get_address_from_secret_key; @@ -59,8 +62,8 @@ pub enum DeployError { EthClientError(#[from] EthClientError), #[error("Deployer decoding error: {0}")] DecodingError(String), - #[error("Failed to interact with .env file, error: {0}")] - EnvFileError(#[from] errors::ConfigError), + #[error("Config error: {0}")] + ConfigError(#[from] errors::ConfigError), #[error("Failed to encode calldata: {0}")] CalldataEncodeError(#[from] CalldataEncodeError), } @@ -82,24 +85,10 @@ const BRIDGE_INITIALIZER_SIGNATURE: &str = "initialize(address)"; #[tokio::main] async fn main() -> Result<(), DeployError> { - #[allow(clippy::expect_fun_call, clippy::expect_used)] - let toml_config = std::env::var("CONFIG_FILE").expect( - format!( - "CONFIG_FILE environment variable not defined. Expected in {}, line: {} -If running locally, a reasonable value would be CONFIG_FILE=config.toml", - file!(), - line!() - ) - .as_str(), - ); - - match ethrex_l2::parse_toml::read_toml(toml_config) { - Ok(_) => (), - Err(err) => { - eprintln!("{}", err); - std::process::exit(1); - } - }; + if let Err(e) = parse_configs(ConfigMode::Sequencer) { + eprintln!("{e}"); + return Err(e.into()); + } let setup_result = setup()?; download_contract_deps(&setup_result.contracts_path)?; @@ -143,7 +132,8 @@ If running locally, a reasonable value would be CONFIG_FILE=config.toml", } } - let env_lines = read_env_as_lines().map_err(DeployError::EnvFileError)?; + let env_lines = + read_env_as_lines_by_config(ConfigMode::Sequencer).map_err(DeployError::ConfigError)?; let mut wr_lines: Vec = Vec::new(); let mut env_lines_iter = env_lines.into_iter(); @@ -168,12 +158,12 @@ If running locally, a reasonable value would be CONFIG_FILE=config.toml", } wr_lines.push(line); } - write_env_file(wr_lines).map_err(DeployError::EnvFileError)?; + write_env_file_by_config(wr_lines, ConfigMode::Sequencer)?; Ok(()) } fn setup() -> Result { - read_env_file()?; + read_env_file_by_config(ConfigMode::Sequencer)?; let eth_client = EthClient::new(&read_env_var("ETH_RPC_URL")?); diff --git a/crates/l2/contracts/system_contracts_updater.rs b/crates/l2/contracts/system_contracts_updater.rs index 5aad47ca43..bc3d152904 100644 --- a/crates/l2/contracts/system_contracts_updater.rs +++ b/crates/l2/contracts/system_contracts_updater.rs @@ -6,13 +6,13 @@ use bytes::Bytes; use ethrex_common::types::Genesis; use ethrex_common::types::GenesisAccount; use ethrex_common::U256; -use ethrex_l2::utils::config::read_env_file; +use ethrex_l2::utils::config::{read_env_file_by_config, ConfigMode}; use ethrex_l2_sdk::COMMON_BRIDGE_L2_ADDRESS; use utils::compile_contract; use utils::ContractCompilationError; fn main() -> Result<(), ContractCompilationError> { - read_env_file()?; + read_env_file_by_config(ConfigMode::Sequencer)?; #[allow(clippy::expect_fun_call, clippy::expect_used)] let contracts_path = Path::new( &std::env::var("DEPLOYER_CONTRACTS_PATH").expect( diff --git a/crates/l2/docker-compose-l2.yaml b/crates/l2/docker-compose-l2.yaml index a0ce0a6ede..fb0b797875 100644 --- a/crates/l2/docker-compose-l2.yaml +++ b/crates/l2/docker-compose-l2.yaml @@ -14,7 +14,7 @@ services: volumes: # NOTE: CI_ETHREX_WORKDIR is defined in crates/l2/Makefile - ./contracts:${CI_ETHREX_WORKDIR}/contracts - - ./config.toml:${CI_ETHREX_WORKDIR}/config.toml + - ./configs/config.toml:${CI_ETHREX_WORKDIR}/configs/config.toml - ./.env:${CI_ETHREX_WORKDIR}/.env - ../../test_data/genesis-l1-dev.json:${CI_ETHREX_WORKDIR}/test_data/genesis-l1-dev.json - ../../test_data/private_keys_l1.txt:${CI_ETHREX_WORKDIR}/test_data/private_keys_l1.txt @@ -24,7 +24,7 @@ services: # specified in the `volumes:` section - DEPLOYER_CONTRACTS_PATH=${CI_ETHREX_WORKDIR}/contracts - ENV_FILE=${CI_ETHREX_WORKDIR}/.env - - CONFIG_FILE=${CI_ETHREX_WORKDIR}/config.toml + - CONFIGS_PATH=${CI_ETHREX_WORKDIR}/configs - GENESIS_L1_PATH=${CI_ETHREX_WORKDIR}/test_data/genesis-l1-dev.json - PRIVATE_KEYS_PATH=${CI_ETHREX_WORKDIR}/test_data/private_keys_l1.txt depends_on: diff --git a/crates/l2/docs/README.md b/crates/l2/docs/README.md index a100b2fa9b..89ec5a65d3 100644 --- a/crates/l2/docs/README.md +++ b/crates/l2/docs/README.md @@ -16,7 +16,12 @@ For more detailed documentation on each part of the system: 1. `cd crates/l2` 2. `make rm-db-l2 && make down` - It will remove any old database, if present, stored in your computer. The absolute path of libmdbx is defined by [data_dir](https://docs.rs/dirs/latest/dirs/fn.data_dir.html). -3. `cp config_example.toml config.toml` → check if you want to change any config. +3. `cp configs/config_example.toml configs/config.toml` → check if you want to change any config. + - The `salt_is_zero` can be set to: + - `false` → randomizes the SALT to allow multiple deployments with random addresses. + - `true` → uses SALT equal to `H256::zero()` to deploy to deterministic addresses. + - The `L1` has to be restarted to use the `salt_is_zero = true`. + - Set it to `false` if not using the CI or running a deterministic test. 4. `make init` - Init the L1 in a docker container on port `8545`. - Deploy the needed contracts for the L2 on the L1. @@ -27,7 +32,7 @@ For more information on how to run the L2 node with the prover attached to it, t ## Configuration -Configuration is done through env vars. A detailed list is available in each part documentation. +Configuration consists of two steps, the parsing of a `.toml` config file and the creation and modification of a `.env` file, then each component reads the `.env` to load the environment variables. A detailed list is available in each part documentation. ## Testing diff --git a/crates/l2/docs/prover.md b/crates/l2/docs/prover.md index cf6053c885..f5ad3d84c5 100644 --- a/crates/l2/docs/prover.md +++ b/crates/l2/docs/prover.md @@ -7,9 +7,9 @@ - [What](#what) - [Workflow](#workflow) - [How](#how) - - [Quick Test](#quick-test) + - [Quick Test](#quick-test) - [Dev Mode](#dev-mode) - - [Run the whole system with the prover](#run-the-whole-system-with-the-prover) + - [Run the whole system with the prover - In one Machine](#run-the-whole-system-with-the-prover---in-one-machine) - [GPU mode](#gpu-mode) - [Proving Process Test](#proving-process-test) - [Run the whole system with a GPU Prover](#run-the-whole-system-with-a-gpu-prover) @@ -84,18 +84,22 @@ make init-prover T="prover_type (pico,risc0,sp1,exec) G=true" select the "exec" backend whenever it's not desired to generate proofs, like in a CI environment. -#### Run the whole system with the prover +#### Run the whole system with the prover - In one Machine + +> [!NOTE] +> Used for development purposes. 1. `cd crates/l2` 2. `make rm-db-l2 && make down` - It will remove any old database, if present, stored in your computer. The absolute path of libmdbx is defined by [data_dir](https://docs.rs/dirs/latest/dirs/fn.data_dir.html). -3. `cp config_example.toml config.toml` → check if you want to change any config. -4. `make init` +3. `cp configs/config_example.toml configs/config.toml` → check if you want to change any config. +4. `cp configs/prover_client_config_example.toml configs/prover_client_config.toml` → check if you want to change any config. +5. `make init` - Make sure you have the `solc` compiler installed in your system. - Init the L1 in a docker container on port `8545`. - Deploy the needed contracts for the L2 on the L1. - Start the L2 locally on port `1729`. -5. In a new terminal → `make init-prover T=(risc0 or sp1)`. +6. In a new terminal → `make init-prover T=(sp1,risc0,pico,exec)`. After this initialization we should have the prover running in `dev_mode` → No real proofs. @@ -137,30 +141,40 @@ Then run any of the targets: Two servers are required: one for the `prover` and another for the `proposer`. If you run both components on the same machine, the `prover` may consume all available resources, leading to potential stuttering or performance issues for the `proposer`/`node`. +- The number 1 simbolizes a machine with GPU for the `prover_client`. +- The number 2 simbolizes a machine for the `sequencer`/L2 node itself. + 1. `prover_client`/`zkvm` → prover with gpu, make sure to have all the required dependencies described at the beginning of [Gpu Mode](#gpu-mode) section. 1. `cd ethrex/crates/l2` - 2. `cp config_example.toml config.toml` and change the `prover_server_endpoint` entry under the [prover.client] section with the ip of the other server. + 2. `cp configs/prover_client_config_example.toml configs/prover_client_config.toml` and change the `prover_server_endpoint` with machine's `2` ip and make sure the port matches the one defined in machine 2. The important variables are: ```sh -[prover.client] -prover_server_endpoint=:3000 +[prover_client] +prover_server_endpoint=:3900 ``` - `Finally`, to start the `prover_client`/`zkvm`, run: - - `make init-prover T=(risc0 or sp1) G=true` + - `make init-prover T=(sp1,risc0,pico,exec) G=true` 2. `prover_server`/`proposer` → this server just needs rust installed. 1. `cd ethrex/crates/l2` - 2. `cp config_example.toml config.toml` and change the addresses and the following fields: - - [prover.server] - `listen_ip=0.0.0.0`ย → used to handle the tcp communication with the other server. + 2. `cp configs/config_example.toml configs/config.toml` and change the addresses and the following fields: + - [prover_server] + - `listen_ip=0.0.0.0`ย → Used to handle TCP communication with other servers from any network interface. - The `COMMITTER` and `PROVER_SERVER_VERIFIER` must be different accounts, the `DEPLOYER_ADDRESS` as well as the `L1_WATCHER` may be the same account used by the `COMMITTER`. - [deployer] - `salt_is_zero=false` → set to false to randomize the salt. - `sp1_deploy_verifier = true` overwrites `sp1_contract_verifier`. Check if the contract is deployed in your preferred network or set to `true` to deploy it. - - `risc0_contract_verifier = 0xd9b0d07CeCd808a8172F21fA7C97992168f045CA`ย → risc0โ€™s verifier contract deployed on Sepolia. (Check the if the contract is deployed in your preferred network). An analog variable called `sp1_contract_verifier` exists for SP1. + - `risc0_contract_verifier` + - Check the if the contract is present on your preferred network. + - `sp1_contract_verifier` + - It can be deployed. + - Check the if the contract is present on your preferred network. + - `pico_contract_verifier` + - It can be deployed. + - Check the if the contract is present on your preferred network. - Set the [eth] `rpc_url` to any L1 endpoint. > [!NOTE] @@ -172,11 +186,21 @@ prover_server_endpoint=:3000 ## Configuration -The following environment variables are available to configure the prover: +Configuration is done through environment variables. The easiest way to configure the ProverClient is by creating a `prover_client_config.toml` file and setting the variables there. Then, at start, it will read the file and set the variables. + +The following environment variables are available to configure the Proposer consider looking at the provided [prover_client_config_example.toml](../configs/prover_client_config_example.toml): + +The following environment variables are used by the ProverClient: + +- `CONFIGS_PATH`: The path where the `PROVER_CLIENT_CONFIG_FILE` is located at. +- `PROVER_CLIENT_CONFIG_FILE`: The `.toml` that contains the config for the `prover_client`. +- `PROVER_ENV_FILE`: The name of the `.env` that has the parsed `.toml` configuration. +- `PROVER_CLIENT_PROVER_SERVER_ENDPOINT`: Prover Server's Endpoint used to connect the Client to the Server. + +The following environment variables are used by the ProverServer: - `PROVER_SERVER_LISTEN_IP`: IP used to start the Server. - `PROVER_SERVER_LISTEN_PORT`: Port used to start the Server. -- `PROVER_CLIENT_PROVER_SERVER_ENDPOINT`: Prover Server's Endpoint used to connect the Client to the Server. - `PROVER_SERVER_VERIFIER_ADDRESS`: The address of the account that sends the zkProofs on-chain and interacts with the `OnChainProposer` `verify()` function. - `PROVER_SERVER_VERIFIER_PRIVATE_KEY`: The private key of the account that sends the zkProofs on-chain and interacts with the `OnChainProposer` `verify()` function. diff --git a/crates/l2/docs/proposer.md b/crates/l2/docs/sequencer.md similarity index 76% rename from crates/l2/docs/proposer.md rename to crates/l2/docs/sequencer.md index c4198c468b..dded80bbf6 100644 --- a/crates/l2/docs/proposer.md +++ b/crates/l2/docs/sequencer.md @@ -1,10 +1,11 @@ -# ethrex L2 Proposer +# ethrex L2 Sequencer ## ToC -- [ethrex L2 Proposer](#ethrex-l2-proposer) +- [ethrex L2 Sequencer](#ethrex-l2-sequencer) - [ToC](#toc) - [Components](#components) + - [Block Producer](#block-producer) - [L1 Watcher](#l1-watcher) - [L1 Transaction Sender](#l1-transaction-sender) - [Prover Server](#prover-server) @@ -14,6 +15,10 @@ The L2 Proposer is composed of the following components: +### Block Producer + +Creates Blocks with a connection to the `auth.rpc` port. + ### L1 Watcher This component handles the L1->L2 messages. Without rest, it is always watching the L1 for new deposit events defined as `DepositInitiated()` that contain the deposit transaction to be executed on the L2. Once a new deposit event is detected, it will insert the deposit transaction into the L2. @@ -36,18 +41,24 @@ For more information about the Prover Server, the [Prover Docs](./prover.md) pro ## Configuration -Configuration is done through environment variables. The easiest way to configure the Proposer is by creating a `config.toml` file and setting the variables there. Then, at start, it will read the file and set the variables. +Configuration is done through environment variables. The easiest way to configure the Sequencer is by creating a `config.toml` file and setting the variables there. Then, at start, it will read the file and set the variables. -The following environment variables are available to configure the Proposer (consider looking at the provided [config_example.toml](../config_example.toml): +> [!NOTE] +> The deployer.rs is in charge of parsing the `.toml` and creating/updating the `.env` +> If you don't deploy files, the `.toml` will not be parsed. - +The following environment variables are available to configure the Proposer consider looking at the provided [config_example.toml](../configs/config_example.toml): + + - Under the [deployer] section: - `address`: L1 account which will deploy the common bridge contracts in L1. - `private key`: Its private key. - `risc0_contract_verifier`: Address which will verify the `risc0` proofs. - `sp1_contract_verifier`: Address which will verify the `sp1` proofs. - - `sp1_deploy_verifier`: Whether to deploy the sp1 verifier + - `sp1_deploy_verifier`: Whether to deploy the `sp1` verifier contract or not. + - `pico_contract_verifier`: Address which will verify the `pico` proofs. + - `pico_deploy_verifier`: Whether to deploy the `pico` verifier contract or not. - `salt_is_zero`: Whether a 0 value salt will be used. Keep as true for deterministic `create2` operations. - Under the [eth] section: @@ -72,11 +83,7 @@ Under the [committer] section: - `l1_private_key`: Private key of the L1 committer. - `on_chain_proposer_address`: Address of the on-chain committer. -- Under the [prover.client] section: - - `prover_server_endpoint`: Endpoint for the prover server. - - `interval_ms`: Interval in milliseconds to prove new blocks (Currently unused). - -- Under the [prover.server] section: +- Under the [prover_server] section: - `listen_ip`: IP to listen for proof data requests. - `listen_port`: Port to listen for proof data requests. - `verifier_address`: Address of the account that sends verify transaction to L1. @@ -84,4 +91,6 @@ Under the [committer] section: - `dev_mode`: whether `dev_mode` is activated. -If you want to use a different configuration file, you can set the `CONFIG_FILE` environment variable to the path of the file. +If you want to use a different configuration file, you can set the: +- `CONFIGS_PATH`: The path where the `SEQUENCER_CONFIG_FILE` is located at. +- `SEQUENCER_CONFIG_FILE`: The `.toml` that contains the config for the `sequencer`. diff --git a/crates/l2/errors.rs b/crates/l2/errors.rs index 14ae9bc740..8b13789179 100644 --- a/crates/l2/errors.rs +++ b/crates/l2/errors.rs @@ -1,30 +1 @@ -#[derive(Debug, thiserror::Error)] -pub enum ConfigError { - #[error( - "Could not find crates/l2/config.toml -Have you tried copying the provided example? Try: -cp {manifest_dir}/config_example.toml {manifest_dir}/config.toml -", - manifest_dir = env!("CARGO_MANIFEST_DIR") - )] - TomlFileNotFound, - - #[error( - "Could not parse config.toml -Check the provided example to see if you have all the required fields. -The example can be found in: -crates/l2/config_example.toml -You can also see the differences with: -diff {manifest_dir}/config_example.toml {manifest_dir}/config.toml -", - manifest_dir = env!("CARGO_MANIFEST_DIR") - - )] - TomlFormat, - #[error( - "\x1b[91mCould not write to .env file.\x1b[0m -" - )] - EnvWriteError(String), -} diff --git a/crates/l2/l2.rs b/crates/l2/l2.rs index 536bc82c67..657195b9d7 100644 --- a/crates/l2/l2.rs +++ b/crates/l2/l2.rs @@ -1,5 +1,4 @@ pub mod errors; -pub mod parse_toml; pub mod sequencer; pub mod utils; diff --git a/crates/l2/prover/src/main.rs b/crates/l2/prover/src/main.rs index 7cb824fe78..d417e3ab03 100644 --- a/crates/l2/prover/src/main.rs +++ b/crates/l2/prover/src/main.rs @@ -1,6 +1,9 @@ -use ethrex_l2::utils::config::{prover_client::ProverClientConfig, read_env_file}; +use ethrex_l2::utils::config::{ + prover_client::ProverClientConfig, read_env_file_by_config, toml_parser::parse_configs, + ConfigMode, +}; use ethrex_prover_lib::init_client; -use tracing::{self, debug, error, warn, Level}; +use tracing::{self, debug, error, Level}; #[tokio::main] async fn main() { @@ -13,12 +16,18 @@ async fn main() { return; } - if let Err(e) = read_env_file() { - warn!("Failed to read .env file: {e}"); + if let Err(e) = parse_configs(ConfigMode::ProverClient) { + error!("Failed to parse .toml file: {e}"); + return; + } + + if let Err(e) = read_env_file_by_config(ConfigMode::ProverClient) { + error!("Failed to read .env file. It is '.env.prover' by default: {e}"); + return; } let Ok(config) = ProverClientConfig::from_env() else { - error!("Failed to read ProverClientConfig from .env file"); + error!("Failed to read ProverClientConfig from .env file. It is '.env.prover' by default"); return; }; diff --git a/crates/l2/sequencer/mod.rs b/crates/l2/sequencer/mod.rs index 8fcc8e5572..6086ac6862 100644 --- a/crates/l2/sequencer/mod.rs +++ b/crates/l2/sequencer/mod.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use crate::utils::config::read_env_file; +use crate::utils::config::{read_env_file_by_config, ConfigMode}; use block_producer::start_block_producer; use ethrex_blockchain::Blockchain; use ethrex_storage::Store; @@ -24,7 +24,7 @@ pub mod utils; pub async fn start_l2(store: Store, blockchain: Arc) { info!("Starting Proposer"); - if let Err(e) = read_env_file() { + if let Err(e) = read_env_file_by_config(ConfigMode::Sequencer) { error!("Failed to read .env file: {e}"); return; } diff --git a/crates/l2/tests/tests.rs b/crates/l2/tests/tests.rs index 5c411b27a7..26986469bd 100644 --- a/crates/l2/tests/tests.rs +++ b/crates/l2/tests/tests.rs @@ -2,7 +2,7 @@ #![allow(clippy::expect_used)] use bytes::Bytes; use ethereum_types::{Address, H160, U256}; -use ethrex_l2::utils::config::read_env_file; +use ethrex_l2::utils::config::{read_env_file_by_config, ConfigMode}; use ethrex_l2_sdk::calldata; use ethrex_rpc::clients::eth::{ eth_sender::Overrides, from_hex_string_to_u256, BlockByNumber, EthClient, @@ -44,7 +44,7 @@ async fn l2_integration_test() -> Result<(), Box> { let eth_client = eth_client(); let proposer_client = proposer_client(); - read_env_file()?; + read_env_file_by_config(ConfigMode::Sequencer)?; // 1. Check balances on L1 and L2 diff --git a/crates/l2/utils/config/errors.rs b/crates/l2/utils/config/errors.rs index 17025d41ac..c47c4b182d 100644 --- a/crates/l2/utils/config/errors.rs +++ b/crates/l2/utils/config/errors.rs @@ -13,6 +13,37 @@ pub enum ConfigError { BuildProposerEngineServerFromConfigError(#[from] auth::errors::ConfigError), #[error("Error building Prover server from config: {0}")] BuildProverServerFromConfigError(#[from] eth::errors::EthClientError), + #[error("Error parsing the .toml configuration files: {0}")] + TomlParserError(#[from] TomlParserError), #[error("{0}")] Custom(String), } + +#[derive(Debug, thiserror::Error)] +pub enum TomlParserError { + #[error( + "Could not find crates/l2/configs/{0} +Have you tried copying the provided example? Try: +cp {manifest_dir}/configs/*_example.toml {manifest_dir}/configs/*.toml +", + manifest_dir = env!("CARGO_MANIFEST_DIR") + + )] + TomlFileNotFound(String), + + #[error( + "Could not parse crates/l2/configs/{0} +Check the provided example to see if you have all the required fields. +The example can be found at: +crates/l2/configs/*_example.toml +You can also see the differences with: +diff {manifest_dir}/configs/*_example.toml {manifest_dir}/configs/*.toml +", + manifest_dir = env!("CARGO_MANIFEST_DIR") + + )] + TomlFormat(String), + + #[error("\x1b[91mCould not write to .env file.\x1b[0m {0}")] + EnvWriteError(String), +} diff --git a/crates/l2/utils/config/mod.rs b/crates/l2/utils/config/mod.rs index 5df646f97c..0fede2aeca 100644 --- a/crates/l2/utils/config/mod.rs +++ b/crates/l2/utils/config/mod.rs @@ -1,9 +1,10 @@ use std::{ io::{BufRead, Write}, - path::PathBuf, + path::{Path, PathBuf}, }; use tracing::{debug, info}; + pub mod block_producer; pub mod committer; pub mod eth; @@ -12,9 +13,60 @@ pub mod prover_client; pub mod prover_server; pub mod errors; +pub mod toml_parser; + +#[derive(Clone, Copy)] +pub enum ConfigMode { + /// Parses the entire the config.toml + /// And generates the .env file. + Sequencer, + /// Parses the prover_config.toml + /// And generates the .env.prover file only with the prover_client config variables. + ProverClient, +} + +impl ConfigMode { + /// Gets the .*config.toml file from the environment, or sets a default value + /// config.toml for the sequencer/L2 node + /// prover_config.toml for the the prover_client + fn get_config_file_path(&self, config_path: &str) -> PathBuf { + match self { + ConfigMode::Sequencer => { + let sequencer_config_file_name = + std::env::var("SEQUENCER_CONFIG_FILE").unwrap_or("config.toml".to_owned()); + Path::new(&config_path).join(sequencer_config_file_name) + } + ConfigMode::ProverClient => { + let prover_client_config_file_name = std::env::var("PROVER_CLIENT_CONFIG_FILE") + .unwrap_or("prover_client_config.toml".to_owned()); + Path::new(&config_path).join(prover_client_config_file_name) + } + } + } -pub fn read_env_file() -> Result<(), errors::ConfigError> { - let env_file = open_env_file()?; + /// Gets the .env* file from the environment, or sets a default value + /// .env for the sequencer/L2 node + /// .env.prover for the the prover_client + pub fn get_env_path_or_default(&self) -> PathBuf { + let cargo_manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + + match self { + ConfigMode::Sequencer => std::env::var("ENV_FILE") + .map(Into::into) + .unwrap_or(cargo_manifest_dir.join(".env")), + ConfigMode::ProverClient => std::env::var("PROVER_ENV_FILE") + .map(Into::into) + .unwrap_or(cargo_manifest_dir.join(".env.prover")), + } + } +} + +/// Reads the desired .env* file +/// .env if running the sequencer/L2 node +/// .env.prover if running the prover_client +pub fn read_env_file_by_config(config_mode: ConfigMode) -> Result<(), errors::ConfigError> { + let env_file_path = config_mode.get_env_path_or_default(); + let env_file = open_env_file(&env_file_path)?; let reader = std::io::BufReader::new(env_file); for line in reader.lines() { @@ -41,16 +93,17 @@ pub fn read_env_file() -> Result<(), errors::ConfigError> { Ok(()) } -pub fn read_env_as_lines( +pub fn read_env_as_lines_by_config( + config_mode: ConfigMode, ) -> Result>, errors::ConfigError> { - let env_file = open_env_file()?; + let env_file_path = config_mode.get_env_path_or_default(); + let env_file = open_env_file(&env_file_path)?; let reader = std::io::BufReader::new(env_file); Ok(reader.lines()) } -fn open_env_file() -> std::io::Result { - let path = get_env_file_path(); +fn open_env_file(path: &Path) -> std::io::Result { match std::fs::File::open(path) { Ok(file) => Ok(file), Err(err) if err.kind() == std::io::ErrorKind::NotFound => { @@ -61,19 +114,16 @@ fn open_env_file() -> std::io::Result { } } -pub fn get_env_file_path() -> PathBuf { - match std::env::var("ENV_FILE") { - Ok(env_file_path) => PathBuf::from(env_file_path), - Err(_) => PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(".env"), - } -} +pub fn write_env_file_by_config( + lines: Vec, + config_mode: ConfigMode, +) -> Result<(), errors::ConfigError> { + let env_file_path = config_mode.get_env_path_or_default(); -pub fn write_env_file(lines: Vec) -> Result<(), errors::ConfigError> { - let path = get_env_file_path(); let env_file = match std::fs::OpenOptions::new() .write(true) .truncate(true) - .open(path) + .open(env_file_path) { Ok(file) => file, Err(err) if err.kind() == std::io::ErrorKind::NotFound => { diff --git a/crates/l2/parse_toml.rs b/crates/l2/utils/config/toml_parser.rs similarity index 64% rename from crates/l2/parse_toml.rs rename to crates/l2/utils/config/toml_parser.rs index 08d13ad7dd..683bb508f0 100644 --- a/crates/l2/parse_toml.rs +++ b/crates/l2/utils/config/toml_parser.rs @@ -1,4 +1,7 @@ -use crate::{errors::*, utils}; +use crate::utils::config::{ + errors::{ConfigError, TomlParserError}, + ConfigMode, +}; use serde::Deserialize; use std::fs::OpenOptions; use std::io::Write; @@ -16,11 +19,10 @@ struct Deployer { } impl Deployer { - pub fn to_env(&self) -> String { + fn to_env(&self) -> String { let prefix = "DEPLOYER"; format!( - " -{prefix}_ADDRESS={} + "{prefix}_ADDRESS={} {prefix}_PRIVATE_KEY={} {prefix}_RISC0_CONTRACT_VERIFIER={} {prefix}_SP1_CONTRACT_VERIFIER={} @@ -47,7 +49,7 @@ struct Eth { } impl Eth { - pub fn to_env(&self) -> String { + fn to_env(&self) -> String { let prefix = "ETH"; format!( " @@ -65,7 +67,7 @@ struct Engine { } impl Engine { - pub fn to_env(&self) -> String { + fn to_env(&self) -> String { let prefix = "ENGINE_API"; format!( " @@ -110,7 +112,7 @@ struct Proposer { } impl Proposer { - pub fn to_env(&self) -> String { + fn to_env(&self) -> String { let prefix = "PROPOSER"; format!( " @@ -152,26 +154,42 @@ impl Committer { } #[derive(Deserialize, Debug)] -struct Client { +struct ProverClient { prover_server_endpoint: String, + sp1_prover: String, + risc0_dev_mode: u64, interval_ms: u64, } -impl Client { - pub fn to_env(&self) -> String { +impl ProverClient { + fn to_env(&self) -> String { let prefix = "PROVER_CLIENT"; format!( - " -{prefix}_PROVER_SERVER_ENDPOINT={} + "{prefix}_PROVER_SERVER_ENDPOINT={} {prefix}_INTERVAL_MS={} +RISC0_DEV_MODE={} +SP1_PROVER={} ", - self.prover_server_endpoint, self.interval_ms + self.prover_server_endpoint, self.interval_ms, self.risc0_dev_mode, self.sp1_prover ) } } #[derive(Deserialize, Debug)] -struct Server { +struct ProverClientConfig { + prover_client: ProverClient, +} + +impl ProverClientConfig { + fn to_env(&self) -> String { + let mut env_representation = String::new(); + env_representation.push_str(&self.prover_client.to_env()); + env_representation + } +} + +#[derive(Deserialize, Debug)] +struct ProverServer { listen_ip: String, listen_port: u64, verifier_address: String, @@ -180,8 +198,8 @@ struct Server { dev_interval_ms: u64, } -impl Server { - pub fn to_env(&self) -> String { +impl ProverServer { + fn to_env(&self) -> String { let prefix = "PROVER_SERVER"; format!( " @@ -202,21 +220,6 @@ impl Server { } } -#[derive(Deserialize, Debug)] -struct Prover { - client: Client, - server: Server, -} - -impl Prover { - pub fn to_env(&self) -> String { - let mut env = String::new(); - env.push_str(&self.client.to_env()); - env.push_str(&self.server.to_env()); - env - } -} - #[derive(Deserialize, Debug)] struct L2Config { deployer: Deployer, @@ -225,25 +228,27 @@ struct L2Config { watcher: Watcher, proposer: Proposer, committer: Committer, - prover: Prover, + prover_server: ProverServer, } impl L2Config { - pub fn to_env(&self) -> String { + fn to_env(&self) -> String { let mut env_representation = String::new(); + env_representation.push_str(&self.deployer.to_env()); env_representation.push_str(&self.eth.to_env()); env_representation.push_str(&self.engine.to_env()); env_representation.push_str(&self.watcher.to_env()); env_representation.push_str(&self.proposer.to_env()); env_representation.push_str(&self.committer.to_env()); - env_representation.push_str(&self.prover.to_env()); + env_representation.push_str(&self.prover_server.to_env()); + env_representation } } -pub fn write_to_env(config: String) -> Result<(), ConfigError> { - let env_file_path = utils::config::get_env_file_path(); +fn write_to_env(config: String, mode: ConfigMode) -> Result<(), TomlParserError> { + let env_file_path = mode.get_env_path_or_default(); let env_file = OpenOptions::new() .write(true) .create(true) @@ -252,7 +257,7 @@ pub fn write_to_env(config: String) -> Result<(), ConfigError> { match env_file { Ok(mut file) => { file.write_all(&config.into_bytes()).map_err(|_| { - ConfigError::EnvWriteError(format!( + TomlParserError::EnvWriteError(format!( "Couldn't write file in {}, line: {}", file!(), line!() @@ -260,7 +265,7 @@ pub fn write_to_env(config: String) -> Result<(), ConfigError> { })?; } Err(err) => { - return Err(ConfigError::EnvWriteError(format!( + return Err(TomlParserError::EnvWriteError(format!( "Error: {}. Couldn't write file in {}, line: {}", err, file!(), @@ -271,9 +276,43 @@ pub fn write_to_env(config: String) -> Result<(), ConfigError> { Ok(()) } -pub fn read_toml(toml_path: String) -> Result<(), ConfigError> { - let file = std::fs::read_to_string(toml_path).map_err(|_| ConfigError::TomlFileNotFound)?; - let config: L2Config = toml::from_str(&file).map_err(|_| ConfigError::TomlFormat)?; - write_to_env(config.to_env())?; +fn read_config(config_path: String, mode: ConfigMode) -> Result<(), ConfigError> { + let toml_path = mode.get_config_file_path(&config_path); + let toml_file_name = toml_path + .file_name() + .ok_or(ConfigError::Custom("Invalid CONFIGS_PATH".to_string()))? + .to_str() + .ok_or(ConfigError::Custom("Couldn't convert to_str()".to_string()))? + .to_owned(); + let file = std::fs::read_to_string(toml_path) + .map_err(|_| TomlParserError::TomlFileNotFound(toml_file_name.clone()))?; + match mode { + ConfigMode::Sequencer => { + let config: L2Config = toml::from_str(&file) + .map_err(|_| TomlParserError::TomlFormat(toml_file_name.clone()))?; + write_to_env(config.to_env(), mode)?; + } + ConfigMode::ProverClient => { + let config: ProverClientConfig = + toml::from_str(&file).map_err(|_| TomlParserError::TomlFormat(toml_file_name))?; + write_to_env(config.to_env(), mode)?; + } + } + Ok(()) } + +pub fn parse_configs(mode: ConfigMode) -> Result<(), ConfigError> { + #[allow(clippy::expect_fun_call, clippy::expect_used)] + let config_path = std::env::var("CONFIGS_PATH").expect( + format!( + "CONFIGS_PATH environment variable not defined. Expected in {}, line: {} +If running locally, a reasonable value would be CONFIGS_PATH=./configs", + file!(), + line!() + ) + .as_str(), + ); + + read_config(config_path, mode).map_err(From::from) +}