diff --git a/Makefile b/Makefile index 72a10ccb76..b2c632d3b4 100644 --- a/Makefile +++ b/Makefile @@ -504,7 +504,6 @@ task_sender_send_infinite_proofs_devnet: cargo run --release -- send-infinite-proofs \ --burst-size $(BURST_SIZE) --burst-time-secs $(BURST_TIME_SECS) \ --eth-rpc-url http://localhost:8545 \ - --batcher-url ws://localhost:8080 \ --network devnet \ --proofs-dirpath $(CURDIR)/scripts/test_files/task_sender/proofs \ --private-keys-filepath $(CURDIR)/batcher/aligned-task-sender/wallets/devnet @@ -512,8 +511,8 @@ task_sender_send_infinite_proofs_devnet: task_sender_test_connections_devnet: @cd batcher/aligned-task-sender && \ cargo run --release -- test-connections \ - --batcher-url ws://localhost:8080 \ - --num-senders $(NUM_SENDERS) + --num-senders $(NUM_SENDERS) \ + --network devnet # ===== HOLESKY-STAGE ===== task_sender_generate_and_fund_wallets_holesky_stage: @@ -532,7 +531,6 @@ task_sender_send_infinite_proofs_holesky_stage: cargo run --release -- send-infinite-proofs \ --burst-size $(BURST_SIZE) --burst-time-secs $(BURST_TIME_SECS) \ --eth-rpc-url https://ethereum-holesky-rpc.publicnode.com \ - --batcher-url wss://stage.batcher.alignedlayer.com \ --network holesky-stage \ --proofs-dirpath $(CURDIR)/scripts/test_files/task_sender/proofs \ --private-keys-filepath $(CURDIR)/batcher/aligned-task-sender/wallets/holesky-stage @@ -540,13 +538,14 @@ task_sender_send_infinite_proofs_holesky_stage: task_sender_test_connections_holesky_stage: @cd batcher/aligned-task-sender && \ cargo run --release -- test-connections \ - --batcher-url wss://stage.batcher.alignedlayer.com \ - --num-senders $(NUM_SENDERS) + --num-senders $(NUM_SENDERS) \ + --network holesky-stage __UTILS__: aligned_get_user_balance_devnet: @cd batcher/aligned/ && cargo run --release -- get-user-balance \ - --user_addr $(USER_ADDR) + --user_addr $(USER_ADDR) \ + --network devnet aligned_get_user_balance_holesky: @cd batcher/aligned/ && cargo run --release -- get-user-balance \ diff --git a/alerts/.env.devnet b/alerts/.env.devnet index cf967dba1f..55bfc07bab 100644 --- a/alerts/.env.devnet +++ b/alerts/.env.devnet @@ -24,7 +24,6 @@ # Variables for sender_with_alert.sh REPETITIONS=8 SENDER_ADDRESS=0x14dC79964da2C08b23698B3D3cc7Ca32193d9955 -BATCHER_URL=ws://localhost:8080 RPC_URL=http://localhost:8545 EXPLORER_URL=http://localhost:3000 NETWORK=devnet diff --git a/alerts/.env.example b/alerts/.env.example index 2c0fe8a843..d210912a5e 100644 --- a/alerts/.env.example +++ b/alerts/.env.example @@ -26,7 +26,6 @@ NETWORK= # Variables for sender_with_alert.sh REPETITIONS= SENDER_ADDRESS= -BATCHER_URL= RPC_URL= EXPLORER_URL= NETWORK= diff --git a/alerts/sender_with_alert.sh b/alerts/sender_with_alert.sh index 64d372627b..9af03efea3 100755 --- a/alerts/sender_with_alert.sh +++ b/alerts/sender_with_alert.sh @@ -4,7 +4,6 @@ # - REPETITIONS # - EXPLORER_URL # - SENDER_ADDRESS -# - BATCHER_URL # - RPC_URL # - EXPLORER_URL # - NETWORK @@ -92,7 +91,7 @@ do mkdir -p ./scripts/test_files/gnark_groth16_bn254_infinite_script/infinite_proofs ## Generate Proof - nonce=$(aligned get-user-nonce --batcher_url $BATCHER_URL --user_addr $SENDER_ADDRESS 2>&1 | awk '{print $9}') + nonce=$(aligned get-user-nonce --network $NETWORK --user_addr $SENDER_ADDRESS 2>&1 | awk '{print $9}') x=$((nonce + 1)) # So we don't have any issues with nonce = 0 echo "Generating proof $x != 0" go run ./scripts/test_files/gnark_groth16_bn254_infinite_script/cmd/main.go $x @@ -108,7 +107,6 @@ do --proof_generator_addr $SENDER_ADDRESS \ --private_key $PRIVATE_KEY \ --rpc_url $RPC_URL \ - --batcher_url $BATCHER_URL \ --network $NETWORK \ --max_fee 0.004ether \ 2>&1) diff --git a/batcher/aligned-batcher/src/lib.rs b/batcher/aligned-batcher/src/lib.rs index a181ea8037..7fa09d539a 100644 --- a/batcher/aligned-batcher/src/lib.rs +++ b/batcher/aligned-batcher/src/lib.rs @@ -1522,10 +1522,7 @@ impl Batcher { Ok(()) } Err(e) => { - error!( - "Failed to send batch to contract, batch will be lost: {:?}", - e - ); + error!("Failed to send batch to contract: {:?}", e); self.metrics.reverted_batches.inc(); Err(e) diff --git a/batcher/aligned-sdk/src/communication/batch.rs b/batcher/aligned-sdk/src/communication/batch.rs index d3935593f1..56c0b2ad11 100644 --- a/batcher/aligned-sdk/src/communication/batch.rs +++ b/batcher/aligned-sdk/src/communication/batch.rs @@ -41,7 +41,7 @@ pub async fn await_batch_verification( network: Network, ) -> Result<(), errors::SubmitError> { for _ in 0..RETRIES { - if is_proof_verified(aligned_verification_data, network, rpc_url) + if is_proof_verified(aligned_verification_data, network.clone(), rpc_url) .await .is_ok_and(|r| r) { diff --git a/batcher/aligned-sdk/src/core/constants.rs b/batcher/aligned-sdk/src/core/constants.rs index 01d0215952..ee6dd7b366 100644 --- a/batcher/aligned-sdk/src/core/constants.rs +++ b/batcher/aligned-sdk/src/core/constants.rs @@ -38,3 +38,25 @@ pub const BUMP_MIN_RETRY_DELAY: u64 = 500; // milliseconds pub const BUMP_MAX_RETRIES: usize = 33; // ~ 1 day pub const BUMP_BACKOFF_FACTOR: f32 = 2.0; pub const BUMP_MAX_RETRY_DELAY: u64 = 3600; // seconds + +/// NETWORK ADDRESSES /// +/// BatcherPaymentService +pub const BATCHER_PAYMENT_SERVICE_ADDRESS_DEVNET: &str = + "0x7bc06c482DEAd17c0e297aFbC32f6e63d3846650"; +pub const BATCHER_PAYMENT_SERVICE_ADDRESS_HOLESKY: &str = + "0x815aeCA64a974297942D2Bbf034ABEe22a38A003"; +pub const BATCHER_PAYMENT_SERVICE_ADDRESS_HOLESKY_STAGE: &str = + "0x7577Ec4ccC1E6C529162ec8019A49C13F6DAd98b"; +pub const BATCHER_PAYMENT_SERVICE_ADDRESS_MAINNET: &str = + "0xb0567184A52cB40956df6333510d6eF35B89C8de"; +/// AlignedServiceManager +pub const ALIGNED_SERVICE_MANAGER_DEVNET: &str = "0x851356ae760d987E095750cCeb3bC6014560891C"; +pub const ALIGNED_SERVICE_MANAGER_HOLESKY: &str = "0x58F280BeBE9B34c9939C3C39e0890C81f163B623"; +pub const ALIGNED_SERVICE_MANAGER_HOLESKY_STAGE: &str = + "0x9C5231FC88059C086Ea95712d105A2026048c39B"; +pub const ALIGNED_SERVICE_MANAGER_MAINNET: &str = "0xeF2A435e5EE44B2041100EF8cbC8ae035166606c"; +/// Batcher URL's +pub const BATCHER_URL_DEVNET: &str = "ws://localhost:8080"; +pub const BATCHER_URL_HOLESKY: &str = "wss://batcher.alignedlayer.com"; +pub const BATCHER_URL_HOLESKY_STAGE: &str = "wss://stage.batcher.alignedlayer.com"; +pub const BATCHER_URL_MAINNET: &str = "wss://mainnet.batcher.alignedlayer.com"; diff --git a/batcher/aligned-sdk/src/core/types.rs b/batcher/aligned-sdk/src/core/types.rs index ba59b5d6b5..2acf6a6810 100644 --- a/batcher/aligned-sdk/src/core/types.rs +++ b/batcher/aligned-sdk/src/core/types.rs @@ -11,6 +11,7 @@ use ethers::types::transaction::eip712::Eip712; use ethers::types::transaction::eip712::Eip712Error; use ethers::types::Address; use ethers::types::Signature; +use ethers::types::H160; use ethers::types::U256; use lambdaworks_crypto::merkle_tree::{ merkle::MerkleTree, proof::Proof, traits::IsMerkleTreeBackend, @@ -18,6 +19,13 @@ use lambdaworks_crypto::merkle_tree::{ use serde::{Deserialize, Serialize}; use sha3::{Digest, Keccak256}; +use super::constants::{ + ALIGNED_SERVICE_MANAGER_DEVNET, ALIGNED_SERVICE_MANAGER_HOLESKY, + ALIGNED_SERVICE_MANAGER_HOLESKY_STAGE, ALIGNED_SERVICE_MANAGER_MAINNET, + BATCHER_PAYMENT_SERVICE_ADDRESS_DEVNET, BATCHER_PAYMENT_SERVICE_ADDRESS_HOLESKY, + BATCHER_PAYMENT_SERVICE_ADDRESS_HOLESKY_STAGE, BATCHER_PAYMENT_SERVICE_ADDRESS_MAINNET, + BATCHER_URL_DEVNET, BATCHER_URL_HOLESKY, BATCHER_URL_HOLESKY_STAGE, BATCHER_URL_MAINNET, +}; use super::errors::VerifySignatureError; // VerificationData is a bytes32 instead of a VerificationData struct because in the BatcherPaymentService contract @@ -396,27 +404,45 @@ pub enum GetNonceResponseMessage { InvalidRequest(String), } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub enum Network { Devnet, Holesky, HoleskyStage, Mainnet, + Custom(String, String, String), } -impl FromStr for Network { - type Err = String; - - fn from_str(s: &str) -> Result { - match s.to_lowercase().as_str() { - "holesky" => Ok(Network::Holesky), - "holesky-stage" => Ok(Network::HoleskyStage), - "devnet" => Ok(Network::Devnet), - "mainnet" => Ok(Network::Mainnet), - _ => Err( - "Invalid network, possible values are: \"holesky\", \"holesky-stage\", \"devnet\", \"mainnet\"" - .to_string(), - ), +impl Network { + pub fn get_aligned_service_manager_address(&self) -> ethers::types::H160 { + match self { + Self::Devnet => H160::from_str(ALIGNED_SERVICE_MANAGER_DEVNET).unwrap(), + Self::Holesky => H160::from_str(ALIGNED_SERVICE_MANAGER_HOLESKY).unwrap(), + Self::HoleskyStage => H160::from_str(ALIGNED_SERVICE_MANAGER_HOLESKY_STAGE).unwrap(), + Self::Mainnet => H160::from_str(ALIGNED_SERVICE_MANAGER_MAINNET).unwrap(), + Self::Custom(s, _, _) => H160::from_str(s.as_str()).unwrap(), + } + } + + pub fn get_batcher_payment_service_address(&self) -> ethers::types::H160 { + match self { + Self::Devnet => H160::from_str(BATCHER_PAYMENT_SERVICE_ADDRESS_DEVNET).unwrap(), + Self::Holesky => H160::from_str(BATCHER_PAYMENT_SERVICE_ADDRESS_HOLESKY).unwrap(), + Self::HoleskyStage => { + H160::from_str(BATCHER_PAYMENT_SERVICE_ADDRESS_HOLESKY_STAGE).unwrap() + } + Self::Mainnet => H160::from_str(BATCHER_PAYMENT_SERVICE_ADDRESS_MAINNET).unwrap(), + Self::Custom(_, s, _) => H160::from_str(s.as_str()).unwrap(), + } + } + + pub fn get_batcher_url(&self) -> &str { + match self { + Self::Devnet => BATCHER_URL_DEVNET, + Self::Holesky => BATCHER_URL_HOLESKY, + Self::HoleskyStage => BATCHER_URL_HOLESKY_STAGE, + Self::Mainnet => BATCHER_URL_MAINNET, + Self::Custom(_, _, s) => s.as_str(), } } } diff --git a/batcher/aligned-sdk/src/sdk.rs b/batcher/aligned-sdk/src/sdk.rs index e933ab6f8d..4d8221cea7 100644 --- a/batcher/aligned-sdk/src/sdk.rs +++ b/batcher/aligned-sdk/src/sdk.rs @@ -28,10 +28,10 @@ use ethers::{ prelude::k256::ecdsa::SigningKey, providers::{Http, Middleware, Provider}, signers::{LocalWallet, Wallet}, - types::{Address, H160, U256}, + types::{Address, U256}, }; use sha3::{Digest, Keccak256}; -use std::{str::FromStr, sync::Arc}; +use std::sync::Arc; use tokio::{net::TcpStream, sync::Mutex}; use tokio_tungstenite::{connect_async, tungstenite::Message, MaybeTlsStream, WebSocketStream}; @@ -49,7 +49,6 @@ use std::path::PathBuf; /// Submits multiple proofs to the batcher to be verified in Aligned and waits for the verification on-chain. /// # Arguments -/// * `batcher_url` - The url of the batcher to which the proof will be submitted. /// * `eth_rpc_url` - The URL of the Ethereum RPC node. /// * `chain` - The chain on which the verification will be done. /// * `verification_data` - An array of verification data of each proof. @@ -79,7 +78,6 @@ use std::path::PathBuf; /// * `GenericError` if the error doesn't match any of the previous ones. #[allow(clippy::too_many_arguments)] // TODO: Refactor this function, use NoncedVerificationData pub async fn submit_multiple_and_wait_verification( - batcher_url: &str, eth_rpc_url: &str, network: Network, verification_data: &[VerificationData], @@ -87,22 +85,16 @@ pub async fn submit_multiple_and_wait_verification( wallet: Wallet, nonce: U256, ) -> Vec> { - let mut aligned_verification_data = submit_multiple( - batcher_url, - network, - verification_data, - max_fee, - wallet, - nonce, - ) - .await; + let mut aligned_verification_data = + submit_multiple(network.clone(), verification_data, max_fee, wallet, nonce).await; // TODO: open issue: use a join to .await all at the same time, avoiding the loop // And await only once per batch, no need to await multiple proofs if they are in the same batch. let mut error_awaiting_batch_verification: Option = None; for aligned_verification_data_item in aligned_verification_data.iter().flatten() { if let Err(e) = - await_batch_verification(aligned_verification_data_item, eth_rpc_url, network).await + await_batch_verification(aligned_verification_data_item, eth_rpc_url, network.clone()) + .await { error_awaiting_batch_verification = Some(e); break; @@ -218,7 +210,6 @@ async fn fetch_gas_price( /// Submits multiple proofs to the batcher to be verified in Aligned. /// # Arguments -/// * `batcher_url` - The url of the batcher to which the proof will be submitted. /// * `network` - The netork on which the verification will be done. /// * `verification_data` - An array of verification data of each proof. /// * `max_fees` - An array of the maximum fee that the submitter is willing to pay for each proof verification. @@ -242,14 +233,13 @@ async fn fetch_gas_price( /// * `ProofQueueFlushed` if there is an error in the batcher and the proof queue is flushed. /// * `GenericError` if the error doesn't match any of the previous ones. pub async fn submit_multiple( - batcher_url: &str, network: Network, verification_data: &[VerificationData], max_fee: U256, wallet: Wallet, nonce: U256, ) -> Vec> { - let (ws_stream, _) = match connect_async(batcher_url).await { + let (ws_stream, _) = match connect_async(network.get_batcher_url()).await { Ok((ws_stream, response)) => (ws_stream, response), Err(e) => return vec![Err(errors::SubmitError::WebSocketConnectionError(e))], }; @@ -271,28 +261,6 @@ pub async fn submit_multiple( .await } -pub fn get_payment_service_address(network: Network) -> ethers::types::H160 { - match network { - Network::Devnet => H160::from_str("0x7bc06c482DEAd17c0e297aFbC32f6e63d3846650").unwrap(), - Network::Holesky => H160::from_str("0x815aeCA64a974297942D2Bbf034ABEe22a38A003").unwrap(), - Network::HoleskyStage => { - H160::from_str("0x7577Ec4ccC1E6C529162ec8019A49C13F6DAd98b").unwrap() - } - Network::Mainnet => H160::from_str("0xb0567184A52cB40956df6333510d6eF35B89C8de").unwrap(), - } -} - -pub fn get_aligned_service_manager_address(network: Network) -> ethers::types::H160 { - match network { - Network::Devnet => H160::from_str("0x851356ae760d987E095750cCeb3bC6014560891C").unwrap(), - Network::Holesky => H160::from_str("0x58F280BeBE9B34c9939C3C39e0890C81f163B623").unwrap(), - Network::HoleskyStage => { - H160::from_str("0x9C5231FC88059C086Ea95712d105A2026048c39B").unwrap() - } - Network::Mainnet => H160::from_str("0xeF2A435e5EE44B2041100EF8cbC8ae035166606c").unwrap(), - } -} - // Will submit the proofs to the batcher and wait for their responses // Will return once all proofs are responded, or up to a proof that is responded with an error async fn _submit_multiple( @@ -328,7 +296,7 @@ async fn _submit_multiple( let response_stream = Arc::new(Mutex::new(response_stream)); - let payment_service_addr = get_payment_service_address(network); + let payment_service_addr = network.get_batcher_payment_service_address(); let result = async { let sent_verification_data_rev = send_messages( @@ -354,7 +322,6 @@ async fn _submit_multiple( /// Submits a proof to the batcher to be verified in Aligned and waits for the verification on-chain. /// # Arguments -/// * `batcher_url` - The url of the batcher to which the proof will be submitted. /// * `eth_rpc_url` - The URL of the Ethereum RPC node. /// * `chain` - The chain on which the verification will be done. /// * `verification_data` - The verification data of the proof. @@ -384,7 +351,6 @@ async fn _submit_multiple( /// * `GenericError` if the error doesn't match any of the previous ones. #[allow(clippy::too_many_arguments)] // TODO: Refactor this function, use NoncedVerificationData pub async fn submit_and_wait_verification( - batcher_url: &str, eth_rpc_url: &str, network: Network, verification_data: &VerificationData, @@ -395,7 +361,6 @@ pub async fn submit_and_wait_verification( let verification_data = vec![verification_data.clone()]; let aligned_verification_data = submit_multiple_and_wait_verification( - batcher_url, eth_rpc_url, network, &verification_data, @@ -416,7 +381,6 @@ pub async fn submit_and_wait_verification( /// Submits a proof to the batcher to be verified in Aligned. /// # Arguments -/// * `batcher_url` - The url of the batcher to which the proof will be submitted. /// * `chain` - The chain on which the verification will be done. /// * `verification_data` - The verification data of the proof. /// * `max_fee` - The maximum fee that the submitter is willing to pay for the verification. @@ -440,7 +404,6 @@ pub async fn submit_and_wait_verification( /// * `ProofQueueFlushed` if there is an error in the batcher and the proof queue is flushed. /// * `GenericError` if the error doesn't match any of the previous ones. pub async fn submit( - batcher_url: &str, network: Network, verification_data: &VerificationData, max_fee: U256, @@ -449,15 +412,8 @@ pub async fn submit( ) -> Result { let verification_data = vec![verification_data.clone()]; - let aligned_verification_data = submit_multiple( - batcher_url, - network, - &verification_data, - max_fee, - wallet, - nonce, - ) - .await; + let aligned_verification_data = + submit_multiple(network, &verification_data, max_fee, wallet, nonce).await; match aligned_verification_data.first() { Some(Ok(aligned_verification_data)) => Ok(aligned_verification_data.clone()), @@ -498,8 +454,8 @@ async fn _is_proof_verified( network: Network, eth_rpc_provider: Provider, ) -> Result { - let contract_address = get_aligned_service_manager_address(network); - let payment_service_addr = get_payment_service_address(network); + let contract_address = network.clone().get_aligned_service_manager_address(); + let payment_service_addr = network.get_batcher_payment_service_address(); // All the elements from the merkle proof have to be concatenated let merkle_proof: Vec = aligned_verification_data @@ -558,19 +514,21 @@ pub fn get_vk_commitment( /// as the batcher proofs might not yet be on ethereum, /// producing an out-of-sync nonce with the payment service contract on ethereum /// # Arguments -/// * `batcher_url` - The batcher websocket url. /// * `address` - The user address for which the nonce will be retrieved. +/// * `network` - The network from which the nonce will be retrieved. /// # Returns /// * The next nonce of the proof submitter account. /// # Errors /// * `EthRpcError` if the batcher has an error in the Ethereum call when retrieving the nonce if not already cached. pub async fn get_nonce_from_batcher( - batcher_ws_url: &str, + network: Network, address: Address, ) -> Result { - let (ws_stream, _) = connect_async(batcher_ws_url).await.map_err(|_| { - GetNonceError::ConnectionFailed("Ws connection to batcher failed".to_string()) - })?; + let (ws_stream, _) = connect_async(network.get_batcher_url()) + .await + .map_err(|_| { + GetNonceError::ConnectionFailed("Ws connection to batcher failed".to_string()) + })?; debug!("WebSocket handshake has been successfully completed"); let (mut ws_write, mut ws_read) = ws_stream.split(); @@ -640,7 +598,7 @@ pub async fn get_nonce_from_ethereum( let eth_rpc_provider = Provider::::try_from(eth_rpc_url) .map_err(|e| GetNonceError::EthRpcError(e.to_string()))?; - let payment_service_address = get_payment_service_address(network); + let payment_service_address = network.get_batcher_payment_service_address(); match batcher_payment_service(eth_rpc_provider, payment_service_address).await { Ok(contract) => { @@ -691,7 +649,7 @@ pub async fn deposit_to_aligned( signer: SignerMiddleware, LocalWallet>, network: Network, ) -> Result { - let payment_service_address = get_payment_service_address(network); + let payment_service_address = network.get_batcher_payment_service_address(); let from = signer.address(); let tx = TransactionRequest::new() @@ -729,7 +687,7 @@ pub async fn get_balance_in_aligned( let eth_rpc_provider = Provider::::try_from(eth_rpc_url) .map_err(|e| errors::BalanceError::EthereumProviderError(e.to_string()))?; - let payment_service_address = get_payment_service_address(network); + let payment_service_address = network.get_batcher_payment_service_address(); match batcher_payment_service(eth_rpc_provider, payment_service_address).await { Ok(batcher_payment_service) => { diff --git a/batcher/aligned-task-sender/README.md b/batcher/aligned-task-sender/README.md index 73b1df8686..c73d454b1e 100644 --- a/batcher/aligned-task-sender/README.md +++ b/batcher/aligned-task-sender/README.md @@ -61,8 +61,7 @@ To run it, you can: cargo run --release -- send-infinite-proofs \ --burst-size --burst-time-secs \ --eth-rpc-url \ - --batcher-url \ - --network holesky-stage \ + --network \ --proofs-dirpath $(PWD)/scripts/test_files/task_sender/proofs \ --private-keys-filepath ``` @@ -82,7 +81,6 @@ This command enables and hangs N connections with the Batcher. To run it, you can: ``` cargo run --release -- test-connections \ - --batcher-url \ --num-senders ``` diff --git a/batcher/aligned-task-sender/src/commands.rs b/batcher/aligned-task-sender/src/commands.rs index f074b91563..6a163c1910 100644 --- a/batcher/aligned-task-sender/src/commands.rs +++ b/batcher/aligned-task-sender/src/commands.rs @@ -2,7 +2,6 @@ use aligned_sdk::core::types::{Network, ProvingSystemId, VerificationData}; use aligned_sdk::sdk::{deposit_to_aligned, get_nonce_from_batcher, submit_multiple}; use ethers::prelude::*; use ethers::utils::parse_ether; -use futures_util::StreamExt; use k256::ecdsa::SigningKey; use log::{debug, error, info}; use rand::seq::SliceRandom; @@ -64,7 +63,7 @@ pub async fn generate_proofs(args: GenerateProofsArgs) { } pub async fn generate_and_fund_wallets(args: GenerateAndFundWalletsArgs) { - if matches!(args.network.into(), Network::Devnet) { + if matches!(args.network.clone().into(), Network::Devnet) { let Ok(eth_rpc_provider) = Provider::::try_from(args.eth_rpc_url.clone()) else { error!("Could not connect to eth rpc"); return; @@ -97,11 +96,12 @@ pub async fn generate_and_fund_wallets(args: GenerateAndFundWalletsArgs) { let funded_wallet_signer = SignerMiddleware::new(eth_rpc_provider.clone(), wallet.clone()); tokio::time::sleep(Duration::from_millis(50)).await; // To avoid overloading the RPC + let network = args.network.clone(); let handle = tokio::spawn(async move { if let Err(err) = deposit_to_aligned( amount_to_deposit_to_aligned, funded_wallet_signer.clone(), - args.network.into(), + network.into(), ) .await { @@ -188,8 +188,12 @@ pub async fn generate_and_fund_wallets(args: GenerateAndFundWalletsArgs) { amount_to_deposit_to_aligned, i ); let signer = SignerMiddleware::new(eth_rpc_provider.clone(), wallet.clone()); - if let Err(err) = - deposit_to_aligned(amount_to_deposit_to_aligned, signer, args.network.into()).await + if let Err(err) = deposit_to_aligned( + amount_to_deposit_to_aligned, + signer, + args.network.clone().into(), + ) + .await { error!("Could not deposit to aligned, err: {:?}", err); return; @@ -211,15 +215,13 @@ pub async fn generate_and_fund_wallets(args: GenerateAndFundWalletsArgs) { /// infinitely hangs connections pub async fn test_connection(args: TestConnectionsArgs) { - if args.batcher_url == "wss://batcher.alignedlayer.com" { - error!("Network not supported by the connection tester"); - return; - } info!("Going to only open a connection"); let mut handlers = vec![]; + let network: Network = args.network.into(); + let ws_url_string = network.get_batcher_url().to_string(); for i in 0..args.num_senders { - let ws_url = args.batcher_url.clone(); + let ws_url = ws_url_string.clone(); let handle = tokio::spawn(async move { let conn = connect_async(ws_url).await; if let Ok((mut ws_stream, _)) = conn { @@ -250,7 +252,7 @@ struct Sender { } pub async fn send_infinite_proofs(args: SendInfiniteProofsArgs) { - if matches!(args.network.into(), Network::Holesky) { + if matches!(args.network.clone().into(), Network::Holesky) { error!("Network not supported this infinite proof sender"); return; } @@ -314,18 +316,20 @@ pub async fn send_infinite_proofs(args: SendInfiniteProofsArgs) { let max_fee = U256::from_dec_str(&args.max_fee).expect("Invalid max fee"); let mut handles = vec![]; + let network: Network = args.network.into(); info!("Starting senders!"); for (i, sender) in senders.iter().enumerate() { - // this is necessary because of the move - let batcher_url = args.batcher_url.clone(); let wallet = sender.wallet.clone(); let verification_data = verification_data.clone(); + // this is necessary because of the move + let network_clone = network.clone(); // a thread to send tasks from each loaded wallet: let handle = tokio::spawn(async move { loop { + let n = network_clone.clone(); let mut result = Vec::with_capacity(args.burst_size); - let nonce = get_nonce_from_batcher(&batcher_url, wallet.address()) + let nonce = get_nonce_from_batcher(n.clone(), wallet.address()) .await .inspect_err(|e| { error!( @@ -344,13 +348,11 @@ pub async fn send_infinite_proofs(args: SendInfiniteProofsArgs) { info!( "Sending {:?} Proofs to Aligned Batcher on {:?} from sender {}, nonce: {}, address: {:?}", - args.burst_size, args.network, i, nonce, wallet.address(), + args.burst_size, n.clone(), i, nonce, wallet.address(), ); - let batcher_url = batcher_url.clone(); let aligned_verification_data = submit_multiple( - &batcher_url.clone(), - args.network.into(), + n.clone(), &verification_data_to_send.clone(), max_fee, wallet.clone(), diff --git a/batcher/aligned-task-sender/src/structs.rs b/batcher/aligned-task-sender/src/structs.rs index 7c5d36f6bb..fdeba460d0 100644 --- a/batcher/aligned-task-sender/src/structs.rs +++ b/batcher/aligned-task-sender/src/structs.rs @@ -2,6 +2,7 @@ use aligned_sdk::core::types::Network; use clap::Parser; use clap::Subcommand; use clap::ValueEnum; +use std::str::FromStr; #[derive(Parser, Debug)] #[command(version, about, long_about = None)] @@ -77,23 +78,15 @@ pub struct GenerateAndFundWalletsArgs { long = "private-keys-filepath" )] pub private_keys_filepath: String, - #[arg( - name = "The Ethereum network's name", - long = "network", - default_value = "devnet" - )] + #[clap(flatten)] pub network: NetworkArg, } #[derive(Parser, Debug)] #[command(version, about, long_about = None)] pub struct TestConnectionsArgs { - #[arg( - name = "Batcher connection address", - long = "batcher-url", - default_value = "ws://localhost:8080" - )] - pub batcher_url: String, + #[clap(flatten)] + pub network: NetworkArg, #[arg( name = "Number of spawned sockets", long = "num-senders", @@ -111,12 +104,6 @@ pub struct SendInfiniteProofsArgs { default_value = "http://localhost:8545" )] pub eth_rpc_url: String, - #[arg( - name = "Batcher connection address", - long = "batcher-url", - default_value = "ws://localhost:8080" - )] - pub batcher_url: String, #[arg( name = "Number of proofs per burst", long = "burst-size", @@ -131,11 +118,7 @@ pub struct SendInfiniteProofsArgs { pub burst_time_secs: u64, #[arg(name = "Max Fee", long = "max-fee", default_value = "1300000000000000")] pub max_fee: String, - #[arg( - name = "The Ethereum network's name", - long = "network", - default_value = "devnet" - )] + #[clap(flatten)] pub network: NetworkArg, #[arg( name = "Private keys filepath for the senders", @@ -150,21 +133,87 @@ pub struct SendInfiniteProofsArgs { pub proofs_dir: String, } -#[derive(Debug, Clone, Copy, ValueEnum)] -pub enum NetworkArg { +#[derive(Debug, Clone, Copy)] +enum NetworkNameArg { Devnet, Holesky, HoleskyStage, Mainnet, } +impl FromStr for NetworkNameArg { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "devnet" => Ok(NetworkNameArg::Devnet), + "holesky" => Ok(NetworkNameArg::Holesky), + "holesky-stage" => Ok(NetworkNameArg::HoleskyStage), + "mainnet" => Ok(NetworkNameArg::Mainnet), + _ => Err( + "Unknown network. Possible values: devnet, holesky, holesky-stage, mainnet" + .to_string(), + ), + } + } +} + +#[derive(Debug, clap::Args, Clone)] +pub struct NetworkArg { + #[arg( + name = "The working network's name", + long = "network", + default_value = "devnet", + help = "[possible values: devnet, holesky, holesky-stage, mainnet]" + )] + network: Option, + #[arg( + name = "Aligned Service Manager Contract Address", + long = "aligned_service_manager", + conflicts_with("The working network's name"), + requires("Batcher Payment Service Contract Address"), + requires("Batcher URL") + )] + aligned_service_manager_address: Option, + #[arg( + name = "Batcher Payment Service Contract Address", + long = "batcher_payment_service", + conflicts_with("The working network's name"), + requires("Aligned Service Manager Contract Address"), + requires("Batcher URL") + )] + batcher_payment_service_address: Option, + #[arg( + name = "Batcher URL", + long = "batcher_url", + conflicts_with("The working network's name"), + requires("Aligned Service Manager Contract Address"), + requires("Batcher Payment Service Contract Address") + )] + batcher_url: Option, +} + impl From for Network { - fn from(chain_arg: NetworkArg) -> Self { - match chain_arg { - NetworkArg::Devnet => Network::Devnet, - NetworkArg::Holesky => Network::Holesky, - NetworkArg::HoleskyStage => Network::HoleskyStage, - NetworkArg::Mainnet => Network::Mainnet, + fn from(network_arg: NetworkArg) -> Self { + let mut processed_network_argument = network_arg.clone(); + + if network_arg.batcher_url.is_some() + || network_arg.aligned_service_manager_address.is_some() + || network_arg.batcher_payment_service_address.is_some() + { + processed_network_argument.network = None; // We need this because network is Devnet as default, which is not true for a Custom network + } + + match processed_network_argument.network { + None => Network::Custom( + network_arg.aligned_service_manager_address.unwrap(), + network_arg.batcher_payment_service_address.unwrap(), + network_arg.batcher_url.unwrap(), + ), + Some(NetworkNameArg::Devnet) => Network::Devnet, + Some(NetworkNameArg::Holesky) => Network::Holesky, + Some(NetworkNameArg::HoleskyStage) => Network::HoleskyStage, + Some(NetworkNameArg::Mainnet) => Network::Mainnet, } } } diff --git a/batcher/aligned/generate_proof_and_send.sh b/batcher/aligned/generate_proof_and_send.sh index bcfa684544..42d01b8fbd 100755 --- a/batcher/aligned/generate_proof_and_send.sh +++ b/batcher/aligned/generate_proof_and_send.sh @@ -26,7 +26,6 @@ go run scripts/test_files/gnark_groth16_bn254_infinite_script/cmd/main.go $x # Set default values for RPC and BATCHER if they are not set RPC=${RPC:-http://localhost:8545} -BATCHER_CONN=${BATCHER_CONN:-ws://localhost:8080} if [ -z "$NETWORK" ]; then echo "NETWORK is not set. Setting it to devnet" NETWORK="devnet" @@ -42,7 +41,6 @@ cmd=( --vk "scripts/test_files/gnark_groth16_bn254_infinite_script/infinite_proofs/ineq_${x}_groth16.vk" --proof_generator_addr 0x66f9664f97F2b50F62D13eA064982f936dE76657 --rpc_url "$RPC" - --batcher_url "$BATCHER_CONN" --network "$NETWORK" ) diff --git a/batcher/aligned/send_infinite_sp1_tasks/send_infinite_sp1_tasks.sh b/batcher/aligned/send_infinite_sp1_tasks/send_infinite_sp1_tasks.sh index 079a8dcc57..620a465719 100755 --- a/batcher/aligned/send_infinite_sp1_tasks/send_infinite_sp1_tasks.sh +++ b/batcher/aligned/send_infinite_sp1_tasks/send_infinite_sp1_tasks.sh @@ -13,7 +13,6 @@ else fi RPC=${RPC:-http://localhost:8545} -BATCHER_CONN=${BATCHER_CONN:-ws://localhost:8080} if [ -z "$NETWORK" ]; then echo "NETWORK is not set. Setting it to devnet" NETWORK="devnet" @@ -33,7 +32,6 @@ do --vm_program ../../scripts/test_files/sp1/sp1_fibonacci.elf \ --proof_generator_addr "$random_address" \ --network "$NETWORK" \ - --batcher_url "$BATCHER_CONN" \ --repetitions "2" \ --rpc_url "$RPC" diff --git a/batcher/aligned/send_infinite_tasks.sh b/batcher/aligned/send_infinite_tasks.sh index d79ff8ab3b..90e24a4013 100755 --- a/batcher/aligned/send_infinite_tasks.sh +++ b/batcher/aligned/send_infinite_tasks.sh @@ -14,7 +14,6 @@ fi # Set default values for RPC and BATCHER if they are not set RPC=${RPC:-http://localhost:8545} -BATCHER_CONN=${BATCHER_CONN:-ws://localhost:8080} if [ -z "$NETWORK" ]; then echo "NETWORK is not set. Setting it to devnet" NETWORK="devnet" @@ -35,7 +34,6 @@ do --proof_generator_addr 0x66f9664f97F2b50F62D13eA064982f936dE76657 \ --repetitions "2" \ --rpc_url "$RPC" \ - --batcher_url "$BATCHER_CONN" \ --network "$NETWORK" cd ../.. diff --git a/batcher/aligned/src/main.rs b/batcher/aligned/src/main.rs index d82af51211..b058ade14d 100644 --- a/batcher/aligned/src/main.rs +++ b/batcher/aligned/src/main.rs @@ -64,12 +64,6 @@ pub enum AlignedCommands { #[derive(Parser, Debug)] #[command(version, about, long_about = None)] pub struct SubmitArgs { - #[arg( - name = "Batcher connection address", - long = "batcher_url", - default_value = "ws://localhost:8080" - )] - batcher_url: String, #[arg( name = "Ethereum RPC provider connection address", long = "rpc_url", @@ -114,30 +108,22 @@ pub struct SubmitArgs { max_fee: String, // String because U256 expects hex #[arg(name = "Nonce", long = "nonce")] nonce: Option, // String because U256 expects hex - #[arg( - name = "The working network's name", - long = "network", - default_value = "devnet" - )] + #[clap(flatten)] network: NetworkArg, } #[derive(Parser, Debug)] #[command(version, about, long_about = None)] pub struct DepositToBatcherArgs { + #[command(flatten)] + private_key_type: PrivateKeyType, #[arg( name = "Ethereum RPC provider address", long = "rpc_url", default_value = "http://localhost:8545" )] eth_rpc_url: String, - #[command(flatten)] - private_key_type: PrivateKeyType, - #[arg( - name = "The working network's name", - long = "network", - default_value = "devnet" - )] + #[clap(flatten)] network: NetworkArg, #[arg(name = "Amount to deposit", long = "amount", required = true)] amount: String, @@ -154,11 +140,8 @@ pub struct VerifyProofOnchainArgs { default_value = "http://localhost:8545" )] eth_rpc_url: String, - #[arg( - name = "The working network's name", - long = "network", - default_value = "devnet" - )] + + #[clap(flatten)] network: NetworkArg, } @@ -176,11 +159,7 @@ pub struct GetVkCommitmentArgs { #[derive(Parser, Debug)] #[command(version, about, long_about = None)] pub struct GetUserBalanceArgs { - #[arg( - name = "The working network's name", - long = "network", - default_value = "devnet" - )] + #[clap(flatten)] network: NetworkArg, #[arg( name = "Ethereum RPC provider address", @@ -199,12 +178,8 @@ pub struct GetUserBalanceArgs { #[derive(Parser, Debug)] #[command(version, about, long_about = None)] pub struct GetUserNonceArgs { - #[arg( - name = "Batcher connection address", - long = "batcher_url", - default_value = "ws://localhost:8080" - )] - batcher_url: String, + #[clap(flatten)] + network: NetworkArg, #[arg( name = "The user's Ethereum address", long = "user_addr", @@ -222,25 +197,6 @@ pub struct PrivateKeyType { private_key: Option, } -#[derive(Debug, Clone, ValueEnum, Copy)] -enum NetworkArg { - Devnet, - Holesky, - HoleskyStage, - Mainnet, -} - -impl From for Network { - fn from(env_arg: NetworkArg) -> Self { - match env_arg { - NetworkArg::Devnet => Network::Devnet, - NetworkArg::Holesky => Network::Holesky, - NetworkArg::HoleskyStage => Network::HoleskyStage, - NetworkArg::Mainnet => Network::Mainnet, - } - } -} - #[derive(Debug, Clone, ValueEnum)] pub enum ProvingSystemArg { #[clap(name = "GnarkPlonkBls12_381")] @@ -269,6 +225,93 @@ impl From for ProvingSystemId { } } +#[derive(Debug, Clone, Copy)] +enum NetworkNameArg { + Devnet, + Holesky, + HoleskyStage, + Mainnet, +} + +impl FromStr for NetworkNameArg { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "devnet" => Ok(NetworkNameArg::Devnet), + "holesky" => Ok(NetworkNameArg::Holesky), + "holesky-stage" => Ok(NetworkNameArg::HoleskyStage), + "mainnet" => Ok(NetworkNameArg::Mainnet), + _ => Err( + "Unknown network. Possible values: devnet, holesky, holesky-stage, mainnet" + .to_string(), + ), + } + } +} + +#[derive(Debug, clap::Args, Clone)] +struct NetworkArg { + #[arg( + name = "The working network's name", + long = "network", + default_value = "devnet", + help = "[possible values: devnet, holesky, holesky-stage, mainnet]" + )] + network: Option, + #[arg( + name = "Aligned Service Manager Contract Address", + long = "aligned_service_manager", + conflicts_with("The working network's name"), + requires("Batcher Payment Service Contract Address"), + requires("Batcher URL") + )] + aligned_service_manager_address: Option, + + #[arg( + name = "Batcher Payment Service Contract Address", + long = "batcher_payment_service", + conflicts_with("The working network's name"), + requires("Aligned Service Manager Contract Address"), + requires("Batcher URL") + )] + batcher_payment_service_address: Option, + + #[arg( + name = "Batcher URL", + long = "batcher_url", + conflicts_with("The working network's name"), + requires("Aligned Service Manager Contract Address"), + requires("Batcher Payment Service Contract Address") + )] + batcher_url: Option, +} + +impl From for Network { + fn from(network_arg: NetworkArg) -> Self { + let mut processed_network_argument = network_arg.clone(); + + if network_arg.batcher_url.is_some() + || network_arg.aligned_service_manager_address.is_some() + || network_arg.batcher_payment_service_address.is_some() + { + processed_network_argument.network = None; // We need this because network is Devnet as default, which is not true for a Custom network + } + + match processed_network_argument.network { + None => Network::Custom( + network_arg.aligned_service_manager_address.unwrap(), + network_arg.batcher_payment_service_address.unwrap(), + network_arg.batcher_url.unwrap(), + ), + Some(NetworkNameArg::Devnet) => Network::Devnet, + Some(NetworkNameArg::Holesky) => Network::Holesky, + Some(NetworkNameArg::HoleskyStage) => Network::HoleskyStage, + Some(NetworkNameArg::Mainnet) => Network::Mainnet, + } + } +} + #[tokio::main] async fn main() -> Result<(), AlignedError> { env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); @@ -295,7 +338,6 @@ async fn main() -> Result<(), AlignedError> { })?; let repetitions = submit_args.repetitions; - let connect_addr = submit_args.batcher_url.clone(); let keystore_path = &submit_args.private_key_type.keystore_path; let private_key = &submit_args.private_key_type.private_key; @@ -330,29 +372,31 @@ async fn main() -> Result<(), AlignedError> { let nonce = match &submit_args.nonce { Some(nonce) => U256::from_dec_str(nonce).map_err(|_| SubmitError::InvalidNonce)?, - None => get_nonce_from_batcher(&connect_addr, wallet.address()) - .await - .map_err(|e| match e { - aligned_sdk::core::errors::GetNonceError::EthRpcError(e) => { - SubmitError::GetNonceError(e) - } - aligned_sdk::core::errors::GetNonceError::ConnectionFailed(e) => { - SubmitError::GenericError(e) - } - aligned_sdk::core::errors::GetNonceError::InvalidRequest(e) => { - SubmitError::GenericError(e) - } - aligned_sdk::core::errors::GetNonceError::SerializationError(e) => { - SubmitError::GenericError(e) - } - aligned_sdk::core::errors::GetNonceError::ProtocolMismatch { - current, - expected, - } => SubmitError::ProtocolVersionMismatch { current, expected }, - aligned_sdk::core::errors::GetNonceError::UnexpectedResponse(e) => { - SubmitError::UnexpectedBatcherResponse(e) - } - })?, + None => { + get_nonce_from_batcher(submit_args.network.clone().into(), wallet.address()) + .await + .map_err(|e| match e { + aligned_sdk::core::errors::GetNonceError::EthRpcError(e) => { + SubmitError::GetNonceError(e) + } + aligned_sdk::core::errors::GetNonceError::ConnectionFailed(e) => { + SubmitError::GenericError(e) + } + aligned_sdk::core::errors::GetNonceError::InvalidRequest(e) => { + SubmitError::GenericError(e) + } + aligned_sdk::core::errors::GetNonceError::SerializationError(e) => { + SubmitError::GenericError(e) + } + aligned_sdk::core::errors::GetNonceError::ProtocolMismatch { + current, + expected, + } => SubmitError::ProtocolVersionMismatch { current, expected }, + aligned_sdk::core::errors::GetNonceError::UnexpectedResponse(e) => { + SubmitError::UnexpectedBatcherResponse(e) + } + })? + } }; warn!("Nonce: {nonce}"); @@ -364,7 +408,6 @@ async fn main() -> Result<(), AlignedError> { info!("Submitting proofs to the Aligned batcher..."); let aligned_verification_data_vec = submit_multiple( - &connect_addr, submit_args.network.into(), &verification_data_arr, max_fee_wei, @@ -532,7 +575,7 @@ async fn main() -> Result<(), AlignedError> { } GetUserNonce(args) => { let address = H160::from_str(&args.address).unwrap(); - match get_nonce_from_batcher(&args.batcher_url, address).await { + match get_nonce_from_batcher(args.network.into(), address).await { Ok(nonce) => { info!("Nonce for address {} is {}", address, nonce); } diff --git a/docs/3_guides/9_aligned_cli.md b/docs/3_guides/9_aligned_cli.md index da3bd2a500..42bcbb7af5 100644 --- a/docs/3_guides/9_aligned_cli.md +++ b/docs/3_guides/9_aligned_cli.md @@ -71,9 +71,15 @@ Submit a proof to the Aligned Layer batcher. - Default: `0.0013ether` - `--nonce `: Proof nonce. - By default, the nonce is set automatically. By setting the nonce manually, you can perform a proof replacement. -- `--network `: Network name to interact with. - - Default: `devnet` - - Possible values: `devnet`, `holesky`, `mainnet` +- One of the following, to specify which Network to interact with: + - `--network `: Network name to interact with. + - Default: `devnet` + - Possible values: `devnet`, `holesky`, `mainnet` + - For a custom Network, you must specify the following parameters: + - `--aligned_service_manager ` + - `--batcher_payment_service ` + - `--batcher_url ` + #### Example: ```bash @@ -108,9 +114,14 @@ Check if a proof was verified by Aligned on Ethereum. - Mainnet: `https://ethereum-rpc.publicnode.com` - Holesky: `https://ethereum-holesky-rpc.publicnode.com` - Also, you can use your own Ethereum RPC providers. -- `--network `: Network name to interact with. - - Default: `devnet` - - Possible values: `devnet`, `holesky`, `mainnet` +- One of the following, to specify which Network to interact with: + - `--network `: Network name to interact with. + - Default: `devnet` + - Possible values: `devnet`, `holesky`, `mainnet` + - For a custom Network, you must specify the following parameters: + - `--aligned_service_manager ` + - `--batcher_payment_service ` + - `--batcher_url ` #### Example: ```bash @@ -158,10 +169,15 @@ Deposits Ethereum into the Aligned Layer's `BatcherPaymentService.sol` contract. - Mainnet: `https://ethereum-rpc.publicnode.com` - Holesky: `https://ethereum-holesky-rpc.publicnode.com` - Also, you can use your own Ethereum RPC providers. -- `--network `: Network name to interact with. - - Default: `devnet` - - Possible values: `devnet`, `holesky`, `mainnet` - `--amount `: Amount of Ether to deposit. +- One of the following, to specify which Network to interact with: + - `--network `: Network name to interact with. + - Default: `devnet` + - Possible values: `devnet`, `holesky`, `mainnet` + - For a custom Network, you must specify the following parameters: + - `--aligned_service_manager ` + - `--batcher_payment_service ` + - `--batcher_url ` #### Example: ```bash @@ -186,9 +202,14 @@ Retrieves the user's balance in the Aligned Layer's contract. #### Options: -- `--network `: Network name to interact with. - - Default: `devnet` - - Possible values: `devnet`, `holesky`, `mainnet` +- One of the following, to specify which Network to interact with: + - `--network `: Network name to interact with. + - Default: `devnet` + - Possible values: `devnet`, `holesky`, `mainnet` + - For a custom Network, you must specify the following parameters: + - `--aligned_service_manager ` + - `--batcher_payment_service ` + - `--batcher_url ` - `--rpc_url `: User's Ethereum RPC provider connection address. - Default: `http://localhost:8545` - Mainnet: `https://ethereum-rpc.publicnode.com` @@ -218,11 +239,15 @@ Retrieves the user's current nonce from the batcher. `get-user-nonce [OPTIONS] --user_addr ` #### Options: -- **`--batcher_url`**: Websocket URL for the Aligned Layer batcher. - - Default: `ws://localhost:8080` - - Mainnet: `wss://mainnet.batcher.alignedlayer.com` - - Holesky: `wss://batcher.alignedlayer.com` - `--user_addr `: User's Ethereum address. +- One of the following, to specify which Network to interact with: + - `--network `: Network name to interact with. + - Default: `devnet` + - Possible values: `devnet`, `holesky`, `mainnet` + - For a custom Network, you must specify the following parameters: + - `--aligned_service_manager ` + - `--batcher_payment_service ` + - `--batcher_url ` #### Example: ```bash