From a40c52d7e3667cee470a520cc783f4f79f1659a5 Mon Sep 17 00:00:00 2001 From: Vid Kersic Date: Mon, 4 Sep 2023 23:11:55 +0200 Subject: [PATCH] chore: update CLI --- .dockerignore | 9 +- .gitignore | 3 +- Cargo.lock | 18 +- Cargo.toml | 1 + Makefile | 17 +- README.md | 17 +- bin/silius/Cargo.toml | 23 +- bin/silius/src/bundler.rs | 309 +++++++++++++++++++ bin/silius/src/cli.rs | 338 --------------------- bin/silius/src/cli/args.rs | 507 +++++++++++++++++++++++++++++++ bin/silius/src/cli/commands.rs | 138 +++++++++ bin/silius/src/cli/mod.rs | 88 ++++++ bin/silius/src/create-wallet.rs | 42 --- bin/silius/src/lib.rs | 1 + bin/silius/src/main.rs | 6 + bin/silius/src/silius-rpc.rs | 91 ------ bin/silius/src/silius-uopool.rs | 90 ------ bin/silius/src/silius.rs | 244 --------------- bin/silius/src/utils.rs | 7 +- bundler-spec-tests/launcher.sh | 10 +- crates/bundler/Cargo.toml | 2 +- crates/contracts/Cargo.toml | 2 +- crates/grpc/Cargo.toml | 2 +- crates/grpc/src/builder.rs | 2 +- crates/grpc/src/bundler.rs | 9 +- crates/grpc/src/uopool.rs | 87 +++--- crates/primitives/Cargo.toml | 2 +- crates/primitives/src/bundler.rs | 4 +- crates/primitives/src/consts.rs | 8 + crates/primitives/src/wallet.rs | 28 +- crates/rpc/Cargo.toml | 2 +- crates/rpc/src/lib.rs | 2 +- crates/rpc/src/rpc.rs | 160 ++++++---- crates/rpc/tests/common.rs | 20 +- crates/rpc/tests/rpc.rs | 77 +++-- crates/uopool/Cargo.toml | 2 +- docker-compose.yml | 25 +- examples/Cargo.toml | 2 +- tests/Cargo.toml | 2 +- 39 files changed, 1358 insertions(+), 1039 deletions(-) create mode 100644 bin/silius/src/bundler.rs delete mode 100644 bin/silius/src/cli.rs create mode 100644 bin/silius/src/cli/args.rs create mode 100644 bin/silius/src/cli/commands.rs create mode 100644 bin/silius/src/cli/mod.rs delete mode 100644 bin/silius/src/create-wallet.rs create mode 100644 bin/silius/src/main.rs delete mode 100644 bin/silius/src/silius-rpc.rs delete mode 100644 bin/silius/src/silius-uopool.rs delete mode 100644 bin/silius/src/silius.rs diff --git a/.dockerignore b/.dockerignore index b067256e..b14041a2 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,7 @@ +.github docs +Dockerfile +bundler-spec-tests target -thirdparty/account-abstraction/* -thirdparty/bundler/* -.github -Dockerfile \ No newline at end of file +crates/contrarcts/thirdparty/account-abstraction/* +tests/thirdparty/bundler/* diff --git a/.gitignore b/.gitignore index 80296bd2..2cf2f13b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ .local .vscode **/.env -.env \ No newline at end of file +.env +db \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 6616e04a..04e62144 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5297,7 +5297,7 @@ dependencies = [ [[package]] name = "silius" -version = "0.1.0" +version = "0.2.0-alpha" dependencies = [ "anyhow", "clap", @@ -5317,7 +5317,7 @@ dependencies = [ [[package]] name = "silius-bundler" -version = "0.1.0" +version = "0.2.0-alpha" dependencies = [ "alloy-primitives 0.2.0", "alloy-sol-types 0.2.0", @@ -5341,7 +5341,7 @@ dependencies = [ [[package]] name = "silius-contracts" -version = "0.1.0" +version = "0.2.0-alpha" dependencies = [ "anyhow", "ethers 2.0.8", @@ -5356,7 +5356,7 @@ dependencies = [ [[package]] name = "silius-examples" -version = "0.1.0" +version = "0.2.0-alpha" dependencies = [ "alloy-primitives 0.3.3", "alloy-sol-types 0.3.2", @@ -5373,7 +5373,7 @@ dependencies = [ [[package]] name = "silius-grpc" -version = "0.1.0" +version = "0.2.0-alpha" dependencies = [ "anyhow", "arrayref", @@ -5398,7 +5398,7 @@ dependencies = [ [[package]] name = "silius-primitives" -version = "0.1.0" +version = "0.2.0-alpha" dependencies = [ "anyhow", "educe", @@ -5418,7 +5418,7 @@ dependencies = [ [[package]] name = "silius-rpc" -version = "0.1.0" +version = "0.2.0-alpha" dependencies = [ "anyhow", "async-trait", @@ -5438,7 +5438,7 @@ dependencies = [ [[package]] name = "silius-tests" -version = "0.1.0" +version = "0.2.0-alpha" dependencies = [ "anyhow", "ethers 2.0.8", @@ -5451,7 +5451,7 @@ dependencies = [ [[package]] name = "silius-uopool" -version = "0.1.0" +version = "0.2.0-alpha" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 18b88b6e..436f9761 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ default-members = ["bin/silius"] [workspace.package] authors = ["Vid Kersic "] +version = "0.2.0-alpha" edition = "2021" license = "MIT OR Apache-2.0" repository = "https://github.com/Vid201/silius" diff --git a/Makefile b/Makefile index c366aed8..bcc2de26 100644 --- a/Makefile +++ b/Makefile @@ -2,22 +2,25 @@ build: cargo build --release run-silius: - cargo run --release -- --eth-client-address http://127.0.0.1:8545 --mnemonic-file ${HOME}/.silius/0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --beneficiary 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --entry-points 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789 --http --ws + cargo run --release -- bundler --eth-client-address http://127.0.0.1:8545 --mnemonic-file ${HOME}/.silius/0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --beneficiary 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --entry-points 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789 --http --ws + +run-silius-bundling: + cargo run --release -- bundling --eth-client-address http://127.0.0.1:8545 --mnemonic-file ${HOME}/.silius/0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --beneficiary 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --entry-points 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789 run-silius-uopool: - cargo run --release --bin silius-uopool -- --eth-client-address http://127.0.0.1:8545 --entry-points 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789 + cargo run --release -- uopool --eth-client-address http://127.0.0.1:8545 --entry-points 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789 run-silius-rpc: - cargo run --release --bin silius-rpc --http --ws + cargo run --release -- rpc --http --ws -run-create-wallet: - cargo run --release --bin create-wallet -- --output-path ${HOME}/.silius +run-silius-create-wallet: + cargo run --release -- create-wallet --output-path ${HOME}/.silius run-silius-debug: - cargo run --release -- --eth-client-address http://127.0.0.1:8545 --mnemonic-file ${HOME}/.silius/0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --beneficiary 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --entry-points 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789 --http --ws --rpc-api eth,debug,web3 + cargo run --release -- bundler --eth-client-address http://127.0.0.1:8545 --mnemonic-file ${HOME}/.silius/0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --beneficiary 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --entry-points 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789 --http --ws --http.api eth,debug,web3 --ws.api eth,debug,web3 run-silius-debug-mode: - RUST_LOG=silius=TRACE cargo run --profile debug-fast -- --eth-client-address http://127.0.0.1:8545 --mnemonic-file /home/vid/.silius/0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --beneficiary 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --entry-points 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789 --http --ws --rpc-api eth,debug,web3 + cargo run --profile debug-fast -- bundler --verbosity 3 --eth-client-address http://127.0.0.1:8545 --mnemonic-file /home/vid/.silius/0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --beneficiary 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --entry-points 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789 --http --ws --http.api eth,debug,web3 --ws.api eth,debug,web3 fetch-thirdparty: git submodule update --init diff --git a/README.md b/README.md index b152773f..e8f1a010 100644 --- a/README.md +++ b/README.md @@ -39,31 +39,37 @@ make setup-thirdparty Create wallet for bundler: ```bash -cargo run --release --bin create-wallet -- --output-path ${HOME}/.silius --chain-id 5 +cargo run --release -- create-wallet --output-path ${HOME}/.silius --chain-id 5 ``` Run bundler (with user operation pool and JSON-RPC API): ```bash -cargo run --release -- --eth-client-address http://127.0.0.1:8545 --mnemonic-file ${HOME}/.silius/0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --beneficiary 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --entry-points 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789 --http --ws +cargo run --release -- bundler --eth-client-address http://127.0.0.1:8545 --mnemonic-file ${HOME}/.silius/0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --beneficiary 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --entry-points 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789 --http --ws +``` + +Run only bundling component: + +```bash +cargo run --release -- bundling --eth-client-address http://127.0.0.1:8545 --mnemonic-file ${HOME}/.silius/0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --beneficiary 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --entry-points 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789 ``` Run only user operation pool: ```bash -cargo run --release --bin silius-uopool -- --eth-client-address http://127.0.0.1:8545 --entry-points 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789 +cargo run --release -- uopool --eth-client-address http://127.0.0.1:8545 --entry-points 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789 ``` Run only JSON-RPC API: ```bash -cargo run --release --bin silius-rpc --http --ws +cargo run --release -- rpc --http --ws ``` ### Docker ```bash -docker run --net=host -v ./bundler-spec-tests/keys:/data/silius -v ./db:/data/silius/db ghcr.io/vid201/silius:latest --rpc-listen-address 0.0.0.0:3000 --eth-client-address http://127.0.0.1:8545 --datadir data/silius --mnemonic-file data/silius/0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --beneficiary 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --entry-points 0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789 --http --ws --rpc-api eth,debug,web3 +docker run --net=host -v ./bundler-spec-tests/keys/0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266:/data/silius/0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -v ./db:/data/silius/db ghcr.io/vid201/silius:latest bundler --eth-client-address http://127.0.0.1:8545 --datadir data/silius --mnemonic-file data/silius/0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --beneficiary 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --entry-points 0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789 --http --http.addr 0.0.0.0 --http.port 3000 --http.api eth,debug,web3 --ws --ws.addr 0.0.0.0 --ws.port 3001 --ws.api eth,debug,web3 ``` ## Supported networks @@ -117,6 +123,7 @@ The best place for the discussion is the dedicated [Telegram group](https://t.me ## Projects using Silius - [Luban the Paymaster](https://github.com/da-bao-jian/luban-the-paymaster): A Cross-chain Tx Sponsorship Protocol. +- [Ethers UserOp](https://github.com/qi-protocol/ethers-userop/): An ether-rs middleware to craft UserOperations. ## Licenses diff --git a/bin/silius/Cargo.toml b/bin/silius/Cargo.toml index a0139f75..23406765 100644 --- a/bin/silius/Cargo.toml +++ b/bin/silius/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "silius" -version = "0.1.0" +version = { workspace = true } authors = { workspace = true } edition = { workspace = true } license = { workspace = true } @@ -9,10 +9,8 @@ rust-version = { workspace = true } description = """ AA (ERC-4337) bundler - Silius """ -default-run = "silius" [dependencies] - anyhow = { workspace = true } clap = { version = "4", features = ["derive"] } dirs = "4.0" @@ -27,22 +25,3 @@ silius-rpc = { path = "../../crates/rpc" } tokio = { workspace = true } tracing = { workspace = true } tracing-subscriber = { version = "0.3", features = ["env-filter"] } - -[lib] -path = "src/lib.rs" - -[[bin]] -path = "src/silius.rs" -name = "silius" - -[[bin]] -path = "src/silius-uopool.rs" -name = "silius-uopool" - -[[bin]] -path = "src/silius-rpc.rs" -name = "silius-rpc" - -[[bin]] -path = "src/create-wallet.rs" -name = "create-wallet" diff --git a/bin/silius/src/bundler.rs b/bin/silius/src/bundler.rs new file mode 100644 index 00000000..47e4b770 --- /dev/null +++ b/bin/silius/src/bundler.rs @@ -0,0 +1,309 @@ +use crate::{ + cli::args::{BundlerAndUoPoolArgs, BundlerArgs, CreateWalletArgs, RpcArgs, UoPoolArgs}, + utils::unwrap_path_or_home, +}; +use ethers::{ + providers::{Http, Middleware, Provider}, + types::Address, +}; +use silius_grpc::{ + bundler_client::BundlerClient, bundler_service_run, uo_pool_client::UoPoolClient, + uopool_service_run, +}; +use silius_primitives::{ + bundler::SendBundleMode, consts::flashbots_relay_endpoints, Chain, Wallet, +}; +use silius_rpc::{ + debug_api::{DebugApiServer, DebugApiServerImpl}, + eth_api::{EthApiServer, EthApiServerImpl}, + web3_api::{Web3ApiServer, Web3ApiServerImpl}, + JsonRpcServer, JsonRpcServerType, +}; +use std::{collections::HashSet, future::pending, net::SocketAddr, sync::Arc}; +use tracing::info; + +pub async fn launch_bundler( + bundler_args: BundlerArgs, + uopool_args: UoPoolArgs, + common_args: BundlerAndUoPoolArgs, + rpc_args: RpcArgs, +) -> anyhow::Result<()> { + launch_uopool( + uopool_args.clone(), + common_args.eth_client_address.clone(), + common_args.chain.clone(), + common_args.entry_points.clone(), + ) + .await?; + + launch_bundling( + bundler_args.clone(), + common_args.eth_client_address.clone(), + common_args.chain, + common_args.entry_points, + format!( + "http://{:?}:{:?}", + uopool_args.uopool_addr, uopool_args.uopool_port + ), + ) + .await?; + + launch_rpc( + rpc_args, + common_args.eth_client_address, + format!( + "http://{:?}:{:?}", + uopool_args.uopool_addr, uopool_args.uopool_port + ), + format!( + "http://{:?}:{:?}", + bundler_args.bundler_addr, bundler_args.bundler_port + ), + ) + .await?; + + Ok(()) +} + +pub async fn launch_bundling( + args: BundlerArgs, + eth_client_address: String, + chain: Option, + entry_points: Vec
, + uopool_grpc_listen_address: String, +) -> anyhow::Result<()> { + info!("Starting bundling gRPC service..."); + + let eth_client = Arc::new(Provider::::try_from(eth_client_address.clone())?); + let eth_client_version = check_connected_chain(eth_client.clone(), chain).await?; + info!( + "Bundling component connected to Ethereum execution client at {}: {}", + eth_client_address, eth_client_version + ); + + let chain_id = eth_client.get_chainid().await?; + let chain_conn = Chain::from(chain_id); + + let wallet: Wallet; + if args.send_bundle_mode == SendBundleMode::Flashbots { + wallet = Wallet::from_file(args.mnemonic_file.into(), &chain_id, true) + .map_err(|error| anyhow::format_err!("Could not load mnemonic file: {}", error))?; + info!("Wallet Signer {:?}", wallet.signer); + info!("Flashbots Signer {:?}", wallet.fb_signer); + } else { + wallet = Wallet::from_file(args.mnemonic_file.into(), &chain_id, false) + .map_err(|error| anyhow::format_err!("Could not load mnemonic file: {}", error))?; + info!("{:?}", wallet.signer); + } + + info!("Connecting to uopool gRPC service..."); + let uopool_grpc_client = UoPoolClient::connect(uopool_grpc_listen_address).await?; + info!("Connected to uopool gRPC service"); + + bundler_service_run( + SocketAddr::new(args.bundler_addr, args.bundler_port), + wallet, + entry_points, + eth_client_address, + chain_conn, + args.beneficiary, + args.min_balance, + args.bundle_interval, + uopool_grpc_client, + args.send_bundle_mode, + match args.send_bundle_mode { + SendBundleMode::EthClient => None, + SendBundleMode::Flashbots => { + Some(vec![flashbots_relay_endpoints::FLASHBOTS.to_string()]) + } + }, + ); + info!( + "Started bundler gRPC service at {:?}:{:?}", + args.bundler_addr, args.bundler_port + ); + + Ok(()) +} + +pub async fn launch_uopool( + args: UoPoolArgs, + eth_client_address: String, + chain: Option, + entry_points: Vec
, +) -> anyhow::Result<()> { + info!("Starting uopool gRPC service..."); + + let eth_client = Arc::new(Provider::::try_from(eth_client_address.clone())?); + let eth_client_version = check_connected_chain(eth_client.clone(), chain).await?; + info!( + "UoPool connected to Ethereum execution client at {}: {}", + eth_client_address, eth_client_version + ); + + let chain_id = Chain::from(eth_client.get_chainid().await?); + + let datadir = unwrap_path_or_home(args.datadir)?; + + uopool_service_run( + SocketAddr::new(args.uopool_addr, args.uopool_port), + datadir, + entry_points, + eth_client.clone(), + chain_id, + args.max_verification_gas, + args.min_stake, + args.min_unstake_delay, + args.min_priority_fee_per_gas, + args.whitelist, + args.uopool_mode, + ) + .await?; + + info!( + "Started uopool gRPC service at {:?}:{:?}", + args.uopool_addr, args.uopool_port + ); + + Ok(()) +} + +pub async fn launch_rpc( + args: RpcArgs, + eth_client_address: String, + uopool_grpc_listen_address: String, + bundler_grpc_listen_address: String, +) -> anyhow::Result<()> { + if !args.is_enabled() { + return Err(anyhow::anyhow!("No RPC protocol is enabled")); + } + + info!("Starting bundler JSON-RPC server..."); + + let mut server = JsonRpcServer::new( + args.http, + args.http_addr, + args.http_port, + args.ws, + args.ws_addr, + args.ws_port, + ) + .with_cors(&args.http_corsdomain, JsonRpcServerType::Http) + .with_cors(&args.ws_origins, JsonRpcServerType::Ws) + .with_proxy(eth_client_address); + + let http_api: HashSet = HashSet::from_iter(args.http_api.iter().cloned()); + let ws_api: HashSet = HashSet::from_iter(args.ws_api.iter().cloned()); + + if http_api.contains("web3") { + server.add_methods(Web3ApiServerImpl {}.into_rpc(), JsonRpcServerType::Http)?; + } + if ws_api.contains("web3") { + server.add_methods(Web3ApiServerImpl {}.into_rpc(), JsonRpcServerType::Ws)?; + } + + info!("Connecting to uopool gRPC service..."); + let uopool_grpc_client = UoPoolClient::connect(uopool_grpc_listen_address).await?; + info!("Connected to uopool gRPC service..."); + + if args.is_api_method_enabled("eth") { + if http_api.contains("eth") { + server.add_methods( + EthApiServerImpl { + uopool_grpc_client: uopool_grpc_client.clone(), + } + .into_rpc(), + JsonRpcServerType::Http, + )?; + } + if ws_api.contains("eth") { + server.add_methods( + EthApiServerImpl { + uopool_grpc_client: uopool_grpc_client.clone(), + } + .into_rpc(), + JsonRpcServerType::Ws, + )?; + } + } + + if args.is_api_method_enabled("debug") { + info!("Connecting to bundling gRPC service..."); + let bundler_grpc_client = BundlerClient::connect(bundler_grpc_listen_address).await?; + info!("Connected to bundling gRPC service..."); + + if http_api.contains("debug") { + server.add_methods( + DebugApiServerImpl { + uopool_grpc_client: uopool_grpc_client.clone(), + bundler_grpc_client: bundler_grpc_client.clone(), + } + .into_rpc(), + JsonRpcServerType::Http, + )?; + } + + if ws_api.contains("debug") { + server.add_methods( + DebugApiServerImpl { + uopool_grpc_client, + bundler_grpc_client, + } + .into_rpc(), + JsonRpcServerType::Ws, + )?; + } + } + + tokio::spawn(async move { + let (_http_handle, _ws_handle) = server.start().await?; + + info!( + "Started bundler JSON-RPC server with http: {:?}:{:?}, ws: {:?}:{:?}", + args.http_addr, args.http_port, args.ws_addr, args.ws_port, + ); + pending::>().await + }); + + Ok(()) +} + +pub fn create_wallet(args: CreateWalletArgs) -> anyhow::Result<()> { + info!( + "Creating bundler wallet... Storing to: {:?}", + args.output_path + ); + + let path = unwrap_path_or_home(args.output_path)?; + + if args.flashbots_key { + let wallet = Wallet::build_random(path, &args.chain_id, true)?; + info!("Wallet signer {:?}", wallet.signer); + info!("Flashbots signer {:?}", wallet.fb_signer); + } else { + let wallet = Wallet::build_random(path, &args.chain_id, false)?; + info!("Wallet signer {:?}", wallet.signer); + } + + Ok(()) +} + +async fn check_connected_chain( + eth_client: Arc>, + chain: Option, +) -> anyhow::Result { + let chain_id = eth_client.get_chainid().await?; + let chain_conn = Chain::from(chain_id); + + if let Some(chain_opt) = chain { + if chain_conn.name() != chain_opt { + return Err(anyhow::format_err!( + "Tried to connect to the execution client of different chain: {} != {}", + chain_opt, + chain_conn.name() + )); + } + } + + Ok(eth_client.client_version().await?) +} diff --git a/bin/silius/src/cli.rs b/bin/silius/src/cli.rs deleted file mode 100644 index c3d964d3..00000000 --- a/bin/silius/src/cli.rs +++ /dev/null @@ -1,338 +0,0 @@ -use crate::utils::{parse_address, parse_u256, parse_uopool_mode}; -use clap::Parser; -use ethers::types::{Address, U256}; -use expanded_pathbuf::ExpandedPathBuf; -use silius_primitives::UoPoolMode; -use std::net::SocketAddr; - -#[derive(Clone, Debug, Parser)] -pub struct UoPoolServiceOpts { - #[clap(long, default_value = "127.0.0.1:3001")] - pub uopool_grpc_listen_address: SocketAddr, - - #[clap(long)] - pub datadir: Option, - - #[clap(long, value_parser=parse_u256, default_value = "1")] - pub min_stake: U256, - - #[clap(long, value_parser=parse_u256, default_value = "0")] - pub min_unstake_delay: U256, - - #[clap(long, value_parser=parse_u256, default_value = "0")] - pub min_priority_fee_per_gas: U256, - - #[clap(long, value_delimiter=',', value_parser = parse_address)] - pub whitelist: Vec
, - - #[clap(long, default_value = "standard", value_parser=parse_uopool_mode)] - pub uo_pool_mode: UoPoolMode, -} - -#[derive(Clone, Debug, Parser, PartialEq)] -pub struct BundlerServiceOpts { - /// The bundler beneficiary address. - #[clap(long, value_parser=parse_address)] - pub beneficiary: Address, - - /// The minimum balance required for the beneficiary address. - /// - /// By default, this option is set to `100000000000000000`. - #[clap(long, default_value = "100000000000000000", value_parser=parse_u256)] - pub min_balance: U256, - - /// The gRPC listen address for the bundler service. - /// - /// By default, this option is set to `127.0.0.1:3002` - #[clap(long, default_value = "127.0.0.1:3002")] - pub bundler_grpc_listen_address: SocketAddr, - - /// The bundle interval in seconds. - /// - /// By default the interval time is set to 10 - #[clap(long, default_value = "10")] - pub bundle_interval: u64, - - /// Enables or disables the creation of Flashbots bundler signer. - /// - /// By default, this option is set to false. - #[clap(long)] - pub build_fb_signer: Option, - - /// Sets the send bundle mode. - /// - /// By default, this option is set to `eth-client`. - #[clap(long)] - pub send_bundle_mode: Option, -} - -#[derive(Clone, Debug, Parser, PartialEq)] -pub struct RpcServiceOpts { - /// Sets the RPC listen address. - /// - /// By default, this option is set to `127.0.0.1:3000 - #[clap(long, default_value = "127.0.0.1:3000")] - pub rpc_listen_address: String, - - #[clap(long, value_delimiter=',', default_value = "eth", value_parser = ["eth", "debug", "web3"])] - pub rpc_api: Vec, - - /// Enables or disables the HTTP RPC. - /// - /// By default, this option is set to false. - /// - To enable: `--http`. - /// - To disable: no `--http` flag. - #[clap(long)] - pub http: bool, - - /// Enables or disables the WebSocket RPC. - /// - /// By default, this option is set to false. - /// - To enable: `--ws` - /// - To disable: no `--ws` flag. - #[clap(long)] - pub ws: bool, - - /// Configures the CORS filter. - /// - /// By default, this option is set to `*`. - #[clap(long, value_delimiter = ',', default_value = "*")] - pub cors_domain: Vec, -} - -impl RpcServiceOpts { - /// Checks if either HTTP or WebSocket RPC is enabled. - /// - /// # Returns - /// * `bool` - Returns `true` if either HTTP or WebSocket RPC is enabled, otherwise `false`. - pub fn is_enabled(&self) -> bool { - self.http || self.ws - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::{ - net::{IpAddr, Ipv4Addr}, - str::FromStr, - }; - - #[test] - fn bundler_opts() { - let args = vec![ - "bundleropts", - "--beneficiary", - "0x690B9A9E9aa1C9dB991C7721a92d351Db4FaC990", - "--min-balance", - "100000000000000000", - "--bundler-grpc-listen-address", - "127.0.0.1:3002", - "--bundle-interval", - "10", - ]; - assert_eq!( - BundlerServiceOpts { - beneficiary: Address::from_str("0x690B9A9E9aa1C9dB991C7721a92d351Db4FaC990") - .unwrap(), - min_balance: U256::from(100000000000000000_u64), - bundler_grpc_listen_address: SocketAddr::new( - IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), - 3002 - ), - bundle_interval: 10, - build_fb_signer: None, - send_bundle_mode: None, - }, - BundlerServiceOpts::try_parse_from(args).unwrap() - ); - } - - #[test] - fn rpc_service_opts_when_http_and_ws_flag() { - let args = vec![ - "rpcserviceopts", - "--rpc-listen-address", - "127.0.0.1:1234", - "--rpc-api", - "eth,debug,web3", - "--http", - "--ws", - "--cors-domain", - "127.0.0.1:4321", - ]; - assert_eq!( - RpcServiceOpts { - rpc_listen_address: String::from("127.0.0.1:1234"), - rpc_api: vec![ - String::from("eth"), - String::from("debug"), - String::from("web3") - ], - http: true, - ws: true, - cors_domain: vec![String::from("127.0.0.1:4321")], - }, - RpcServiceOpts::try_parse_from(args).unwrap() - ); - } - - #[test] - fn rpc_service_opts_when_http_is_true_ws_is_false() { - let args = vec![ - "rpcserviceopts", - "--rpc-listen-address", - "127.0.0.1:1234", - "--rpc-api", - "eth,debug,web3", - "--http", - "--cors-domain", - "127.0.0.1:4321", - ]; - assert_eq!( - RpcServiceOpts { - rpc_listen_address: String::from("127.0.0.1:1234"), - rpc_api: vec![ - String::from("eth"), - String::from("debug"), - String::from("web3") - ], - http: true, - ws: false, - cors_domain: vec![String::from("127.0.0.1:4321")], - }, - RpcServiceOpts::try_parse_from(args).unwrap() - ); - } - - #[test] - fn rpc_service_opts_when_http_is_false_ws_is_true() { - let args = vec![ - "rpcserviceopts", - "--rpc-listen-address", - "127.0.0.1:1234", - "--rpc-api", - "eth,debug,web3", - "--ws", - "--cors-domain", - "127.0.0.1:4321", - ]; - assert_eq!( - RpcServiceOpts { - rpc_listen_address: String::from("127.0.0.1:1234"), - rpc_api: vec![ - String::from("eth"), - String::from("debug"), - String::from("web3") - ], - http: false, - ws: true, - cors_domain: vec![String::from("127.0.0.1:4321")], - }, - RpcServiceOpts::try_parse_from(args).unwrap() - ); - } - - #[test] - fn rpc_service_opts_when_no_http_and_ws_flag() { - let args = vec![ - "rpcserviceopts", - "--rpc-listen-address", - "127.0.0.1:1234", - "--rpc-api", - "eth,debug,web3", - "--cors-domain", - "127.0.0.1:4321", - ]; - assert_eq!( - RpcServiceOpts { - rpc_listen_address: String::from("127.0.0.1:1234"), - rpc_api: vec![ - String::from("eth"), - String::from("debug"), - String::from("web3") - ], - http: false, - ws: false, - cors_domain: vec![String::from("127.0.0.1:4321")], - }, - RpcServiceOpts::try_parse_from(args).unwrap() - ); - } - - #[test] - fn is_enabled_return_true_when_only_http() { - assert_eq!( - RpcServiceOpts { - rpc_listen_address: String::from("127.0.0.1:1234"), - rpc_api: vec![ - String::from("eth"), - String::from("debug"), - String::from("web3") - ], - http: true, - ws: false, - cors_domain: vec![String::from("127.0.0.1:4321")], - } - .is_enabled(), - true - ); - } - - #[test] - fn is_enabled_return_true_when_only_ws() { - assert_eq!( - RpcServiceOpts { - rpc_listen_address: String::from("127.0.0.1:1234"), - rpc_api: vec![ - String::from("eth"), - String::from("debug"), - String::from("web3") - ], - http: false, - ws: true, - cors_domain: vec![String::from("127.0.0.1:4321")], - } - .is_enabled(), - true - ); - } - - #[test] - fn is_enabled_return_true_when_http_and_ws_are_true() { - assert_eq!( - RpcServiceOpts { - rpc_listen_address: String::from("127.0.0.1:1234"), - rpc_api: vec![ - String::from("eth"), - String::from("debug"), - String::from("web3") - ], - http: true, - ws: true, - cors_domain: vec![String::from("127.0.0.1:4321")], - } - .is_enabled(), - true - ); - } - - #[test] - fn is_enabled_return_false_when_http_and_ws_are_false() { - assert_eq!( - RpcServiceOpts { - rpc_listen_address: String::from("127.0.0.1:1234"), - rpc_api: vec![ - String::from("eth"), - String::from("debug"), - String::from("web3") - ], - http: false, - ws: false, - cors_domain: vec![String::from("127.0.0.1:4321")], - } - .is_enabled(), - false - ); - } -} diff --git a/bin/silius/src/cli/args.rs b/bin/silius/src/cli/args.rs new file mode 100644 index 00000000..a9a7f18e --- /dev/null +++ b/bin/silius/src/cli/args.rs @@ -0,0 +1,507 @@ +use crate::utils::{parse_address, parse_send_bundle_mode, parse_u256, parse_uopool_mode}; +use clap::Parser; +use ethers::types::{Address, U256}; +use expanded_pathbuf::ExpandedPathBuf; +use silius_primitives::{ + bundler::SendBundleMode, + chain::SUPPORTED_CHAINS, + consts::networking::{ + DEFAULT_BUNDLER_GRPC_PORT, DEFAULT_HTTP_RPC_PORT, DEFAULT_UOPOOL_GRPC_PORT, + DEFAULT_WS_RPC_PORT, + }, + UoPoolMode, +}; +use std::{ + net::{IpAddr, Ipv4Addr}, + path::PathBuf, +}; + +/// Bundler CLI args +#[derive(Debug, Clone, Parser, PartialEq)] +pub struct BundlerArgs { + /// Bundler gRPC address to listen on. + #[clap(long = "bundler.addr", default_value_t = IpAddr::V4(Ipv4Addr::LOCALHOST))] + pub bundler_addr: IpAddr, + + /// Bundler gRPC port to listen on. + #[clap(long = "bundler.port", default_value_t = DEFAULT_BUNDLER_GRPC_PORT)] + pub bundler_port: u16, + + /// Path to the mnemonic file. + #[clap(long)] + pub mnemonic_file: PathBuf, + + /// The bundler beneficiary address. + #[clap(long, value_parser=parse_address)] + pub beneficiary: Address, + + /// The minimum balance required for the beneficiary address. + /// + /// By default, this option is set to `100000000000000000`. + #[clap(long, default_value = "100000000000000000", value_parser=parse_u256)] + pub min_balance: U256, + + /// The bundle interval in seconds. + /// + /// By default the interval time is set to 10 + #[clap(long, default_value_t = 10)] + pub bundle_interval: u64, + + /// Sets the send bundle mode. + /// + /// By default, this option is set to `eth-client`. + #[clap(long, default_value = "eth-client", value_parser=parse_send_bundle_mode)] + pub send_bundle_mode: SendBundleMode, +} + +/// UoPool CLI args +#[derive(Debug, Clone, Parser)] +pub struct UoPoolArgs { + /// UoPool gRPC address to listen on. + #[clap(long = "uopool.addr", default_value_t = IpAddr::V4(Ipv4Addr::LOCALHOST))] + pub uopool_addr: IpAddr, + + /// UoPool gRPC port to listen on. + #[clap(long = "uopool.port", default_value_t = DEFAULT_UOPOOL_GRPC_PORT)] + pub uopool_port: u16, + + /// Data directory (primarily for database). + #[clap(long)] + pub datadir: Option, + + /// Max allowed verification gas. + #[clap(long, default_value="3000000", value_parser=parse_u256)] + pub max_verification_gas: U256, + + /// Minimum stake required for entities. + #[clap(long, value_parser=parse_u256, default_value = "1")] + pub min_stake: U256, + + /// Minimum unstake delay for entities. + #[clap(long, value_parser=parse_u256, default_value = "0")] + pub min_unstake_delay: U256, + + /// Minimum priority fee per gas. + #[clap(long, value_parser=parse_u256, default_value = "0")] + pub min_priority_fee_per_gas: U256, + + /// Addresses of whitelisted entities. + #[clap(long, value_delimiter=',', value_parser = parse_address)] + pub whitelist: Vec
, + + /// User operation mempool mode + #[clap(long, default_value = "standard", value_parser=parse_uopool_mode)] + pub uopool_mode: UoPoolMode, +} + +/// Common CLI args for bundler and uopool +#[derive(Debug, Clone, Parser)] +pub struct BundlerAndUoPoolArgs { + /// Ethereum execution client RPC endpoint. + #[clap(long, default_value = "http://127.0.0.1:8545")] + pub eth_client_address: String, + + /// Chain information. + #[clap(long, default_value= "dev", value_parser = SUPPORTED_CHAINS)] + pub chain: Option, + + /// Entry point addresses. + #[clap(long, value_delimiter=',', value_parser=parse_address)] + pub entry_points: Vec
, +} + +/// RPC CLI args +#[derive(Debug, Clone, Parser, PartialEq)] +pub struct RpcArgs { + /// Enables or disables the HTTP RPC. + /// + /// By default, this option is set to false. + /// - To enable: `--http`. + /// - To disable: no `--http` flag. + #[clap(long)] + pub http: bool, + + /// Sets the HTTP RPC address to listen on. + /// + /// By default, this option is set to `127.0.0.1` + #[clap(long = "http.addr", default_value_t = IpAddr::V4(Ipv4Addr::LOCALHOST))] + pub http_addr: IpAddr, + + /// Sets the HTTP RPC port to listen on. + /// + /// By default, this option is set to `3000` + #[clap(long = "http.port", default_value_t = DEFAULT_HTTP_RPC_PORT)] + pub http_port: u16, + + /// Configures the HTTP RPC API modules. + #[clap(long = "http.api", value_delimiter=',', default_value = "eth", value_parser = ["eth", "debug", "web3"])] + pub http_api: Vec, + + /// Configures the allowed CORS domains. + /// + /// By default, this option is set to `*`. + #[clap(long = "http.corsdomain", value_delimiter = ',', default_value = "*")] + pub http_corsdomain: Vec, + + /// Enables or disables the WebSocket RPC. + /// + /// By default, this option is set to false. + /// - To enable: `--ws` + /// - To disable: no `--ws` flag. + #[clap(long)] + pub ws: bool, + + /// Sets the WS RPC address to listen on. + /// + /// By default, this option is set to `127.0.0.1` + #[clap(long = "ws.addr", default_value_t = IpAddr::V4(Ipv4Addr::LOCALHOST))] + pub ws_addr: IpAddr, + + /// Sets the WS RPC port to listen on. + /// + /// By default, this option is set to `3001` + #[clap(long = "ws.port", default_value_t = DEFAULT_WS_RPC_PORT)] + pub ws_port: u16, + + /// Configures the WS RPC API modules. + #[clap(long = "ws.api", value_delimiter=',', default_value = "eth", value_parser = ["eth", "debug", "web3"])] + pub ws_api: Vec, + + /// Configures the allowed WS origins. + /// + /// By default, this option is set to `*`. + #[clap(long = "ws.origins", value_delimiter = ',', default_value = "*")] + pub ws_origins: Vec, +} + +impl RpcArgs { + /// Checks if either HTTP or WebSocket RPC is enabled. + /// + /// # Returns + /// * `bool` - Returns `true` if either HTTP or WebSocket RPC is enabled, otherwise `false`. + pub fn is_enabled(&self) -> bool { + self.http || self.ws + } + + /// Checks if the given API method is enabled. + /// + /// # Arguments + /// * `method: &str` - The API method to check. + /// + /// # Returns + /// * `bool` - Returns `true` if the given API method is enabled, otherwise `false`. + pub fn is_api_method_enabled(&self, method: &str) -> bool { + self.http_api.contains(&method.to_string()) || self.ws_api.contains(&method.to_string()) + } +} + +/// Create wallet CLI args +#[derive(Debug, Clone, Parser)] +pub struct CreateWalletArgs { + /// The path where the wallet will be stored. + #[clap(long, short)] + pub output_path: Option, + + /// The chain id. + #[clap(long, value_parser=parse_u256, default_value="1")] + pub chain_id: U256, + + /// Whether to create a Flashbots key. + #[clap(long, default_value_t = false)] + pub flashbots_key: bool, +} + +#[cfg(test)] +mod tests { + use super::*; + use std::{ + net::{IpAddr, Ipv4Addr}, + str::FromStr, + }; + + #[test] + fn bundler_args() { + let args = vec![ + "bundlerargs", + "--mnemonic-file", + "~/.silius/0x690B9A9E9aa1C9dB991C7721a92d351Db4FaC990", + "--beneficiary", + "0x690B9A9E9aa1C9dB991C7721a92d351Db4FaC990", + "--min-balance", + "100000000000000000", + "--bundler.addr", + "127.0.0.1", + "--bundler.port", + "3002", + "--bundle-interval", + "10", + ]; + assert_eq!( + BundlerArgs { + mnemonic_file: PathBuf::from( + "~/.silius/0x690B9A9E9aa1C9dB991C7721a92d351Db4FaC990" + ), + beneficiary: Address::from_str("0x690B9A9E9aa1C9dB991C7721a92d351Db4FaC990") + .unwrap(), + min_balance: U256::from(100000000000000000_u64), + bundle_interval: 10, + send_bundle_mode: SendBundleMode::EthClient, + bundler_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + bundler_port: 3002, + }, + BundlerArgs::try_parse_from(args).unwrap() + ); + } + + #[test] + fn rpc_args_when_http_and_ws_flag() { + let args = vec![ + "rpcargs", + "--http", + "--http.addr", + "127.0.0.1", + "--http.port", + "3000", + "--http.api", + "eth,debug,web3", + "--http.corsdomain", + "127.0.0.1:4321", + "--ws", + "--ws.addr", + "127.0.0.1", + "--ws.port", + "3001", + "--ws.api", + "eth,debug,web3", + "--ws.origins", + "127.0.0.1:4321", + ]; + assert_eq!( + RpcArgs { + http: true, + http_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + http_port: 3000, + http_api: vec![ + String::from("eth"), + String::from("debug"), + String::from("web3") + ], + http_corsdomain: vec![String::from("127.0.0.1:4321")], + ws: true, + ws_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + ws_port: 3001, + ws_api: vec![ + String::from("eth"), + String::from("debug"), + String::from("web3") + ], + ws_origins: vec![String::from("127.0.0.1:4321")], + }, + RpcArgs::try_parse_from(args).unwrap() + ); + } + + #[test] + fn rpc_args_when_http_is_true_ws_is_false() { + let args = vec![ + "rpcargs", + "--http", + "--http.addr", + "127.0.0.1", + "--http.port", + "3000", + "--http.api", + "eth,debug,web3", + "--http.corsdomain", + "127.0.0.1:4321", + ]; + assert_eq!( + RpcArgs { + http: true, + http_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + http_port: 3000, + http_api: vec![ + String::from("eth"), + String::from("debug"), + String::from("web3") + ], + http_corsdomain: vec![String::from("127.0.0.1:4321")], + ws: false, + ws_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + ws_port: 3001, + ws_api: vec![String::from("eth"),], + ws_origins: vec![String::from("*")], + }, + RpcArgs::try_parse_from(args).unwrap() + ); + } + + #[test] + fn rpc_args_when_http_is_false_ws_is_true() { + let args = vec![ + "rpcargs", + "--ws", + "--ws.addr", + "127.0.0.1", + "--ws.port", + "3001", + "--ws.api", + "eth,debug,web3", + "--ws.origins", + "127.0.0.1:4321", + ]; + assert_eq!( + RpcArgs { + http: false, + http_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + http_port: 3000, + http_api: vec![String::from("eth"),], + http_corsdomain: vec![String::from("*")], + ws: true, + ws_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + ws_port: 3001, + ws_api: vec![ + String::from("eth"), + String::from("debug"), + String::from("web3") + ], + ws_origins: vec![String::from("127.0.0.1:4321")], + }, + RpcArgs::try_parse_from(args).unwrap() + ); + } + + #[test] + fn rpc_args_when_no_http_and_ws_flag() { + let args = vec![ + "rpcargs", + "--http.addr", + "127.0.0.1", + "--http.port", + "3000", + "--http.api", + "eth,debug,web3", + "--http.corsdomain", + "127.0.0.1:4321", + ]; + assert_eq!( + RpcArgs { + http: false, + http_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + http_port: 3000, + http_api: vec![ + String::from("eth"), + String::from("debug"), + String::from("web3") + ], + http_corsdomain: vec![String::from("_")], + ws: false, + ws_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + ws_port: 3001, + ws_api: vec![String::from("eth"),], + ws_origins: vec![String::from("*")], + }, + RpcArgs::try_parse_from(args).unwrap() + ); + } + + #[test] + fn is_enabled_return_true_when_only_http() { + assert_eq!( + RpcArgs { + http: true, + http_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + http_port: 3000, + http_api: vec![ + String::from("eth"), + String::from("debug"), + String::from("web3") + ], + http_corsdomain: vec![String::from("127.0.0.1:4321")], + ws: false, + ws_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + ws_port: 3001, + ws_api: vec![String::from("eth"),], + ws_origins: vec![String::from("*")], + } + .is_enabled(), + true + ); + } + + #[test] + fn is_enabled_return_true_when_only_ws() { + assert_eq!( + RpcArgs { + http: false, + http_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + http_port: 3000, + http_api: vec![String::from("eth"),], + http_corsdomain: vec![String::from("*")], + ws: true, + ws_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + ws_port: 3001, + ws_api: vec![ + String::from("eth"), + String::from("debug"), + String::from("web3") + ], + ws_origins: vec![String::from("127.0.0.1:4321")], + } + .is_enabled(), + true + ); + } + + #[test] + fn is_enabled_return_true_when_http_and_ws_are_true() { + assert_eq!( + RpcArgs { + http: true, + http_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + http_port: 3000, + http_api: vec![ + String::from("eth"), + String::from("debug"), + String::from("web3") + ], + http_corsdomain: vec![String::from("127.0.0.1:4321")], + ws: true, + ws_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + ws_port: 3001, + ws_api: vec![ + String::from("eth"), + String::from("debug"), + String::from("web3") + ], + ws_origins: vec![String::from("127.0.0.1:4321")], + } + .is_enabled(), + true + ); + } + + #[test] + fn is_enabled_return_false_when_http_and_ws_are_false() { + assert_eq!( + RpcArgs { + http: false, + http_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + http_port: 3000, + http_api: vec![ + String::from("eth"), + String::from("debug"), + String::from("web3") + ], + http_corsdomain: vec![String::from("_")], + ws: false, + ws_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + ws_port: 3001, + ws_api: vec![String::from("eth"),], + ws_origins: vec![String::from("*")], + } + .is_enabled(), + false + ); + } +} diff --git a/bin/silius/src/cli/commands.rs b/bin/silius/src/cli/commands.rs new file mode 100644 index 00000000..8257b95b --- /dev/null +++ b/bin/silius/src/cli/commands.rs @@ -0,0 +1,138 @@ +use super::args::{BundlerAndUoPoolArgs, BundlerArgs, CreateWalletArgs, RpcArgs, UoPoolArgs}; +use crate::bundler::{create_wallet, launch_bundler, launch_bundling, launch_rpc, launch_uopool}; +use clap::Parser; +use std::future::pending; + +/// Start the bundler with all components (bundling component, user operation mempool, RPC server) +#[derive(Debug, Parser)] +pub struct BundlerCommand { + /// All Bundler specific args + #[clap(flatten)] + bundler: BundlerArgs, + + /// All UoPool specific args + #[clap(flatten)] + uopool: UoPoolArgs, + + /// Common Bundler and UoPool args + #[clap(flatten)] + common: BundlerAndUoPoolArgs, + + /// All RPC args + #[clap(flatten)] + rpc: RpcArgs, +} + +impl BundlerCommand { + /// Execute the command + pub async fn execute(self) -> anyhow::Result<()> { + launch_bundler(self.bundler, self.uopool, self.common, self.rpc).await?; + pending().await + } +} + +/// Start the bundling component +#[derive(Debug, Parser)] +pub struct BundlingCommand { + /// All Bundler specific args + #[clap(flatten)] + bundler: BundlerArgs, + + /// Common Bundler and UoPool args + #[clap(flatten)] + common: BundlerAndUoPoolArgs, + + /// UoPool gRPC listen address + #[clap(long, default_value = "http://127.0.0.1:3002")] + pub uopool_grpc_listen_address: String, +} + +impl BundlingCommand { + /// Execute the command + pub async fn execute(self) -> anyhow::Result<()> { + launch_bundling( + self.bundler, + self.common.eth_client_address, + self.common.chain, + self.common.entry_points, + self.uopool_grpc_listen_address, + ) + .await?; + pending().await + } +} + +/// Start the user operation mempool +#[derive(Debug, Parser)] +pub struct UoPoolCommand { + /// All UoPool specific args + #[clap(flatten)] + uopool: UoPoolArgs, + + /// Common Bundler and UoPool args + #[clap(flatten)] + common: BundlerAndUoPoolArgs, +} + +impl UoPoolCommand { + /// Execute the command + pub async fn execute(self) -> anyhow::Result<()> { + launch_uopool( + self.uopool, + self.common.eth_client_address, + self.common.chain, + self.common.entry_points, + ) + .await?; + pending().await + } +} + +/// Start the RPC server +#[derive(Debug, Parser)] +pub struct RpcCommand { + /// All RPC args + #[clap(flatten)] + rpc: RpcArgs, + + /// Ethereum execution client RPC endpoint + #[clap(long, default_value = "http://127.0.0.1:8545")] + pub eth_client_address: String, + + /// UoPool gRPC listen address + #[clap(long, default_value = "http://127.0.0.1:3002")] + pub uopool_grpc_listen_address: String, + + /// Bundler gRPC listen address + #[clap(long, default_value = "http://127.0.0.1:3003")] + pub bundler_grpc_listen_address: String, +} + +impl RpcCommand { + /// Execute the command + pub async fn execute(self) -> anyhow::Result<()> { + launch_rpc( + self.rpc, + self.eth_client_address, + self.uopool_grpc_listen_address, + self.bundler_grpc_listen_address, + ) + .await?; + pending().await + } +} + +/// Create wallet for bundling component +#[derive(Debug, Parser)] +pub struct CreateWalletCommand { + /// All create wallet args + #[clap(flatten)] + create_wallet: CreateWalletArgs, +} + +impl CreateWalletCommand { + /// Execute the command + pub fn execute(self) -> anyhow::Result<()> { + create_wallet(self.create_wallet) + } +} diff --git a/bin/silius/src/cli/mod.rs b/bin/silius/src/cli/mod.rs new file mode 100644 index 00000000..0a470e4d --- /dev/null +++ b/bin/silius/src/cli/mod.rs @@ -0,0 +1,88 @@ +use crate::utils::run_until_ctrl_c; +use clap::{value_parser, Parser, Subcommand}; +use std::panic; + +pub mod args; +pub mod commands; + +/// The main Silius CLI interface +#[derive(Debug, Parser)] +#[command(author, version, about = "Silius", long_about = None)] +pub struct Cli { + /// The command to execute + #[clap(subcommand)] + command: Commands, + + /// The verbosity level + #[clap(long, short, global = true, default_value_t = 2, value_parser = value_parser!(u8).range(..=4))] + verbosity: u8, +} + +impl Cli { + /// Get the log level based on the verbosity level + pub fn get_log_level(&self) -> String { + match self.verbosity { + 0 => "error", + 1 => "warn", + 2 => "info", + 3 => "debug", + _ => "trace", + } + .to_string() + } +} + +/// Commands to be executed +#[derive(Debug, Subcommand)] +pub enum Commands { + /// Start the bundler with all components (bundling component, user operation mempool, RPC server) + #[command(name = "bundler")] + Bundler(Box), + + /// Start the bundling component + #[command(name = "bundling")] + Bundling(commands::BundlingCommand), + + /// Start the user operation mempool + #[command(name = "uopool")] + UoPool(commands::UoPoolCommand), + + /// Start the RPC server + #[command(name = "rpc")] + Rpc(commands::RpcCommand), + + /// Create wallet for bundling component + #[command(name = "create-wallet")] + CreateWallet(commands::CreateWalletCommand), +} + +pub fn run() -> anyhow::Result<()> { + let cli = Cli::parse(); + + std::env::set_var("RUST_LOG", cli.get_log_level()); + tracing_subscriber::fmt::init(); + + std::thread::Builder::new() + .stack_size(128 * 1024 * 1024) + .spawn(move || { + let rt = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .thread_stack_size(128 * 1024 * 1024) + .build()?; + + let task = async move { + match cli.command { + Commands::Bundler(command) => command.execute().await, + Commands::Bundling(command) => command.execute().await, + Commands::UoPool(command) => command.execute().await, + Commands::Rpc(command) => command.execute().await, + Commands::CreateWallet(command) => command.execute(), + } + }; + + rt.block_on(run_until_ctrl_c(task))?; + Ok(()) + })? + .join() + .unwrap_or_else(|e| panic::resume_unwind(e)) +} diff --git a/bin/silius/src/create-wallet.rs b/bin/silius/src/create-wallet.rs deleted file mode 100644 index d08b8f09..00000000 --- a/bin/silius/src/create-wallet.rs +++ /dev/null @@ -1,42 +0,0 @@ -use anyhow::Result; -use clap::Parser; -use ethers::types::U256; -use expanded_pathbuf::ExpandedPathBuf; -use silius::utils::{parse_u256, unwrap_path_or_home}; -use silius_primitives::Wallet; -use tracing::info; - -#[derive(Parser)] -#[clap( - name = "silius-create-wallet", - about = "Bundler's wallet creation for ERC-4337 Account Abstraction" -)] -pub struct Opt { - #[clap(long)] - pub output_path: Option, - - #[clap(long, value_parser=parse_u256, default_value="1")] - pub chain_id: U256, - - #[clap(long)] - pub build_fb_wallet: Option, -} - -fn main() -> Result<()> { - let opt: Opt = Opt::parse(); - - tracing_subscriber::fmt::init(); - - let path = unwrap_path_or_home(opt.output_path)?; - - if opt.build_fb_wallet == Some(true) { - let wallet = Wallet::build_random(path, &opt.chain_id, true)?; - info!("Wallet Signer {:?}", wallet.signer); - info!("Flashbots Signer {:?}", wallet.fb_signer); - } else { - let wallet = Wallet::build_random(path, &opt.chain_id, false)?; - info!("Wallet Signer {:?}", wallet.signer); - } - - Ok(()) -} diff --git a/bin/silius/src/lib.rs b/bin/silius/src/lib.rs index 83eeea43..c9052b48 100644 --- a/bin/silius/src/lib.rs +++ b/bin/silius/src/lib.rs @@ -1,2 +1,3 @@ +pub mod bundler; pub mod cli; pub mod utils; diff --git a/bin/silius/src/main.rs b/bin/silius/src/main.rs new file mode 100644 index 00000000..96279ec7 --- /dev/null +++ b/bin/silius/src/main.rs @@ -0,0 +1,6 @@ +fn main() { + if let Err(err) = silius::cli::run() { + eprintln!("Error: {err:?}"); + std::process::exit(1); + } +} diff --git a/bin/silius/src/silius-rpc.rs b/bin/silius/src/silius-rpc.rs deleted file mode 100644 index 55d9b6ac..00000000 --- a/bin/silius/src/silius-rpc.rs +++ /dev/null @@ -1,91 +0,0 @@ -use anyhow::Result; -use clap::Parser; -use silius::cli::RpcServiceOpts; -use silius_grpc::{bundler_client::BundlerClient, uo_pool_client::UoPoolClient}; -use silius_rpc::{ - debug_api::{DebugApiServer, DebugApiServerImpl}, - eth_api::{EthApiServer, EthApiServerImpl}, - web3_api::{Web3ApiServer, Web3ApiServerImpl}, - JsonRpcServer, -}; -use std::{collections::HashSet, future::pending}; -use tracing::info; - -#[derive(Parser)] -#[clap( - name = "silius-rpc", - about = "JSON-RPC server for ERC-4337 Account Abstraction Bundler" -)] -pub struct Opt { - #[clap(flatten)] - pub rpc_opts: RpcServiceOpts, - - // execution client rpc endpoint - #[clap(long, default_value = "http://127.0.0.1:8545")] - pub eth_client_address: String, - - #[clap(long, default_value = "127.0.0.1:3001")] - pub uopool_grpc_listen_address: String, - - #[clap(long, default_value = "127.0.0.1:3002")] - pub bundler_grpc_listen_address: String, -} - -#[tokio::main] -async fn main() -> Result<()> { - let opt: Opt = Opt::parse(); - - if !opt.rpc_opts.is_enabled() { - return Err(anyhow::anyhow!("No RPC protocol is enabled")); - } - - tracing_subscriber::fmt::init(); - - info!("Starting bundler JSON-RPC server..."); - - let api: HashSet = HashSet::from_iter(opt.rpc_opts.rpc_api.iter().cloned()); - - let mut server = JsonRpcServer::new( - opt.rpc_opts.rpc_listen_address.clone(), - opt.rpc_opts.http, - opt.rpc_opts.ws, - ) - .with_proxy(opt.eth_client_address) - .with_cors(opt.rpc_opts.cors_domain); - - if api.contains("web3") { - server.add_method(Web3ApiServerImpl {}.into_rpc())?; - } - - let uopool_grpc_client = - UoPoolClient::connect(format!("http://{}", opt.uopool_grpc_listen_address)).await?; - - if api.contains("eth") { - server.add_method( - EthApiServerImpl { - uopool_grpc_client: uopool_grpc_client.clone(), - } - .into_rpc(), - )?; - } - - if api.contains("debug") { - let bundler_grpc_client = - BundlerClient::connect(format!("http://{}", opt.bundler_grpc_listen_address)).await?; - server.add_method( - DebugApiServerImpl { - uopool_grpc_client, - bundler_grpc_client, - } - .into_rpc(), - )?; - } - - let _handle = server.start().await?; - info!( - "Started bundler JSON-RPC server at {:} with http: {:?} ws: {:?}", - opt.rpc_opts.rpc_listen_address, opt.rpc_opts.http, opt.rpc_opts.ws - ); - - pending::>().await -} diff --git a/bin/silius/src/silius-uopool.rs b/bin/silius/src/silius-uopool.rs deleted file mode 100644 index 33b9540a..00000000 --- a/bin/silius/src/silius-uopool.rs +++ /dev/null @@ -1,90 +0,0 @@ -use anyhow::{format_err, Result}; -use clap::Parser; -use ethers::{ - providers::{Http, Middleware, Provider}, - types::{Address, U256}, -}; -use silius::{ - cli::UoPoolServiceOpts, - utils::{parse_address, parse_u256, unwrap_path_or_home}, -}; -use silius_grpc::uopool_service_run; -use silius_primitives::{chain::SUPPORTED_CHAINS, Chain}; -use std::{future::pending, sync::Arc}; -use tracing::info; - -#[derive(Parser)] -#[clap( - name = "silius-uopool", - about = "User operation pool for ERC-4337 Account Abstraction Bundler" -)] -pub struct Opt { - #[clap(flatten)] - pub uopool_opts: UoPoolServiceOpts, - - #[clap(long, value_delimiter=',', value_parser=parse_address)] - pub entry_points: Vec
, - - #[clap(long, default_value= "dev", value_parser = SUPPORTED_CHAINS)] - pub chain: Option, - - // execution client rpc endpoint - #[clap(long, default_value = "127.0.0.1:8545")] - pub eth_client_address: String, - - #[clap(long, value_parser=parse_u256)] - pub max_verification_gas: U256, -} - -#[tokio::main] -async fn main() -> Result<()> { - let opt: Opt = Opt::parse(); - - tracing_subscriber::fmt::init(); - - let eth_client = Arc::new(Provider::::try_from(opt.eth_client_address.clone())?); - info!( - "Connected to Ethereum execution client at {}: {}", - opt.eth_client_address, - eth_client.client_version().await? - ); - - let chain_id = eth_client.get_chainid().await?; - let chain = Chain::from(chain_id); - - if let Some(chain_opt) = opt.chain { - if chain.name() != chain_opt { - return Err(format_err!( - "Bundler tries to connect to the execution client of different chain: {} != {}", - chain_opt, - chain.name() - )); - } - } - - let datadir = unwrap_path_or_home(opt.uopool_opts.datadir)?; - - info!("Starting uopool gRPC service..."); - - uopool_service_run( - opt.uopool_opts.uopool_grpc_listen_address, - datadir, - opt.entry_points, - eth_client, - chain, - opt.max_verification_gas, - opt.uopool_opts.min_stake, - opt.uopool_opts.min_unstake_delay, - opt.uopool_opts.min_priority_fee_per_gas, - opt.uopool_opts.whitelist, - opt.uopool_opts.uo_pool_mode, - ) - .await?; - - info!( - "Started uopool gRPC service at {:}", - opt.uopool_opts.uopool_grpc_listen_address - ); - - pending().await -} diff --git a/bin/silius/src/silius.rs b/bin/silius/src/silius.rs deleted file mode 100644 index 0656f9f0..00000000 --- a/bin/silius/src/silius.rs +++ /dev/null @@ -1,244 +0,0 @@ -use anyhow::{format_err, Result}; -use clap::Parser; -use ethers::{ - providers::{Http, Middleware, Provider}, - types::{Address, U256}, -}; -use expanded_pathbuf::ExpandedPathBuf; -use silius::{ - cli::{BundlerServiceOpts, RpcServiceOpts, UoPoolServiceOpts}, - utils::{parse_address, parse_u256, run_until_ctrl_c, unwrap_path_or_home}, -}; -use silius_grpc::{ - bundler_client::BundlerClient, bundler_service_run, uo_pool_client::UoPoolClient, - uopool_service_run, -}; -use silius_primitives::{ - bundler::SendBundleMode, chain::SUPPORTED_CHAINS, consts::flashbots_relay_endpoints, Chain, - Wallet, -}; -use silius_rpc::{ - debug_api::{DebugApiServer, DebugApiServerImpl}, - eth_api::{EthApiServer, EthApiServerImpl}, - web3_api::{Web3ApiServer, Web3ApiServerImpl}, - JsonRpcServer, -}; -use std::{collections::HashSet, future::pending, panic, sync::Arc}; -use tracing::info; - -#[derive(Parser)] -#[clap(name = "silius", about = "Bundler for ERC-4337 Account Abstraction")] -pub struct Opt { - #[clap(long)] - pub mnemonic_file: ExpandedPathBuf, - - #[clap(long, value_delimiter=',', value_parser=parse_address)] - pub entry_points: Vec
, - - #[clap(long)] - pub no_uopool: bool, - - #[clap(flatten)] - pub uopool_opts: UoPoolServiceOpts, - - #[clap(long, default_value="3000000", value_parser=parse_u256)] - pub max_verification_gas: U256, - - #[clap(flatten)] - pub rpc_opts: RpcServiceOpts, - - #[clap(long, default_value=None, value_parser = SUPPORTED_CHAINS)] - pub chain: Option, - - // execution client rpc endpoint - #[clap(long, default_value = "http://127.0.0.1:8545")] - pub eth_client_address: String, - - #[clap(flatten)] - pub bundler_opts: BundlerServiceOpts, -} - -fn main() -> Result<()> { - let opt: Opt = Opt::parse(); - - std::env::set_var("RUST_LOG", "info"); - tracing_subscriber::fmt::init(); - - std::thread::Builder::new() - .stack_size(128 * 1024 * 1024) - .spawn(move || { - let rt = tokio::runtime::Builder::new_multi_thread() - .enable_all() - .thread_stack_size(128 * 1024 * 1024) - .build()?; - - let task = async move { - info!("Starting ERC-4337 AA Bundler"); - - let eth_client = - Arc::new(Provider::::try_from(opt.eth_client_address.clone())?); - info!( - "Connected to the Ethereum execution client at {}: {}", - opt.eth_client_address, - eth_client.client_version().await? - ); - - let chain_id = eth_client.get_chainid().await?; - let chain = Chain::from(chain_id); - - if let Some(chain_opt) = opt.chain { - if chain.name() != chain_opt { - return Err(format_err!( - "Bundler tries to connect to the execution client of different chain: {} != {}", - chain_opt, - chain.name() - )); - } - } - - let wallet: Wallet; - if opt.bundler_opts.build_fb_signer == Some(true) { - wallet = Wallet::from_file( - opt.mnemonic_file.clone(), - &chain_id, - true, - ) - .map_err(|error| format_err!("Could not load mnemonic file: {}", error))?; - info!("Wallet Signer {:?}", wallet.signer); - info!("Flashbots Signer {:?}", wallet.fb_signer); - } else { - wallet = Wallet::from_file(opt.mnemonic_file.clone(), &chain_id, false) - .map_err(|error| format_err!("Could not load mnemonic file: {}", error))?; - info!("{:?}", wallet.signer); - } - - - if !opt.no_uopool { - let datadir = unwrap_path_or_home(opt.uopool_opts.datadir)?; - - info!("Starting uopool gRPC service..."); - uopool_service_run( - opt.uopool_opts.uopool_grpc_listen_address, - datadir, - opt.entry_points.clone(), - eth_client, - chain, - opt.max_verification_gas, - opt.uopool_opts.min_stake, - opt.uopool_opts.min_unstake_delay, - opt.uopool_opts.min_priority_fee_per_gas, - opt.uopool_opts.whitelist, - opt.uopool_opts.uo_pool_mode, - ) - .await?; - info!( - "Started uopool gRPC service at {:}", - opt.uopool_opts.uopool_grpc_listen_address - ); - } - - info!("Connecting to uopool gRPC service"); - let uopool_grpc_client = UoPoolClient::connect(format!( - "http://{}", - opt.uopool_opts.uopool_grpc_listen_address - )) - .await?; - info!("Connected to uopool gRPC service"); - - info!("Starting bundler gRPC service..."); - bundler_service_run( - opt.bundler_opts.bundler_grpc_listen_address, - wallet, - opt.entry_points, - opt.eth_client_address.clone(), - chain, - opt.bundler_opts.beneficiary, - opt.bundler_opts.min_balance, - opt.bundler_opts.bundle_interval, - uopool_grpc_client.clone(), - match opt.bundler_opts.send_bundle_mode.as_deref() { - Some(mode) => match mode { - "eth-client" => SendBundleMode::EthClient, - "flashbots" => SendBundleMode::Flashbots, - _ => SendBundleMode::EthClient, - }, - None => SendBundleMode::EthClient, - }, - match opt.bundler_opts.clone().send_bundle_mode { - Some(mode) => match mode.clone().as_str() { - "eth-client" => None, - "flashbots" => Some(vec![ - flashbots_relay_endpoints::FLASHBOTS.to_string(), - ]), - _ => None, - }, - None => None, - }, - ); - info!( - "Started bundler gRPC service at {:}", - opt.bundler_opts.bundler_grpc_listen_address - ); - - if opt.rpc_opts.is_enabled() { - - info!("Starting bundler JSON-RPC server..."); - tokio::spawn({ - async move { - let api: HashSet = - HashSet::from_iter(opt.rpc_opts.rpc_api.iter().cloned()); - - let mut server = JsonRpcServer::new(opt.rpc_opts.rpc_listen_address.clone(), opt.rpc_opts.http, opt.rpc_opts.ws) - .with_proxy(opt.eth_client_address) - .with_cors(opt.rpc_opts.cors_domain); - - if api.contains("web3") { - server.add_method(Web3ApiServerImpl{}.into_rpc())?; - } - - if api.contains("eth") { - server.add_method( - EthApiServerImpl { - uopool_grpc_client: uopool_grpc_client.clone(), - } - .into_rpc(), - )?; - } - - if api.contains("debug") { - let bundler_grpc_client = BundlerClient::connect(format!( - "http://{}", - opt.bundler_opts.bundler_grpc_listen_address - )) - .await?; - server.add_method( - DebugApiServerImpl { - uopool_grpc_client, - bundler_grpc_client, - } - .into_rpc(), - )?; - } - - let _handle = server.start().await?; - info!( - "Started bundler JSON-RPC server at {:} with http: {:?} ws: {:?}", - opt.rpc_opts.rpc_listen_address, - opt.rpc_opts.http, - opt.rpc_opts.ws - ); - - pending::>().await - } - }); - } - - pending().await - }; - rt.block_on(run_until_ctrl_c(task))?; - Ok(()) - - })? - .join() - .unwrap_or_else(|e| panic::resume_unwind(e)) -} diff --git a/bin/silius/src/utils.rs b/bin/silius/src/utils.rs index 950a705b..94f8abb6 100644 --- a/bin/silius/src/utils.rs +++ b/bin/silius/src/utils.rs @@ -2,7 +2,7 @@ use dirs::home_dir; use ethers::types::{Address, U256}; use expanded_pathbuf::ExpandedPathBuf; use pin_utils::pin_mut; -use silius_primitives::UoPoolMode; +use silius_primitives::{bundler::SendBundleMode, UoPoolMode}; use std::{future::Future, str::FromStr}; use tracing::info; @@ -28,6 +28,11 @@ pub fn parse_u256(s: &str) -> Result { U256::from_str_radix(s, 10).map_err(|_| format!("String {s} is not a valid U256")) } +/// Parses SendBundleMode from string +pub fn parse_send_bundle_mode(s: &str) -> Result { + SendBundleMode::from_str(s).map_err(|_| format!("String {s} is not a valid SendBundleMode")) +} + /// Parses UoPoolMode from string pub fn parse_uopool_mode(s: &str) -> Result { UoPoolMode::from_str(s).map_err(|_| format!("String {s} is not a valid UoPoolMode")) diff --git a/bundler-spec-tests/launcher.sh b/bundler-spec-tests/launcher.sh index 7887c06c..19f4ff4c 100644 --- a/bundler-spec-tests/launcher.sh +++ b/bundler-spec-tests/launcher.sh @@ -10,15 +10,19 @@ case $1 in start) docker-compose up -d - RUST_LOG=silius=TRACE silius \ - --rpc-listen-address 0.0.0.0:3000 \ + RUST_LOG=silius=TRACE silius bundler \ --eth-client-address http://localhost:8545 \ --mnemonic-file keys/0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 \ --beneficiary 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 \ --entry-points 0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789 \ --http \ + --http.api 0.0.0.0 \ + --http.port 3000 \ + --http.api eth,debug,web3 \ --ws \ - --rpc-api eth,debug,web3 & echo $! > bundler.pid + --ws.api 0.0.0.0 \ + --ws.port 3001 \ + --ws.api eth,debug,web3 & echo $! > bundler.pid popd cd @account-abstraction && yarn deploy --network localhost ;; diff --git a/crates/bundler/Cargo.toml b/crates/bundler/Cargo.toml index 6daacc1b..e7f0efd2 100644 --- a/crates/bundler/Cargo.toml +++ b/crates/bundler/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "silius-bundler" -version = "0.1.0" +version = { workspace = true } authors = { workspace = true } edition = { workspace = true } license = { workspace = true } diff --git a/crates/contracts/Cargo.toml b/crates/contracts/Cargo.toml index 076da723..17452161 100644 --- a/crates/contracts/Cargo.toml +++ b/crates/contracts/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "silius-contracts" -version = "0.1.0" +version = { workspace = true } authors = { workspace = true } edition = { workspace = true } license = { workspace = true } diff --git a/crates/grpc/Cargo.toml b/crates/grpc/Cargo.toml index a2be058d..8ebded98 100644 --- a/crates/grpc/Cargo.toml +++ b/crates/grpc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "silius-grpc" -version = "0.1.0" +version = { workspace = true } authors = { workspace = true } edition = { workspace = true } license = { workspace = true } diff --git a/crates/grpc/src/builder.rs b/crates/grpc/src/builder.rs index 1a938338..ae5e7e46 100644 --- a/crates/grpc/src/builder.rs +++ b/crates/grpc/src/builder.rs @@ -82,7 +82,7 @@ where } } - pub fn uo_pool(&self) -> UoPool, P, R, E> { + pub fn uopool(&self) -> UoPool, P, R, E> { let entry_point = EntryPoint::::new(self.eth_client.clone(), self.entrypoint_addr); let validator = if self.is_unsafe { diff --git a/crates/grpc/src/bundler.rs b/crates/grpc/src/bundler.rs index a508d3f0..ef24c683 100644 --- a/crates/grpc/src/bundler.rs +++ b/crates/grpc/src/bundler.rs @@ -191,7 +191,7 @@ impl bundler_server::Bundler for BundlerService { #[allow(clippy::too_many_arguments)] pub fn bundler_service_run( - grpc_listen_address: SocketAddr, + addr: SocketAddr, wallet: Wallet, eps: Vec
, eth_client_address: String, @@ -212,7 +212,7 @@ pub fn bundler_service_run( beneficiary, *ep, chain, - send_bundle_mode.clone(), + send_bundle_mode, relay_endpoints.clone(), min_balance, ) @@ -221,14 +221,11 @@ pub fn bundler_service_run( .collect(); let bundler_service = BundlerService::new(bundlers, uopool_grpc_client); - - info!("Bundler gRPC server starting on {}", grpc_listen_address); - bundler_service.start_bundling(bundle_interval); tokio::spawn(async move { let mut builder = tonic::transport::Server::builder(); let svc = bundler_server::BundlerServer::new(bundler_service); - builder.add_service(svc).serve(grpc_listen_address).await + builder.add_service(svc).serve(addr).await }); } diff --git a/crates/grpc/src/uopool.rs b/crates/grpc/src/uopool.rs index 87ed08d2..43e34b1c 100644 --- a/crates/grpc/src/uopool.rs +++ b/crates/grpc/src/uopool.rs @@ -25,7 +25,7 @@ use silius_uopool::{ use std::fmt::{Debug, Display}; use std::{net::SocketAddr, sync::Arc, time::Duration}; use tonic::{Code, Request, Response, Status}; -use tracing::{info, warn}; +use tracing::warn; pub const MAX_UOS_PER_UNSTAKED_SENDER: usize = 4; pub const GAS_INCREASE_PERC: u64 = 10; @@ -39,7 +39,7 @@ where P: Mempool + Send + Sync, R: Reputation, Error = E> + Send + Sync, { - pub uo_pools: Arc>>, + pub uopools: Arc>>, pub chain: Chain, } @@ -50,15 +50,15 @@ where R: Reputation, Error = E> + Send + Sync, E: Debug + Display, { - pub fn new(uo_pools: Arc>>, chain: Chain) -> Self { - Self { uo_pools, chain } + pub fn new(uopools: Arc>>, chain: Chain) -> Self { + Self { uopools, chain } } - fn get_uo_pool(&self, ep: &Address) -> tonic::Result> { + fn get_uopool(&self, ep: &Address) -> tonic::Result> { let m_id = mempool_id(ep, &U256::from(self.chain.id())); - self.uo_pools + self.uopools .get(&m_id) - .map(|b| b.uo_pool()) + .map(|b| b.uopool()) .ok_or(Status::new( Code::Unavailable, "User operation pool is not available", @@ -82,8 +82,8 @@ where let ep = parse_addr(req.ep)?; let res = { - let uo_pool = self.get_uo_pool(&ep)?; - match uo_pool.validate_user_operation(&uo).await { + let uopool = self.get_uopool(&ep)?; + match uopool.validate_user_operation(&uo).await { Ok(res) => res, Err(err) => { return Ok(Response::new(AddResponse { @@ -96,9 +96,9 @@ where } }; - let mut uo_pool = self.get_uo_pool(&ep)?; + let mut uopool = self.get_uopool(&ep)?; - match uo_pool.add_user_operation(uo, res).await { + match uopool.add_user_operation(uo, res).await { Ok(uo_hash) => Ok(Response::new(AddResponse { res: AddResult::Added as i32, data: serde_json::to_string(&uo_hash) @@ -122,9 +122,9 @@ where let req = req.into_inner(); let ep = parse_addr(req.ep)?; - let mut uo_pool = self.get_uo_pool(&ep)?; + let mut uopool = self.get_uopool(&ep)?; - uo_pool.remove_user_operations(req.hashes.into_iter().map(Into::into).collect()); + uopool.remove_user_operations(req.hashes.into_iter().map(Into::into).collect()); Ok(Response::new(())) } @@ -144,9 +144,9 @@ where ) -> Result, Status> { Ok(Response::new(GetSupportedEntryPointsResponse { eps: self - .uo_pools + .uopools .iter() - .map(|mempool| mempool.uo_pool().entry_point_address().into()) + .map(|mempool| mempool.uopool().entry_point_address().into()) .collect(), })) } @@ -160,10 +160,10 @@ where let uo = parse_uo(req.uo)?; let ep = parse_addr(req.ep)?; - let uo_pool = self.get_uo_pool(&ep)?; + let uopool = self.get_uopool(&ep)?; Ok(Response::new( - match uo_pool.estimate_user_operation_gas(&uo).await { + match uopool.estimate_user_operation_gas(&uo).await { Ok(gas) => EstimateUserOperationGasResponse { res: EstimateUserOperationGasResult::Estimated as i32, data: serde_json::to_string(&gas).map_err(|err| { @@ -189,15 +189,15 @@ where let ep = parse_addr(req.ep)?; let uos = { - let uo_pool = self.get_uo_pool(&ep)?; - uo_pool.get_sorted_user_operations().map_err(|e| { + let uopool = self.get_uopool(&ep)?; + uopool.get_sorted_user_operations().map_err(|e| { tonic::Status::internal(format!("Get sorted uos internal error: {e}")) })? }; let uos_valid = { - let mut uo_pool = self.get_uo_pool(&ep)?; - uo_pool + let mut uopool = self.get_uopool(&ep)?; + uopool .bundle_user_operations(uos) .await .map_err(|e| tonic::Status::internal(format!("Bundle uos internal error: {e}")))? @@ -215,9 +215,9 @@ where let req = req.into_inner(); let ep = parse_addr(req.ep)?; - let mut uo_pool = self.get_uo_pool(&ep)?; + let mut uopool = self.get_uopool(&ep)?; - uo_pool + uopool .handle_past_events() .await .map_err(|e| tonic::Status::internal(format!("Failed to handle past events: {e:?}")))?; @@ -233,9 +233,9 @@ where let uo_hash = parse_hash(req.hash)?; - for uo_pool in self.uo_pools.iter() { - if let Ok(uo_by_hash) = uo_pool - .uo_pool() + for uopool in self.uopools.iter() { + if let Ok(uo_by_hash) = uopool + .uopool() .get_user_operation_by_hash(&uo_hash.into()) .await { @@ -260,9 +260,9 @@ where let uo_hash = parse_hash(req.hash)?; - for uo_pool in self.uo_pools.iter() { - if let Ok(uo_receipt) = uo_pool - .uo_pool() + for uopool in self.uopools.iter() { + if let Ok(uo_receipt) = uopool + .uopool() .get_user_operation_receipt(&uo_hash.into()) .await { @@ -291,16 +291,16 @@ where let req = req.into_inner(); let ep = parse_addr(req.ep)?; - let uo_pool = self.get_uo_pool(&ep)?; + let uopool = self.get_uopool(&ep)?; Ok(Response::new(GetAllResponse { - uos: uo_pool.get_all().into_iter().map(Into::into).collect(), + uos: uopool.get_all().into_iter().map(Into::into).collect(), })) } async fn clear(&self, _req: Request<()>) -> Result, Status> { - self.uo_pools.iter_mut().for_each(|uo_pool| { - uo_pool.uo_pool().clear(); + self.uopools.iter_mut().for_each(|uopool| { + uopool.uopool().clear(); }); Ok(Response::new(())) } @@ -312,10 +312,10 @@ where let req = req.into_inner(); let ep = parse_addr(req.ep)?; - let uo_pool = self.get_uo_pool(&ep)?; + let uopool = self.get_uopool(&ep)?; Ok(Response::new(GetAllReputationResponse { - rep: uo_pool + rep: uopool .get_reputation() .into_iter() .map(Into::into) @@ -330,11 +330,10 @@ where let req = req.into_inner(); let ep = parse_addr(req.ep)?; - let mut uo_pool = self.get_uo_pool(&ep)?; + let mut uopool = self.get_uopool(&ep)?; let res = Response::new(SetReputationResponse { - res: match uo_pool.set_reputation(req.rep.iter().map(|re| re.clone().into()).collect()) - { + res: match uopool.set_reputation(req.rep.iter().map(|re| re.clone().into()).collect()) { Ok(_) => SetReputationResult::SetReputation as i32, Err(_) => SetReputationResult::NotSetReputation as i32, }, @@ -346,7 +345,7 @@ where #[allow(clippy::too_many_arguments)] pub async fn uopool_service_run( - grpc_listen_address: SocketAddr, + addr: SocketAddr, datadir: ExpandedPathBuf, eps: Vec
, eth_client: Arc>, @@ -356,7 +355,7 @@ pub async fn uopool_service_run( min_unstake_delay: U256, min_priority_fee_per_gas: U256, whitelist: Vec
, - uo_pool_mode: UoPoolMode, + upool_mode: UoPoolMode, ) -> Result<()> { tokio::spawn(async move { let mut builder = tonic::transport::Server::builder(); @@ -378,7 +377,7 @@ pub async fn uopool_service_run( for ep in eps { let id = mempool_id(&ep, &U256::from(chain.id())); let builder = UoPoolBuilder::new( - uo_pool_mode == UoPoolMode::Unsafe, + upool_mode == UoPoolMode::Unsafe, eth_client.clone(), ep, chain, @@ -404,7 +403,7 @@ pub async fn uopool_service_run( loop { m_map.iter_mut().for_each(|m| { let _ = m - .uo_pool() + .uopool() .reputation .update_hourly() .map_err(|e| warn!("Failed to update hourly reputation: {:?}", e)); @@ -413,9 +412,7 @@ pub async fn uopool_service_run( } }); - info!("UoPool gRPC server starting on {}", grpc_listen_address); - - builder.add_service(svc).serve(grpc_listen_address).await + builder.add_service(svc).serve(addr).await }); tokio::time::sleep(Duration::from_secs(1)).await; diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 1c6cc7ea..34f21649 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "silius-primitives" -version = "0.1.0" +version = { workspace = true } authors = { workspace = true } edition = { workspace = true } license = { workspace = true } diff --git a/crates/primitives/src/bundler.rs b/crates/primitives/src/bundler.rs index c1fddfc7..4c3ac8ae 100644 --- a/crates/primitives/src/bundler.rs +++ b/crates/primitives/src/bundler.rs @@ -1,4 +1,5 @@ use serde::Deserialize; +use strum_macros::{EnumString, EnumVariantNames}; /// Default time interval for auto bundling mode (in seconds) pub const DEFAULT_BUNDLE_INTERVAL: u64 = 10; @@ -13,7 +14,8 @@ pub enum Mode { } /// The `SendBundleMode` determines whether to send the bundle to a Ethereum execution client or to Flashbots relay -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, EnumString, EnumVariantNames, PartialEq, Eq)] +#[strum(serialize_all = "kebab_case")] pub enum SendBundleMode { /// Send the bundle to a Ethereum execution client EthClient, diff --git a/crates/primitives/src/consts.rs b/crates/primitives/src/consts.rs index ac875336..991df1f9 100644 --- a/crates/primitives/src/consts.rs +++ b/crates/primitives/src/consts.rs @@ -58,3 +58,11 @@ pub mod supported_networks { pub const DEV: u64 = 1337; pub const MUMBAI: u64 = 80001; } + +/// Networking related constants +pub mod networking { + pub const DEFAULT_HTTP_RPC_PORT: u16 = 3000; + pub const DEFAULT_WS_RPC_PORT: u16 = 3001; + pub const DEFAULT_UOPOOL_GRPC_PORT: u16 = 3002; + pub const DEFAULT_BUNDLER_GRPC_PORT: u16 = 3003; +} diff --git a/crates/primitives/src/wallet.rs b/crates/primitives/src/wallet.rs index a75dc78f..e46a7bd4 100644 --- a/crates/primitives/src/wallet.rs +++ b/crates/primitives/src/wallet.rs @@ -19,19 +19,19 @@ pub struct Wallet { impl Wallet { /// Builds a `Wallet` and construct using a randomly generated number - /// if `build_fb_wallet` is true, then a Flashbots wallet is also built, otherwise it is set to None + /// if `flashbots_key` is true, then a Flashbots key is also created, otherwise it is set to None /// /// # Arguments /// * `path` - The path to the file where the mnemonic phrase will be written /// * `chain_id` - The chain id of the blockchain network to be used - /// * `build_fb_wallet` - Whether to build a Flashbots wallet + /// * `flashbots_key` - Whether to create a Flashbots key /// /// # Returns /// * `Self` - A new `Wallet` instance pub fn build_random( path: ExpandedPathBuf, chain_id: &U256, - build_fb_wallet: bool, + flashbots_key: bool, ) -> anyhow::Result { let mut rng = rand::thread_rng(); @@ -44,7 +44,7 @@ impl Wallet { .expect("Failed to derive wallet") .build_random(&mut rng)?; - if build_fb_wallet { + if flashbots_key { let mut entries = fs::read_dir(&path)?; let entry = entries.next().expect("No file found")?; @@ -67,19 +67,19 @@ impl Wallet { } /// Create a new wallet from the given file containing the mnemonic phrase - /// if `build_fb_wallet` is true, then a Flashbots wallet is also built, otherwise it is set to None + /// if `flashbots_key` is true, then a Flashbots key is also created, otherwise it is set to None /// /// # Arguments /// * `path` - The path to the file where the mnemonic phrase is stored /// * `chain_id` - The chain id of the blockchain network to be used - /// * `build_fb_wallet` - Whether to build a Flashbots wallet + /// * `flashbots_key` - Whether to create a Flashbots key /// /// # Returns /// * `Self` - A new `Wallet` instance pub fn from_file( path: ExpandedPathBuf, chain_id: &U256, - build_fb_wallet: bool, + flashbots_key: bool, ) -> anyhow::Result { let wallet_builder = MnemonicBuilder::::default().phrase(path.to_path_buf()); @@ -89,7 +89,7 @@ impl Wallet { .expect("Failed to derive wallet") .build()?; - if build_fb_wallet { + if flashbots_key { let fb_wallet = wallet_builder .derivation_path("m/44'/60'/0'/0/1") .expect("Failed to derive wallet") @@ -108,20 +108,16 @@ impl Wallet { } /// Create a new wallet from the given mnemonic phrase - /// if `build_fb_wallet` is true, then a Flashbots wallet is also built, otherwise it is set to None + /// if `flashbots_key` is true, then a Flashbots key is also created, otherwise it is set to None /// /// # Arguments /// * `phrase` - The mnemonic phrase /// * `chain_id` - The chain id of the blockchain network to be used - /// * `build_fb_wallet` - Whether to build a Flashbots wallet + /// * `flashbots_key` - Whether to create a Flashbots key /// /// # Returns /// * `Self` - A new `Wallet` instance - pub fn from_phrase( - phrase: &str, - chain_id: &U256, - build_fb_wallet: bool, - ) -> anyhow::Result { + pub fn from_phrase(phrase: &str, chain_id: &U256, flashbots_key: bool) -> anyhow::Result { let wallet_builder = MnemonicBuilder::::default().phrase(phrase); let wallet = wallet_builder @@ -130,7 +126,7 @@ impl Wallet { .expect("Failed to derive wallet") .build()?; - if build_fb_wallet { + if flashbots_key { let fb_wallet = wallet_builder .derivation_path("m/44'/60'/0'/0/1") .expect("Failed to derive wallet") diff --git a/crates/rpc/Cargo.toml b/crates/rpc/Cargo.toml index 7bb53005..c90839d3 100644 --- a/crates/rpc/Cargo.toml +++ b/crates/rpc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "silius-rpc" -version = "0.1.0" +version = { workspace = true } authors = { workspace = true } edition = { workspace = true } license = { workspace = true } diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs index 185cbd11..9e60fc02 100644 --- a/crates/rpc/src/lib.rs +++ b/crates/rpc/src/lib.rs @@ -11,4 +11,4 @@ mod rpc; mod web3; pub mod web3_api; -pub use rpc::JsonRpcServer; +pub use rpc::{JsonRpcServer, JsonRpcServerType}; diff --git a/crates/rpc/src/rpc.rs b/crates/rpc/src/rpc.rs index a49bca15..7fd3ce64 100644 --- a/crates/rpc/src/rpc.rs +++ b/crates/rpc/src/rpc.rs @@ -5,26 +5,37 @@ use jsonrpsee::{ server::{ServerBuilder, ServerHandle}, Methods, }; +use std::net::{IpAddr, SocketAddr}; use tower::ServiceBuilder; use tower_http::cors::{AllowOrigin, Any, CorsLayer}; /// JsonRpcServer is a wrapper around the `jsonrpsee` [ServerBuilder](https://docs.rs/jsonrpsee/3.0.0-beta.1/jsonrpsee/server/struct.ServerBuilder.html). pub struct JsonRpcServer { - /// The address to listen on. - listen_address: String, - /// The [cors layer](CorsLayer) to filter requests. - cors_layer: Option, - /// The [proxy layer](ProxyJsonRpcLayer) to forward requests. - proxy_layer: Option, /// Whether to start an HTTP server. http: bool, + /// HTTP address to listen on. + http_addr: IpAddr, + /// HTTP port to listen on. + http_port: u16, + /// The HTTP RPC methods to be exposed. + http_methods: Methods, + /// The [cors layer](CorsLayer) for HTTP server to filter requests. + http_cors_layer: Option, /// Whether to start a WS server. ws: bool, - /// The RPC methods to be exposed. - methods: Methods, + /// WS address to listen on. + ws_addr: IpAddr, + /// WS port to listen on. + ws_port: u16, + /// The WS RPC methods to be exposed. + ws_methods: Methods, + /// The [cors layer](CorsLayer) for WS server to filter requests. + ws_cors_layer: Option, + /// The [proxy layer](ProxyJsonRpcLayer) to forward requests. + proxy_layer: Option, } -enum JsonRpcProtocolType { +pub enum JsonRpcServerType { /// Both HTTP and WS. Both, /// Only HTTP. @@ -37,18 +48,35 @@ impl JsonRpcServer { /// Create a new JsonRpcServer. /// /// # Arguments - /// * `listen_address: String` - The address to listen on. + /// * `http: bool` - Whether to start an HTTP server. + /// * `http_addr: IpAddr` - HTTP address to listen on. + /// * `http_port: u16` - HTTP port to listen on. + /// * `ws: bool` - Whether to start a WS server. + /// * `ws_addr: IpAddr` - WS address to listen on. + /// * `ws_port: u16` - WS port to listen on. /// /// # Returns /// * `Self` - A new [JsonRpcServer](JsonRpcServer) instance. - pub fn new(listen_address: String, http: bool, ws: bool) -> Self { + pub fn new( + http: bool, + http_addr: IpAddr, + http_port: u16, + ws: bool, + ws_addr: IpAddr, + ws_port: u16, + ) -> Self { Self { - listen_address, - cors_layer: None, - proxy_layer: None, http, + http_addr, + http_port, + http_methods: Methods::new(), + http_cors_layer: None, ws, - methods: Methods::new(), + ws_addr, + ws_port, + ws_methods: Methods::new(), + ws_cors_layer: None, + proxy_layer: None, } } @@ -56,10 +84,11 @@ impl JsonRpcServer { /// /// # Arguments /// * `cors_domain: Vec` - A list of CORS filters in the form of String. + /// * `typ: JsonRpcServerType` - The type of the server. /// /// # Returns /// * `Self` - A new [JsonRpcServer](JsonRpcServer) instance. - pub fn with_cors(mut self, cors_domain: Vec) -> Self { + pub fn with_cors(mut self, cors_domain: &[String], typ: JsonRpcServerType) -> Self { let cors_layer = if cors_domain.iter().any(|d| d == "*") { CorsLayer::new() .allow_headers(Any) @@ -80,7 +109,15 @@ impl JsonRpcServer { .allow_origin(AllowOrigin::list(origins)) }; - self.cors_layer = Some(cors_layer); + match typ { + JsonRpcServerType::Both => { + self.http_cors_layer = Some(cors_layer.clone()); + self.ws_cors_layer = Some(cors_layer); + } + JsonRpcServerType::Http => self.http_cors_layer = Some(cors_layer), + JsonRpcServerType::Ws => self.ws_cors_layer = Some(cors_layer), + } + self } @@ -96,58 +133,67 @@ impl JsonRpcServer { self } - /// Add a method to the RPC server. + /// Add methods to the RPC server. /// /// # Arguments /// * `methods: impl Into` - The RPC methods to be exposed. + /// * `typ: JsonRpcServerType` - The type of the server. /// /// # Returns /// * `Result<(), Error>` - None if no error. - pub fn add_method(&mut self, methods: impl Into) -> Result<(), Error> { - self.methods.merge(methods).map_err(|e| e.into()) + pub fn add_methods( + &mut self, + methods: impl Into, + typ: JsonRpcServerType, + ) -> Result<(), Error> { + let methods: Methods = methods.into(); + + match typ { + JsonRpcServerType::Both => { + self.http_methods.merge(methods.clone())?; + self.ws_methods.merge(methods).map_err(|e| e.into()) + } + JsonRpcServerType::Http => self.http_methods.merge(methods).map_err(|e| e.into()), + JsonRpcServerType::Ws => self.ws_methods.merge(methods).map_err(|e| e.into()), + } } /// Start the [json RPC server](JsonRpcServer) /// /// # Returns - /// * `Result` - The [handle](ServerHandle) of the server. - pub async fn start(&self) -> anyhow::Result { - let service = ServiceBuilder::new() - .option_layer(self.cors_layer.clone()) - .option_layer(self.proxy_layer.clone()); - - let server = self - .server_builder()? - .set_middleware(service) - .build(&self.listen_address) - .await?; - - Ok(server.start(self.methods.clone())?) - } + /// * `Result<(Option, Option), Error>` - The [handle]((Option, Option)) of the HTTP and WS servers. + pub async fn start(&self) -> anyhow::Result<(Option, Option)> { + let http_handle = if self.http { + let service = ServiceBuilder::new() + .option_layer(self.http_cors_layer.clone()) + .option_layer(self.proxy_layer.clone()); - /// Create a [ServerBuilder](ServerBuilder) based on the server type. - /// - /// # Returns - /// * `Result` - The [ServerBuilder](ServerBuilder) instance. - fn server_builder(&self) -> anyhow::Result { - let protocol_type = self.protocol_type()?; - match protocol_type { - JsonRpcProtocolType::Both => Ok(ServerBuilder::new()), - JsonRpcProtocolType::Http => Ok(ServerBuilder::new().http_only()), - JsonRpcProtocolType::Ws => Ok(ServerBuilder::new().ws_only()), - } - } + let server = ServerBuilder::new() + .http_only() + .set_middleware(service) + .build(SocketAddr::new(self.http_addr, self.http_port)) + .await?; - /// Get the protocol type based on the server configuration. - /// - /// # Returns - /// * `Result` - The [JsonRpcProtocolType](JsonRpcProtocolType) instance. - fn protocol_type(&self) -> anyhow::Result { - match (self.http, self.ws) { - (true, true) => Ok(JsonRpcProtocolType::Both), - (true, false) => Ok(JsonRpcProtocolType::Http), - (false, true) => Ok(JsonRpcProtocolType::Ws), - (false, false) => Err(anyhow::anyhow!("No protocol type selected")), - } + Some(server.start(self.http_methods.clone())?) + } else { + None + }; + let ws_handle = if self.ws { + let service = ServiceBuilder::new() + .option_layer(self.ws_cors_layer.clone()) + .option_layer(self.proxy_layer.clone()); + + let server = ServerBuilder::new() + .ws_only() + .set_middleware(service) + .build(SocketAddr::new(self.ws_addr, self.ws_port)) + .await?; + + Some(server.start(self.ws_methods.clone())?) + } else { + None + }; + + Ok((http_handle, ws_handle)) } } diff --git a/crates/rpc/tests/common.rs b/crates/rpc/tests/common.rs index 66c091c9..72e18c3d 100644 --- a/crates/rpc/tests/common.rs +++ b/crates/rpc/tests/common.rs @@ -6,18 +6,20 @@ use jsonrpsee::{ core::{Error as RpcError, RpcResult}, proc_macros::rpc, }; -use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; +use std::net::{IpAddr, Ipv4Addr}; use std::sync::atomic::{AtomicU16, Ordering}; +pub static ADDRESS: Ipv4Addr = Ipv4Addr::UNSPECIFIED; static PORT: AtomicU16 = AtomicU16::new(8000); -/// test_address returns a address on localhost with a increasing port number. + +/// test_port returns a port with a increasing port number. /// This is to prevent multiple tests from using the same port. /// /// # Returns -/// * `String` - A localhost address with a increasing port number. -pub fn test_address() -> String { +/// * `u16` - A port with a increasing port number. +pub fn test_port() -> u16 { let port = PORT.fetch_add(1, Ordering::SeqCst); - SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, port)).to_string() + port } #[rpc(client, server, namespace = "eth")] @@ -38,12 +40,12 @@ impl DummyEthApiServer for DummyEthApiServerImpl { } } -pub fn build_http_client(address: String) -> Result { - HttpClientBuilder::default().build(format!("http://{}", address)) +pub fn build_http_client(addr: IpAddr, port: u16) -> Result { + HttpClientBuilder::default().build(format!("http://{}:{}", addr, port)) } -pub async fn build_ws_client(address: String) -> Result { +pub async fn build_ws_client(addr: IpAddr, port: u16) -> Result { WsClientBuilder::default() - .build(format!("ws://{}", address)) + .build(format!("ws://{}:{}", addr, port)) .await } diff --git a/crates/rpc/tests/rpc.rs b/crates/rpc/tests/rpc.rs index 59bcd4a8..235ae1dc 100644 --- a/crates/rpc/tests/rpc.rs +++ b/crates/rpc/tests/rpc.rs @@ -1,85 +1,102 @@ mod common; +use crate::common::{test_port, ADDRESS}; use common::{ - build_http_client, build_ws_client, test_address, DummyEthApiClient, DummyEthApiServer, - DummyEthApiServerImpl, + build_http_client, build_ws_client, DummyEthApiClient, DummyEthApiServer, DummyEthApiServerImpl, }; use ethers::types::U64; -use silius_rpc::JsonRpcServer; +use silius_rpc::{JsonRpcServer, JsonRpcServerType}; +use std::net::IpAddr; use tokio; #[tokio::test] async fn only_http_rpc_server() { - let address = test_address(); - let http_enabled = true; - let ws_disabled = false; - let mut server = JsonRpcServer::new(address.clone(), http_enabled, ws_disabled); + let addr = IpAddr::from(ADDRESS); + let port = test_port(); + let http: bool = true; + let ws = false; + + let mut server = JsonRpcServer::new(http, addr.clone(), port, ws, addr.clone(), port); let chain_id: U64 = U64::from(0x7a69); server - .add_method(DummyEthApiServerImpl { chain_id }.into_rpc()) + .add_methods( + DummyEthApiServerImpl { chain_id }.into_rpc(), + JsonRpcServerType::Http, + ) .unwrap(); - let handle = server.start().await.unwrap(); - tokio::spawn(handle.stopped()); + let (http_handle, _ws_handle) = server.start().await.unwrap(); + tokio::spawn(http_handle.unwrap().stopped()); // http client return success response - let http_client = build_http_client(address.clone()).unwrap(); + let http_client = build_http_client(addr.clone(), port).unwrap(); let http_response = DummyEthApiClient::chain_id(&http_client).await.unwrap(); assert_eq!(http_response, chain_id); // ws client cannot connect to http server - assert!(build_ws_client(address.clone()).await.is_err()); + assert!(build_ws_client(addr, port).await.is_err()); } #[tokio::test] async fn only_ws_rpc_server() { - let address = test_address(); - let http_disabled = false; - let ws_enabled = true; - let mut server = JsonRpcServer::new(address.clone(), http_disabled, ws_enabled); + let addr = IpAddr::from(ADDRESS); + let port = test_port(); + let http = false; + let ws = true; + let mut server = JsonRpcServer::new(http, addr.clone(), port, ws, addr.clone(), port); let chain_id: U64 = U64::from(0x7a69); server - .add_method(DummyEthApiServerImpl { chain_id }.into_rpc()) + .add_methods( + DummyEthApiServerImpl { chain_id }.into_rpc(), + JsonRpcServerType::Ws, + ) .unwrap(); - let handle = server.start().await.unwrap(); - tokio::spawn(handle.stopped()); + let (_http_handle, ws_handle) = server.start().await.unwrap(); + tokio::spawn(ws_handle.unwrap().stopped()); // http client return error response - let http_client = build_http_client(address.clone()).unwrap(); + let http_client = build_http_client(addr.clone(), port).unwrap(); let http_response = DummyEthApiClient::chain_id(&http_client).await; assert!(http_response.is_err()); // ws client return success response - let ws_client = build_ws_client(address.clone()).await.unwrap(); + let ws_client = build_ws_client(addr, port).await.unwrap(); let ws_response = DummyEthApiClient::chain_id(&ws_client).await.unwrap(); assert_eq!(ws_response, chain_id); } #[tokio::test] async fn http_and_ws_rpc_server() { - let address = test_address(); - let http_enabled = true; - let ws_enabled = true; - let mut server = JsonRpcServer::new(address.clone(), http_enabled, ws_enabled); + let addr = IpAddr::from(ADDRESS); + let port = test_port(); + let http = true; + let ws = true; + let mut server = JsonRpcServer::new(http, addr.clone(), port, ws, addr.clone(), port); let chain_id: U64 = U64::from(0x7a69); server - .add_method(DummyEthApiServerImpl { chain_id }.into_rpc()) + .add_methods( + DummyEthApiServerImpl { chain_id }.into_rpc(), + JsonRpcServerType::Both, + ) .unwrap(); - let handle = server.start().await.unwrap(); - tokio::spawn(handle.stopped()); + let (http_handle, ws_handle) = server.start().await.unwrap(); + tokio::spawn({ + http_handle.unwrap().stopped().await; + ws_handle.unwrap().stopped() + }); // http client return success response - let http_client = build_http_client(address.clone()).unwrap(); + let http_client = build_http_client(addr.clone(), port).unwrap(); let http_response = DummyEthApiClient::chain_id(&http_client).await.unwrap(); assert_eq!(http_response, chain_id); // ws client return success response - let ws_client = build_ws_client(address.clone()).await.unwrap(); + let ws_client = build_ws_client(addr, port).await.unwrap(); let ws_response = DummyEthApiClient::chain_id(&ws_client).await.unwrap(); assert_eq!(ws_response, chain_id); } diff --git a/crates/uopool/Cargo.toml b/crates/uopool/Cargo.toml index 7f0dcf74..fff9aaf4 100644 --- a/crates/uopool/Cargo.toml +++ b/crates/uopool/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "silius-uopool" -version = "0.1.0" +version = { workspace = true } authors = { workspace = true } edition = { workspace = true } license = { workspace = true } diff --git a/docker-compose.yml b/docker-compose.yml index 62357b52..a83b96b2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,19 +7,28 @@ services: - ./bundler-spec-tests/keys/0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266:/data/silius/0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 - ./db:/data/silius/db command: - - --rpc-listen-address - - 0.0.0.0:3000 - - --eth-client-address + - bundler + - --eth-client-address - http://127.0.0.1:8545 - --datadir - data/silius - - --mnemonic-file - - data/silius/0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 - - --beneficiary + - --mnemonic-file + - data/silius/0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + - --beneficiary - 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 - --entry-points - 0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789 - --http - - --ws - - --rpc-api + - --http.addr + - 0.0.0.0 + - --http.port + - "3000" + - --http.api + - eth,debug,web3 + - --ws + - --ws.addr + - 0.0.0.0 + - --ws.port + - "3001" + - --ws.api - eth,debug,web3 diff --git a/examples/Cargo.toml b/examples/Cargo.toml index a1132059..e48e2ab6 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "silius-examples" -version = "0.1.0" +version = { workspace = true } authors = { workspace = true } edition = { workspace = true } license = { workspace = true } diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 49918e89..079a56e6 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "silius-tests" -version = "0.1.0" +version = { workspace = true } authors = { workspace = true } edition = { workspace = true } license = { workspace = true }